Skip to content

Commit 068edb4

Browse files
committed
Merge branch 'master' into sokol-framebuffer
2 parents 116fc45 + 5cc3e91 commit 068edb4

4 files changed

Lines changed: 76 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
- [Tiny Emulators](https://floooh.github.io/tiny8bit/)
1919
- [Pacman.c](https://floooh.github.io/pacman.c/pacman.html)
2020

21+
### 04-May-2026
22+
23+
- sokol_app.h android: The sokol-app Android backend now uses the Choreographer
24+
API (if available) for frame pacing and for the frame duration (which has
25+
much less jitter, similar to CADisplayLink on macOS/iOS).
26+
27+
Many thanks to @learnopengles for the PR!
28+
29+
Issue: https://github.com/floooh/sokol/issues/1502
30+
PR: https://github.com/floooh/sokol/pull/1503
31+
2132
### 01-May-2026
2233

2334
- sokol_app.h macos: Move `activationPolicy` in front of window

bindgen/gen_zig.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,9 @@ def gen_struct(decl, prefix):
342342
sys.exit(f"ERROR gen_struct is_1d_array_type: {array_type}")
343343
t0 = f"[{array_sizes[0]}]{zig_type}"
344344
t1 = f"[_]{zig_type}"
345-
l(f" {field_name}: {t0} = {t1}{{{def_val}}} ** {array_sizes[0]},")
345+
l(f" {field_name}: {t0} = @splat({def_val}),")
346346
elif util.is_const_void_ptr(array_type):
347-
l(f" {field_name}: [{array_sizes[0]}]?*const anyopaque = [_]?*const anyopaque{{null}} ** {array_sizes[0]},")
347+
l(f" {field_name}: [{array_sizes[0]}]?*const anyopaque = @splat(null),")
348348
else:
349349
sys.exit(f"ERROR gen_struct: array {field_name}: {field_type} => {array_type} [{array_sizes[0]}]")
350350
elif util.is_2d_array_type(field_type):
@@ -359,7 +359,7 @@ def gen_struct(decl, prefix):
359359
else:
360360
sys.exit(f"ERROR gen_struct is_2d_array_type: {array_type}")
361361
t0 = f"[{array_sizes[0]}][{array_sizes[1]}]{zig_type}"
362-
l(f" {field_name}: {t0} = [_][{array_sizes[1]}]{zig_type}{{[_]{zig_type}{{{def_val}}} ** {array_sizes[1]}}} ** {array_sizes[0]},")
362+
l(f" {field_name}: {t0} = @splat(@splat({def_val})),")
363363
else:
364364
sys.exit(f"ERROR gen_struct: {field_name}: {field_type};")
365365
l("};")

sokol_app.h

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,8 @@ typedef struct sapp_allocator {
18001800
_SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \
18011801
_SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \
18021802
_SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity successfully created") \
1803+
_SAPP_LOGITEM_XMACRO(ANDROID_CHOREOGRAPHER_ENABLED, "Choreographer frame loop enabled") \
1804+
_SAPP_LOGITEM_XMACRO(ANDROID_CHOREOGRAPHER_UNAVAILABLE, "Choreographer unavailable, using poll loop") \
18031805
_SAPP_LOGITEM_XMACRO(WGPU_DEVICE_LOST, "wgpu: device lost") \
18041806
_SAPP_LOGITEM_XMACRO(WGPU_DEVICE_LOG, "wgpu: device log") \
18051807
_SAPP_LOGITEM_XMACRO(WGPU_DEVICE_UNCAPTURED_ERROR, "wgpu: uncaptured error") \
@@ -2208,9 +2210,9 @@ SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index);
22082210
SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc);
22092211

22102212
/* get runtime environment information */
2211-
sapp_environment sapp_get_environment(void);
2213+
SOKOL_APP_API_DECL sapp_environment sapp_get_environment(void);
22122214
/* get current frame's swapchain information (call once per frame!) */
2213-
sapp_swapchain sapp_get_swapchain(void);
2215+
SOKOL_APP_API_DECL sapp_swapchain sapp_get_swapchain(void);
22142216

22152217
/* EGL: get EGLDisplay object */
22162218
SOKOL_APP_API_DECL const void* sapp_egl_get_display(void);
@@ -2515,6 +2517,9 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
25152517
#include <time.h>
25162518
#include <android/native_activity.h>
25172519
#include <android/looper.h>
2520+
#if __ANDROID_API__ >= 29
2521+
#include <android/choreographer.h>
2522+
#endif
25182523
#include <EGL/egl.h>
25192524
#include <GLES3/gl3.h>
25202525
#elif defined(_SAPP_LINUX)
@@ -3047,6 +3052,10 @@ typedef struct {
30473052
EGLDisplay display;
30483053
EGLContext context;
30493054
EGLSurface surface;
3055+
#if __ANDROID_API__ >= 29
3056+
AChoreographer* choreographer;
3057+
bool frame_callback_in_flight;
3058+
#endif
30503059
} _sapp_android_t;
30513060

30523061
#endif // _SAPP_ANDROID
@@ -10191,7 +10200,7 @@ _SOKOL_PRIVATE bool _sapp_win32_make_custom_mouse_cursor(sapp_mouse_cursor curso
1019110200
return win32_cursor != 0;
1019210201
}
1019310202

10194-
SOKOL_API_IMPL void _sapp_win32_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) {
10203+
_SOKOL_PRIVATE void _sapp_win32_destroy_custom_mouse_cursor(sapp_mouse_cursor cursor) {
1019510204
SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
1019610205
HCURSOR win32_cursor = _sapp.win32.custom_cursors[cursor];
1019710206
SOKOL_ASSERT(win32_cursor);
@@ -10432,11 +10441,11 @@ _SOKOL_PRIVATE void _sapp_android_shutdown(void) {
1043210441
ANativeActivity_finish(_sapp.android.activity);
1043310442
}
1043410443

10435-
_SOKOL_PRIVATE void _sapp_android_frame(void) {
10444+
_SOKOL_PRIVATE void _sapp_android_frame(double external_now) {
1043610445
SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY);
1043710446
SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT);
1043810447
SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE);
10439-
_sapp_timing_update(&_sapp.timing, 0.0);
10448+
_sapp_timing_update(&_sapp.timing, external_now);
1044010449
_sapp_android_update_dimensions(_sapp.android.current.window, false);
1044110450
_sapp_frame();
1044210451
eglSwapBuffers(_sapp.android.display, _sapp.android.surface);
@@ -10630,6 +10639,23 @@ _SOKOL_PRIVATE bool _sapp_android_should_update(void) {
1063010639
return is_in_front && has_surface;
1063110640
}
1063210641

10642+
#if __ANDROID_API__ >= 29
10643+
_SOKOL_PRIVATE void _sapp_android_frame_callback(int64_t frame_time_nanos, void* data) {
10644+
_SOKOL_UNUSED(data);
10645+
_sapp.android.frame_callback_in_flight = false;
10646+
if (_sapp.android.is_thread_stopping) {
10647+
return;
10648+
}
10649+
if (_sapp_android_should_update()) {
10650+
// Post the next frame callback. We do this here rather than later so the runnable can be
10651+
// queued early in the looper.
10652+
AChoreographer_postFrameCallback64(_sapp.android.choreographer, _sapp_android_frame_callback, NULL);
10653+
_sapp.android.frame_callback_in_flight = true;
10654+
_sapp_android_frame((double)frame_time_nanos / 1.0e9);
10655+
}
10656+
}
10657+
#endif
10658+
1063310659
_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) {
1063410660
SOKOL_ASSERT(_sapp.valid);
1063510661
/* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */
@@ -10652,6 +10678,17 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) {
1065210678
_sapp_android_main_cb,
1065310679
NULL); /* data */
1065410680

10681+
#if __ANDROID_API__ >= 29
10682+
_sapp.android.choreographer = AChoreographer_getInstance();
10683+
if (_sapp.android.choreographer != NULL) {
10684+
_SAPP_INFO(ANDROID_CHOREOGRAPHER_ENABLED);
10685+
} else {
10686+
_SAPP_INFO(ANDROID_CHOREOGRAPHER_UNAVAILABLE);
10687+
}
10688+
#else
10689+
_SAPP_INFO(ANDROID_CHOREOGRAPHER_UNAVAILABLE);
10690+
#endif
10691+
1065510692
/* signal start to main thread */
1065610693
pthread_mutex_lock(&_sapp.android.pt.mutex);
1065710694
_sapp.android.is_thread_started = true;
@@ -10660,9 +10697,24 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) {
1066010697

1066110698
/* main loop */
1066210699
while (!_sapp.android.is_thread_stopping) {
10663-
/* sokol frame */
10700+
#if __ANDROID_API__ >= 29
10701+
if (_sapp.android.choreographer != NULL) {
10702+
// Posts _sapp_android_frame_callback with the choreographer to start our frame
10703+
// loop (for example, on first run or when resuming). When we have a choreographer,
10704+
// we'll get frame callbacks via _sapp_android_frame_callback.
10705+
if (!_sapp.android.frame_callback_in_flight && _sapp_android_should_update()) {
10706+
AChoreographer_postFrameCallback64(_sapp.android.choreographer, _sapp_android_frame_callback, NULL);
10707+
_sapp.android.frame_callback_in_flight = true;
10708+
}
10709+
// Blocks until the next event. We don't need a while loop here because we're
10710+
// already being driven by the outer while loop.
10711+
ALooper_pollOnce(-1, NULL, NULL, NULL);
10712+
continue;
10713+
}
10714+
#endif
10715+
// sokol frame -- fallback if not updating frames from choreographer callbacks
1066410716
if (_sapp_android_should_update()) {
10665-
_sapp_android_frame();
10717+
_sapp_android_frame(0.0);
1066610718
}
1066710719

1066810720
/* process all events (or stop early if app is requested to quit) */
@@ -14059,7 +14111,7 @@ SOKOL_API_IMPL sapp_mouse_cursor sapp_bind_mouse_cursor_image(sapp_mouse_cursor
1405914111
return cursor; // returning the passed-in cursor puerly for convenience, in case you want to asign the value to a variable.
1406014112
}
1406114113

14062-
SOKOL_APP_API_DECL void sapp_unbind_mouse_cursor_image(sapp_mouse_cursor cursor) {
14114+
SOKOL_API_IMPL void sapp_unbind_mouse_cursor_image(sapp_mouse_cursor cursor) {
1406314115
SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM));
1406414116
if (_sapp.custom_cursor_bound[(int)cursor]) {
1406514117
// if this is the active cursor, first restore it to its default image,

sokol_gfx.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3297,9 +3297,9 @@ typedef struct sg_buffer_desc {
32973297
the image content cannot be updated from the CPU side
32983298
(but may be updated by the GPU in a render- or compute-pass)
32993299
.dynamic_update (default: false)
3300-
the image content is updated infrequently by the CPU
3300+
the image content is updated infrequently by the CPU via sg_update_image()
33013301
.stream_update (default: false)
3302-
the image content is updated each frame by the CPU via
3302+
the image content is updated each frame by the CPU via sg_update_image()
33033303

33043304
Note that creating a texture view from the image to be used for
33053305
texture-sampling in vertex-, fragment- or compute-shaders

0 commit comments

Comments
 (0)