Android: add command mecanism so that SDLActivity executes no SDL code,

but defers it to SDL main thread
This commit is contained in:
Sylvain
2026-02-02 14:50:26 +01:00
parent 5072b3d252
commit 8db38014fc
13 changed files with 1390 additions and 493 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -52,8 +52,8 @@ typedef enum
void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event);
bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS);
void Android_LockActivityMutex(void);
void Android_UnlockActivityMutex(void);
void Android_LockActivityState(void);
void Android_UnlockActivityState(void);
void Android_SetAllowRecreateActivity(bool enabled);
@@ -157,6 +157,10 @@ bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata,
const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
bool multiple);
// Pump RPC commands
void Android_PumpRPC(SDL_Window *window);
// Ends C function definitions when using C++
#ifdef __cplusplus
/* *INDENT-OFF* */

View File

@@ -1513,7 +1513,9 @@ static void SDL_PumpEventsInternal(bool push_sentinel)
#ifdef SDL_PLATFORM_ANDROID
// Android event processing is independent of the video subsystem
Android_PumpEvents(0);
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_Window *window = _this->windows;
Android_PumpEvents(window, 0);
#else
// Get events from the video subsystem
SDL_VideoDevice *_this = SDL_GetVideoDevice();
@@ -1746,7 +1748,10 @@ bool SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS)
}
delay = (expiration - now);
}
Android_PumpEvents(delay);
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_Window *window = _this->windows;
Android_PumpEvents(window, delay);
}
#else
SDL_VideoDevice *_this = SDL_GetVideoDevice();

View File

@@ -64,7 +64,7 @@ static int numjoysticks = 0;
* This code manipulation is done to get a sequential list of codes.
* FIXME: This is only suited for the case where we use a fixed number of buttons determined by ANDROID_MAX_NBUTTONS
*/
static int keycode_to_SDL(int keycode)
int android_keycode_to_SDL(int keycode)
{
// FIXME: If this function gets too unwieldy in the future, replace with a lookup table
int button = 0;
@@ -198,7 +198,7 @@ bool Android_OnPadDown(int device_id, int keycode)
{
Uint64 timestamp = SDL_GetTicksNS();
SDL_joylist_item *item;
int button = keycode_to_SDL(keycode);
int button = android_keycode_to_SDL(keycode);
if (button >= 0) {
SDL_LockJoysticks();
item = JoystickByDeviceId(device_id);
@@ -218,7 +218,7 @@ bool Android_OnPadUp(int device_id, int keycode)
{
Uint64 timestamp = SDL_GetTicksNS();
SDL_joylist_item *item;
int button = keycode_to_SDL(keycode);
int button = android_keycode_to_SDL(keycode);
if (button >= 0) {
SDL_LockJoysticks();
item = JoystickByDeviceId(device_id);

View File

@@ -28,6 +28,7 @@
#include "../SDL_sysjoystick.h"
extern int android_keycode_to_SDL(int keycode);
extern bool Android_OnPadDown(int device_id, int keycode);
extern bool Android_OnPadUp(int device_id, int keycode);
extern bool Android_OnJoy(int device_id, int axisnum, float value);

View File

@@ -1069,7 +1069,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
}
#ifdef SDL_PLATFORM_ANDROID
if (!Android_WaitActiveAndLockActivity()) {
if (!Android_WaitActiveAndLockActivity(window)) {
return NULL;
}
#endif
@@ -1249,7 +1249,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
SDL_renderers = renderer;
#ifdef SDL_PLATFORM_ANDROID
Android_UnlockActivityMutex();
Android_UnlockActivityState();
#endif
SDL_ClearError();
@@ -1258,7 +1258,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
error:
#ifdef SDL_PLATFORM_ANDROID
Android_UnlockActivityMutex();
Android_UnlockActivityState();
#endif
if (renderer) {

View File

@@ -35,43 +35,40 @@
#ifdef SDL_VIDEO_OPENGL_EGL
static void android_egl_context_restore(SDL_Window *window)
{
if (window) {
SDL_WindowData *data = window->internal;
SDL_GL_MakeCurrent(window, NULL);
if (!SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context)) {
// The context is no longer valid, create a new one
data->egl_context = (EGLContext)SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context);
SDL_Event event;
SDL_zero(event);
event.type = SDL_EVENT_RENDER_DEVICE_RESET;
event.render.windowID = SDL_GetWindowID(window);
SDL_PushEvent(&event);
}
data->backup_done = false;
SDL_GL_SetSwapInterval(data->swap_interval);
SDL_WindowData *data = window->internal;
SDL_GL_MakeCurrent(window, NULL);
if (!SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context)) {
// The context is no longer valid, create a new one
data->egl_context = (EGLContext)SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, (SDL_GLContext)data->egl_context);
SDL_Event event;
SDL_zero(event);
event.type = SDL_EVENT_RENDER_DEVICE_RESET;
event.render.windowID = SDL_GetWindowID(window);
SDL_PushEvent(&event);
}
data->backup_done = false;
SDL_GL_SetSwapInterval(data->swap_interval);
}
static void android_egl_context_backup(SDL_Window *window)
{
if (window) {
int interval = 0;
// Keep a copy of the EGL Context so we can try to restore it when we resume
SDL_WindowData *data = window->internal;
data->egl_context = SDL_GL_GetCurrentContext();
int interval = 0;
// Keep a copy of the EGL Context so we can try to restore it when we resume
SDL_WindowData *data = window->internal;
data->egl_context = SDL_GL_GetCurrentContext();
// Save/Restore the swap interval / vsync
if (SDL_GL_GetSwapInterval(&interval)) {
data->has_swap_interval = 1;
data->swap_interval = interval;
}
// We need to do this so the EGLSurface can be freed
SDL_GL_MakeCurrent(window, NULL);
data->backup_done = true;
SDL_Log("android_egl_context_backup ...");
// Save/Restore the swap interval / vsync
if (SDL_GL_GetSwapInterval(&interval)) {
data->has_swap_interval = 1;
data->swap_interval = interval;
}
// We need to do this so the EGLSurface can be freed
SDL_GL_MakeCurrent(window, NULL);
data->backup_done = true;
}
#endif
@@ -110,7 +107,7 @@ static void Android_ResumeAudio(void)
}
}
static void Android_OnPause(void)
static void Android_OnPause(SDL_Window *window)
{
SDL_OnApplicationWillEnterBackground();
SDL_OnApplicationDidEnterBackground();
@@ -121,10 +118,8 @@ static void Android_OnPause(void)
* was being queued.
*/
#ifdef SDL_VIDEO_OPENGL_EGL
if (Android_Window && !Android_Window->external_graphics_context) {
Android_LockActivityMutex();
android_egl_context_backup(Android_Window);
Android_UnlockActivityMutex();
if (window && !window->external_graphics_context) {
android_egl_context_backup(window);
}
#endif
@@ -136,7 +131,7 @@ static void Android_OnPause(void)
Android_Paused = true;
}
static void Android_OnResume(void)
static void Android_OnResume(SDL_Window *window)
{
Android_Paused = false;
@@ -146,10 +141,8 @@ static void Android_OnResume(void)
#ifdef SDL_VIDEO_OPENGL_EGL
// Restore the GL Context from here, as this operation is thread dependent
if (Android_Window && !Android_Window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) {
Android_LockActivityMutex();
android_egl_context_restore(Android_Window);
Android_UnlockActivityMutex();
if (window && !window->external_graphics_context && !SDL_HasEvent(SDL_EVENT_QUIT)) {
android_egl_context_restore(window);
}
#endif
@@ -176,17 +169,17 @@ static void Android_OnDestroy(void)
Android_Destroyed = true;
}
static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event)
static void Android_HandleLifecycleEvent(SDL_Window *window, SDL_AndroidLifecycleEvent event)
{
switch (event) {
case SDL_ANDROID_LIFECYCLE_WAKE:
// Nothing to do, just return
break;
case SDL_ANDROID_LIFECYCLE_PAUSE:
Android_OnPause();
Android_OnPause(window);
break;
case SDL_ANDROID_LIFECYCLE_RESUME:
Android_OnResume();
Android_OnResume(window);
break;
case SDL_ANDROID_LIFECYCLE_LOWMEMORY:
Android_OnLowMemory();
@@ -211,14 +204,17 @@ static Sint64 GetLifecycleEventTimeout(bool paused, Sint64 timeoutNS)
return timeoutNS;
}
void Android_PumpEvents(Sint64 timeoutNS)
void Android_PumpEvents(SDL_Window *window, Sint64 timeoutNS)
{
SDL_AndroidLifecycleEvent event;
bool paused = Android_Paused;
while (!Android_Destroyed &&
Android_WaitLifecycleEvent(&event, GetLifecycleEventTimeout(paused, timeoutNS))) {
Android_HandleLifecycleEvent(event);
Android_PumpRPC(window);
Android_HandleLifecycleEvent(window, event);
switch (event) {
case SDL_ANDROID_LIFECYCLE_WAKE:
@@ -238,24 +234,47 @@ void Android_PumpEvents(Sint64 timeoutNS)
}
}
bool Android_WaitActiveAndLockActivity(void)
void Android_PumpLifecycleEvents(SDL_Window *window)
{
/* Make sure we have pumped all events so that Android_Paused state is correct */
// Make sure we have pumped all events so that Android_Paused state is correct
SDL_AndroidLifecycleEvent event;
while (!Android_Destroyed && Android_WaitLifecycleEvent(&event, 0)) {
Android_HandleLifecycleEvent(event);
Android_HandleLifecycleEvent(window, event);
}
}
while (Android_Paused && !Android_Destroyed) {
Android_PumpEvents(-1);
}
bool Android_WaitActiveAndLockActivity(SDL_Window *window)
{
retry:
// Lock first.
// So no new lifecycle event comes in the meantimes from the SDLActivity,
// Hence SDLActivity won't change state.
Android_LockActivityState();
// Make sure we have pumped all events so that Android_Paused state is correct
Android_PumpLifecycleEvents(window);
if (Android_Destroyed) {
SDL_SetError("Android activity has been destroyed");
Android_UnlockActivityState();
return false;
}
Android_LockActivityMutex();
if (!Android_Paused) {
// SDLActivity is Active. return lock'ed
Android_PumpRPC(window);
return true;
} else {
// Still Paused
// Unlock and wait for the SDLActivity to send new cycle events
// or for the user to move the app to foreground.
Android_UnlockActivityState();
SDL_Delay(10);
goto retry;
}
return true;
}

View File

@@ -21,6 +21,8 @@
#include "SDL_internal.h"
extern void Android_InitEvents(void);
extern void Android_PumpEvents(Sint64 timeoutNS);
extern bool Android_WaitActiveAndLockActivity(void);
extern void Android_PumpEvents(SDL_Window *window, Sint64 timeoutNS);
extern bool Android_WaitActiveAndLockActivity(SDL_Window *window);
extern void Android_QuitEvents(void);
extern void Android_PumpLifecycleEvents(SDL_Window *window);

View File

@@ -49,13 +49,13 @@ SDL_GLContext Android_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *win
{
SDL_GLContext result;
if (!Android_WaitActiveAndLockActivity()) {
if (!Android_WaitActiveAndLockActivity(window)) {
return NULL;
}
result = SDL_EGL_CreateContext(_this, window->internal->egl_surface);
Android_UnlockActivityMutex();
Android_UnlockActivityState();
return result;
}
@@ -64,8 +64,6 @@ bool Android_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
bool result;
Android_LockActivityMutex();
/* The following two calls existed in the original Java code
* If you happen to have a device that's affected by their removal,
* please report to our bug tracker. -- Gabriel
@@ -75,8 +73,6 @@ bool Android_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
_this->egl_data->eglWaitGL();*/
result = SDL_EGL_SwapBuffers(_this, window->internal->egl_surface);
Android_UnlockActivityMutex();
return result;
}

View File

@@ -300,15 +300,15 @@ void Android_SendResize(SDL_Window *window)
}
}
void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom)
void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom)
{
Android_SafeInsetLeft = left;
Android_SafeInsetRight = right;
Android_SafeInsetTop = top;
Android_SafeInsetBottom = bottom;
if (Android_Window) {
SDL_SetWindowSafeAreaInsets(Android_Window, left, right, top, bottom);
if (window) {
SDL_SetWindowSafeAreaInsets(window, left, right, top, bottom);
}
}

View File

@@ -30,7 +30,7 @@ extern void Android_SetScreenResolution(int surfaceWidth, int surfaceHeight, int
extern void Android_SetFormat(int format_wanted, int format_got);
extern void Android_SetOrientation(SDL_DisplayOrientation orientation);
extern void Android_SendResize(SDL_Window *window);
extern void Android_SetWindowSafeAreaInsets(int left, int right, int top, int bottom);
extern void Android_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom);
extern void Android_SetDarkMode(bool enabled);
// Private display data

View File

@@ -34,18 +34,32 @@
// Currently only one window
SDL_Window *Android_Window = NULL;
static bool window_created = false;
bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
{
SDL_WindowData *data;
bool result = true;
if (!Android_WaitActiveAndLockActivity()) {
// Android_WaitActiveAndLockActivity() process some RPC:
// "onSurfaceCreated" that needs "window->internal"
// and also "nativeSetScreenResolution"
data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
if (!data) {
return false;
}
if (Android_Window) {
#ifdef SDL_VIDEO_OPENGL_EGL
data->egl_surface = EGL_NO_SURFACE;
#endif
window->internal = data;
if (!Android_WaitActiveAndLockActivity(window)) {
return false;
}
if (window_created) {
result = SDL_SetError("Android only supports one window");
goto endfunction;
}
@@ -63,44 +77,23 @@ bool Android_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
SDL_SetMouseFocus(window);
SDL_SetKeyboardFocus(window);
data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
if (!data) {
if (!Android_nativeSurfaceCreated(window)) {
result = false;
goto endfunction;
}
data->native_window = Android_JNI_GetNativeWindow();
if (!data->native_window) {
SDL_free(data);
result = SDL_SetError("Could not fetch native window");
if (!Android_nativeSurfaceChanged(window)) {
result = false;
goto endfunction;
}
SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
/* Do not create EGLSurface for Vulkan window since it will then make the window
incompatible with vkCreateAndroidSurfaceKHR */
#ifdef SDL_VIDEO_OPENGL_EGL
if (window->flags & SDL_WINDOW_OPENGL) {
data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window);
if (data->egl_surface == EGL_NO_SURFACE) {
ANativeWindow_release(data->native_window);
SDL_free(data);
result = false;
goto endfunction;
}
}
SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
#endif
SDL_SetWindowSafeAreaInsets(window, Android_SafeInsetLeft, Android_SafeInsetRight, Android_SafeInsetTop, Android_SafeInsetBottom);
window->internal = data;
Android_Window = window;
window_created = true;
endfunction:
Android_UnlockActivityMutex();
Android_UnlockActivityState();
return result;
}
@@ -112,54 +105,48 @@ void Android_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
SDL_FullscreenResult Android_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
{
Android_LockActivityMutex();
SDL_WindowData *data;
int old_w, old_h, new_w, new_h;
if (window == Android_Window) {
SDL_WindowData *data;
int old_w, old_h, new_w, new_h;
// If the window is being destroyed don't change visible state
if (!window->is_destroying) {
Android_JNI_SetWindowStyle(fullscreen);
}
// If the window is being destroyed don't change visible state
if (!window->is_destroying) {
Android_JNI_SetWindowStyle(fullscreen);
/* Ensure our size matches reality after we've executed the window style change.
*
* It is possible that we've set width and height to the full-size display, but on
* Samsung DeX or Chromebooks or other windowed Android environments, our window may
* still not be the full display size.
*/
if (!SDL_IsDeXMode() && !SDL_IsChromebook()) {
goto endfunction;
}
data = window->internal;
if (!data || !data->native_window) {
if (data && !data->native_window) {
SDL_SetError("Missing native window");
}
goto endfunction;
}
/* Ensure our size matches reality after we've executed the window style change.
*
* It is possible that we've set width and height to the full-size display, but on
* Samsung DeX or Chromebooks or other windowed Android environments, our window may
* still not be the full display size.
*/
if (!SDL_IsDeXMode() && !SDL_IsChromebook()) {
goto endfunction;
}
old_w = window->w;
old_h = window->h;
data = window->internal;
if (!data || !data->native_window) {
if (data && !data->native_window) {
SDL_SetError("Missing native window");
}
goto endfunction;
}
new_w = ANativeWindow_getWidth(data->native_window);
new_h = ANativeWindow_getHeight(data->native_window);
old_w = window->w;
old_h = window->h;
if (new_w < 0 || new_h < 0) {
SDL_SetError("ANativeWindow_getWidth/Height() fails");
}
new_w = ANativeWindow_getWidth(data->native_window);
new_h = ANativeWindow_getHeight(data->native_window);
if (new_w < 0 || new_h < 0) {
SDL_SetError("ANativeWindow_getWidth/Height() fails");
}
if (old_w != new_w || old_h != new_h) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h);
}
if (old_w != new_w || old_h != new_h) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, new_w, new_h);
}
endfunction:
Android_UnlockActivityMutex();
return SDL_FULLSCREEN_SUCCEEDED;
}
@@ -176,29 +163,86 @@ void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool
void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
Android_LockActivityMutex();
window_created = false;
if (window == Android_Window) {
Android_Window = NULL;
if (window->internal) {
SDL_WindowData *data = window->internal;
if (window->internal) {
Android_nativeSurfaceCreated(window);
SDL_free(window->internal);
window->internal = NULL;
}
}
#ifdef SDL_VIDEO_OPENGL_EGL
if (data->egl_surface != EGL_NO_SURFACE) {
SDL_EGL_DestroySurface(_this, data->egl_surface);
}
#endif
if (data->native_window) {
ANativeWindow_release(data->native_window);
}
SDL_free(window->internal);
window->internal = NULL;
// Those functions called from RPC onNativeSurface{Created,Changed,Destroyed}()
// and SDL_{Create,Destroy}Window();
bool Android_nativeSurfaceCreated(SDL_Window *window)
{
if (window) {
SDL_WindowData *data = window->internal;
data->native_window = Android_JNI_GetNativeWindow();
SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
if (data->native_window == NULL) {
SDL_SetError("Could not fetch native window");
return false;
}
return true;
}
Android_UnlockActivityMutex();
return false;
}
bool Android_nativeSurfaceChanged(SDL_Window *window)
{
if (window) {
#ifdef SDL_VIDEO_OPENGL_EGL
/* Do not create EGLSurface for Vulkan window since it will then make the window
incompatible with vkCreateAndroidSurfaceKHR */
if (window->flags & SDL_WINDOW_OPENGL) {
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_WindowData *data = window->internal;
// If the surface has been previously destroyed by onNativeSurfaceDestroyed
// or if it is the first time, recreate it.
if (data->egl_surface == EGL_NO_SURFACE) {
data->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)data->native_window);
SDL_SetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
}
if (data->egl_surface == EGL_NO_SURFACE) {
if (data->native_window) {
ANativeWindow_release(data->native_window);
data->native_window = 0;
}
return false;
}
}
#endif
return true;
}
return false;
}
void Android_nativeSurfaceDestroyed(SDL_Window *window)
{
if (window) {
SDL_WindowData *data = window->internal;
#ifdef SDL_VIDEO_OPENGL_EGL
if (data->egl_surface != EGL_NO_SURFACE) {
SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface);
data->egl_surface = EGL_NO_SURFACE;
}
#endif
if (data->native_window) {
ANativeWindow_release(data->native_window);
data->native_window = NULL;
}
}
}
#endif // SDL_VIDEO_DRIVER_ANDROID

View File

@@ -33,7 +33,6 @@ extern void Android_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
extern void Android_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable);
extern void Android_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
extern SDL_Window *Android_Window;
struct SDL_WindowData
{
@@ -48,4 +47,11 @@ struct SDL_WindowData
};
bool Android_nativeSurfaceCreated(SDL_Window *window);
bool Android_nativeSurfaceChanged(SDL_Window *window);
void Android_nativeSurfaceDestroyed(SDL_Window *window);
#endif // SDL_androidwindow_h_