mirror of
https://github.com/libretro/RetroArch
synced 2025-02-01 00:32:46 +00:00
fcccc9dc0b
Added a platform parameter to egl_init_context. If the caller provides a platform other than EGL_NONE, then it will try to use eglGetPlatformDisplay or eglGetPlatformDisplayEXT instead of eglGetDisplay. If neither eglGetPlatformDisplay or eglGetPlatformDisplayEXT is supported, then it will still fall back to calling eglGetDisplay. Updated the Wayland, X11, and DRM callers to use the correct platform enum. Those are the callers that don't just pass EGL_DEFAULT_DISPLAY as the native display handle. Calling eglGetDisplay with any value other than EGL_DEFAULT_DISPLAY is inherently unreliable, because it requires the EGL implementation to guess a platform type based on a (void *) pointer. Some implementations might not identify a particular platform, or worse, might guess wrong. Fixes https://github.com/libretro/RetroArch/issues/4790
622 lines
15 KiB
C
622 lines
15 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <stdint.h>
|
|
|
|
#include <sys/system_properties.h>
|
|
|
|
#include <formats/image.h>
|
|
#include <string/stdstring.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../../config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_EGL
|
|
#include "../common/egl_common.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_OPENGLES
|
|
#include "../common/gl_common.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_VULKAN
|
|
#include "../common/vulkan_common.h"
|
|
#endif
|
|
|
|
#include "../../frontend/drivers/platform_linux.h"
|
|
|
|
static enum gfx_ctx_api android_api = GFX_CTX_NONE;
|
|
|
|
/* forward declaration */
|
|
int system_property_get(const char *cmd, const char *args, char *value);
|
|
|
|
#ifdef HAVE_OPENGLES
|
|
static bool g_es3 = false;
|
|
|
|
#ifndef EGL_OPENGL_ES3_BIT_KHR
|
|
#define EGL_OPENGL_ES3_BIT_KHR 0x0040
|
|
#endif
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
#ifdef HAVE_EGL
|
|
egl_ctx_data_t egl;
|
|
#endif
|
|
#ifdef HAVE_VULKAN
|
|
gfx_ctx_vulkan_data_t vk;
|
|
unsigned width;
|
|
unsigned height;
|
|
unsigned swap_interval;
|
|
#endif
|
|
} android_ctx_data_t;
|
|
|
|
static void android_gfx_ctx_destroy(void *data)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
#ifdef HAVE_VULKAN
|
|
struct android_app *android_app = (struct android_app*)g_android;
|
|
#endif
|
|
|
|
if (!and)
|
|
return;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
egl_destroy(&and->egl);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
vulkan_context_destroy(&and->vk, android_app->window);
|
|
|
|
if (and->vk.context.queue_lock)
|
|
slock_free(and->vk.context.queue_lock);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
free(data);
|
|
}
|
|
|
|
static void *android_gfx_ctx_init(video_frame_info_t *video_info, void *video_driver)
|
|
{
|
|
#ifdef HAVE_OPENGLES
|
|
EGLint n, major, minor;
|
|
EGLint format;
|
|
#if 0
|
|
struct retro_hw_render_callback *hwr = video_driver_get_hw_context();
|
|
bool debug = hwr->debug_context;
|
|
#endif
|
|
EGLint context_attributes[] = {
|
|
EGL_CONTEXT_CLIENT_VERSION, g_es3 ? 3 : 2,
|
|
#if 0
|
|
EGL_CONTEXT_FLAGS_KHR, debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0,
|
|
#endif
|
|
EGL_NONE
|
|
};
|
|
EGLint attribs[] = {
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_ALPHA_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 16,
|
|
EGL_NONE
|
|
};
|
|
#endif
|
|
struct android_app *android_app = (struct android_app*)g_android;
|
|
android_ctx_data_t *and = (android_ctx_data_t*)calloc(1, sizeof(*and));
|
|
|
|
if (!android_app || !and)
|
|
return false;
|
|
|
|
#ifdef HAVE_OPENGLES
|
|
if (g_es3)
|
|
attribs[1] = EGL_OPENGL_ES3_BIT_KHR;
|
|
#endif
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
RARCH_LOG("Android EGL: GLES version = %d.\n", g_es3 ? 3 : 2);
|
|
|
|
if (!egl_init_context(&and->egl, EGL_NONE, EGL_DEFAULT_DISPLAY,
|
|
&major, &minor, &n, attribs))
|
|
{
|
|
egl_report_error();
|
|
goto error;
|
|
}
|
|
|
|
if (!egl_get_native_visual_id(&and->egl, &format))
|
|
goto error;
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
if (!vulkan_context_init(&and->vk, VULKAN_WSI_ANDROID))
|
|
goto error;
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
slock_lock(android_app->mutex);
|
|
if (!android_app->window)
|
|
goto unlock_error;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
ANativeWindow_setBuffersGeometry(android_app->window, 0, 0, format);
|
|
|
|
#ifdef HAVE_EGL
|
|
if (!egl_create_context(&and->egl, context_attributes))
|
|
{
|
|
egl_report_error();
|
|
goto unlock_error;
|
|
}
|
|
|
|
if (!egl_create_surface(&and->egl, android_app->window))
|
|
goto unlock_error;
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
slock_unlock(android_app->mutex);
|
|
return and;
|
|
|
|
unlock_error:
|
|
slock_unlock(android_app->mutex);
|
|
error:
|
|
android_gfx_ctx_destroy(and);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void android_gfx_ctx_get_video_size(void *data,
|
|
unsigned *width, unsigned *height)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
egl_get_video_size(&and->egl, width, height);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
*width = and->width;
|
|
*height = and->height;
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void android_gfx_ctx_check_window(void *data, bool *quit,
|
|
bool *resize, unsigned *width, unsigned *height,
|
|
bool is_shutdown)
|
|
{
|
|
unsigned new_width = 0;
|
|
unsigned new_height = 0;
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
|
|
*quit = false;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
egl_get_video_size(&and->egl, &new_width, &new_height);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
/* Swapchains are recreated in set_resize as a
|
|
* central place, so use that to trigger swapchain reinit. */
|
|
*resize = and->vk.need_new_swapchain;
|
|
new_width = and->width;
|
|
new_height = and->height;
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (new_width != *width || new_height != *height)
|
|
{
|
|
RARCH_LOG("[Android]: Resizing (%u x %u) -> (%u x %u).\n",
|
|
*width, *height, new_width, new_height);
|
|
|
|
*width = new_width;
|
|
*height = new_height;
|
|
*resize = true;
|
|
}
|
|
|
|
/* Check if we are exiting. */
|
|
if (is_shutdown)
|
|
*quit = true;
|
|
}
|
|
|
|
static bool android_gfx_ctx_set_resize(void *data,
|
|
unsigned width, unsigned height)
|
|
{
|
|
#ifdef HAVE_VULKAN
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
struct android_app *android_app = (struct android_app*)g_android;
|
|
#endif
|
|
(void)data;
|
|
(void)width;
|
|
(void)height;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
and->width = ANativeWindow_getWidth(android_app->window);
|
|
and->height = ANativeWindow_getHeight(android_app->window);
|
|
RARCH_LOG("[Android]: Native window size: %u x %u.\n", and->width, and->height);
|
|
if (!vulkan_create_swapchain(&and->vk, and->width, and->height, and->swap_interval))
|
|
{
|
|
RARCH_ERR("[Android]: Failed to update swapchain.\n");
|
|
return false;
|
|
}
|
|
|
|
and->vk.context.invalid_swapchain = true;
|
|
and->vk.need_new_swapchain = false;
|
|
#endif
|
|
break;
|
|
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool android_gfx_ctx_set_video_mode(void *data,
|
|
video_frame_info_t *video_info,
|
|
unsigned width, unsigned height,
|
|
bool fullscreen)
|
|
{
|
|
#ifdef HAVE_VULKAN
|
|
struct android_app *android_app = (struct android_app*)g_android;
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
#endif
|
|
|
|
(void)width;
|
|
(void)height;
|
|
(void)fullscreen;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
and->width = ANativeWindow_getWidth(android_app->window);
|
|
and->height = ANativeWindow_getHeight(android_app->window);
|
|
RARCH_LOG("[Android]: Native window size: %u x %u.\n", and->width, and->height);
|
|
if (!vulkan_surface_create(&and->vk, VULKAN_WSI_ANDROID, NULL, android_app->window,
|
|
and->width, and->height, and->swap_interval))
|
|
{
|
|
RARCH_ERR("[Android]: Failed to create surface.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void android_gfx_ctx_input_driver(void *data,
|
|
const char *joypad_name,
|
|
const input_driver_t **input, void **input_data)
|
|
{
|
|
void *androidinput = input_android.init(joypad_name);
|
|
|
|
*input = androidinput ? &input_android : NULL;
|
|
*input_data = androidinput;
|
|
}
|
|
|
|
static bool android_gfx_ctx_bind_api(void *data,
|
|
enum gfx_ctx_api api, unsigned major, unsigned minor)
|
|
{
|
|
unsigned version;
|
|
|
|
switch (api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_OPENGLES
|
|
version = major * 100 + minor;
|
|
if (version > 300)
|
|
return false;
|
|
if (version < 300)
|
|
g_es3 = false;
|
|
else if (version == 300)
|
|
g_es3 = true;
|
|
|
|
if (api == GFX_CTX_OPENGL_ES_API)
|
|
{
|
|
android_api = api;
|
|
return true;
|
|
}
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
android_api = api;
|
|
return true;
|
|
#else
|
|
break;
|
|
#endif
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool android_gfx_ctx_has_focus(void *data)
|
|
{
|
|
bool focused = false;
|
|
struct android_app *android_app = (struct android_app*)g_android;
|
|
if (!android_app)
|
|
return true;
|
|
|
|
slock_lock(android_app->mutex);
|
|
focused = !android_app->unfocused;
|
|
slock_unlock(android_app->mutex);
|
|
|
|
return focused;
|
|
}
|
|
|
|
static bool android_gfx_ctx_suppress_screensaver(void *data, bool enable)
|
|
{
|
|
(void)data;
|
|
(void)enable;
|
|
return false;
|
|
}
|
|
|
|
static void dpi_get_density(char *s, size_t len)
|
|
{
|
|
system_property_get("getprop", "ro.sf.lcd_density", s);
|
|
|
|
if (string_is_empty(s))
|
|
system_property_get("wm", "density", s);
|
|
}
|
|
|
|
static bool android_gfx_ctx_get_metrics(void *data,
|
|
enum display_metric_types type, float *value)
|
|
{
|
|
static int dpi = -1;
|
|
|
|
switch (type)
|
|
{
|
|
case DISPLAY_METRIC_MM_WIDTH:
|
|
return false;
|
|
case DISPLAY_METRIC_MM_HEIGHT:
|
|
return false;
|
|
case DISPLAY_METRIC_DPI:
|
|
if (dpi == -1)
|
|
{
|
|
char density[PROP_VALUE_MAX];
|
|
density[0] = '\0';
|
|
|
|
dpi_get_density(density, sizeof(density));
|
|
if (string_is_empty(density))
|
|
goto dpi_fallback;
|
|
dpi = atoi(density);
|
|
|
|
if (dpi <= 0)
|
|
goto dpi_fallback;
|
|
}
|
|
*value = (float)dpi;
|
|
break;
|
|
case DISPLAY_METRIC_NONE:
|
|
default:
|
|
*value = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
dpi_fallback:
|
|
/* add a fallback in case the device doesn't report DPI.
|
|
* Hopefully fixes issues with the moto G2. */
|
|
dpi = 90;
|
|
*value = (float)dpi;
|
|
return true;
|
|
}
|
|
|
|
static void android_gfx_ctx_swap_buffers(void *data, video_frame_info_t *video_info)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
case GFX_CTX_OPENVG_API:
|
|
#ifdef HAVE_EGL
|
|
egl_swap_buffers(&and->egl);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
vulkan_present(&and->vk, and->vk.context.current_swapchain_index);
|
|
vulkan_acquire_next_image(&and->vk);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void android_gfx_ctx_set_swap_interval(void *data, unsigned swap_interval)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
egl_set_swap_interval(&and->egl, swap_interval);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_VULKAN_API:
|
|
#ifdef HAVE_VULKAN
|
|
if (and->swap_interval != swap_interval)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Setting swap interval: %u.\n", swap_interval);
|
|
and->swap_interval = swap_interval;
|
|
if (and->vk.swapchain)
|
|
and->vk.need_new_swapchain = true;
|
|
}
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static gfx_ctx_proc_t android_gfx_ctx_get_proc_address(const char *symbol)
|
|
{
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
return egl_get_proc_address(symbol);
|
|
#else
|
|
break;
|
|
#endif
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void android_gfx_ctx_bind_hw_render(void *data, bool enable)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
|
|
switch (android_api)
|
|
{
|
|
case GFX_CTX_OPENGL_API:
|
|
case GFX_CTX_OPENGL_ES_API:
|
|
#ifdef HAVE_EGL
|
|
egl_bind_hw_render(&and->egl, enable);
|
|
#endif
|
|
break;
|
|
case GFX_CTX_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_VULKAN
|
|
static void *android_gfx_ctx_get_context_data(void *data)
|
|
{
|
|
android_ctx_data_t *and = (android_ctx_data_t*)data;
|
|
return &and->vk.context;
|
|
}
|
|
#endif
|
|
|
|
static uint32_t android_gfx_ctx_get_flags(void *data)
|
|
{
|
|
uint32_t flags = 0;
|
|
BIT32_SET(flags, GFX_CTX_FLAGS_NONE);
|
|
|
|
return flags;
|
|
}
|
|
|
|
static void android_gfx_ctx_set_flags(void *data, uint32_t flags)
|
|
{
|
|
(void)flags;
|
|
}
|
|
|
|
const gfx_ctx_driver_t gfx_ctx_android = {
|
|
android_gfx_ctx_init,
|
|
android_gfx_ctx_destroy,
|
|
android_gfx_ctx_bind_api,
|
|
android_gfx_ctx_set_swap_interval,
|
|
android_gfx_ctx_set_video_mode,
|
|
android_gfx_ctx_get_video_size,
|
|
NULL, /* get_video_output_size */
|
|
NULL, /* get_video_output_prev */
|
|
NULL, /* get_video_output_next */
|
|
android_gfx_ctx_get_metrics,
|
|
NULL,
|
|
NULL, /* update_title */
|
|
android_gfx_ctx_check_window,
|
|
android_gfx_ctx_set_resize,
|
|
android_gfx_ctx_has_focus,
|
|
android_gfx_ctx_suppress_screensaver,
|
|
NULL, /* has_windowed */
|
|
android_gfx_ctx_swap_buffers,
|
|
android_gfx_ctx_input_driver,
|
|
android_gfx_ctx_get_proc_address,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
"android",
|
|
android_gfx_ctx_get_flags,
|
|
android_gfx_ctx_set_flags,
|
|
android_gfx_ctx_bind_hw_render,
|
|
#ifdef HAVE_VULKAN
|
|
android_gfx_ctx_get_context_data,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
NULL
|
|
};
|