From b98b291dd44fddb1efcd0673facd0647f60fe4b5 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Wed, 21 Oct 2026 09:44:36 -0700 Subject: [PATCH] Publish drawable dimensions from SDL pixel-size events --- docs/internal/FAITHFUL_TIMING_PLAN.md | 2 + runtime/src/gpu_gl_renderer.c | 113 ++++++++++++++++++++------ runtime/tests/test_gl_render_thread.c | 22 +++++ 3 files changed, 114 insertions(+), 23 deletions(-) diff --git a/docs/internal/FAITHFUL_TIMING_PLAN.md b/docs/internal/FAITHFUL_TIMING_PLAN.md index bb22382d..9cfcbb31 100644 --- a/docs/internal/FAITHFUL_TIMING_PLAN.md +++ b/docs/internal/FAITHFUL_TIMING_PLAN.md @@ -213,6 +213,8 @@ on a fixed region -> next. ## 5. Status / Log (update every session) +- **2026-10-21 (BRIEF-4 drawable cache):** Reuse `8ed11cc7`/`ad173490` drawable caching and SDL3 pixel-size event publication. Seven query/event hardware controls match all output hashes; resize and zero-per-frame-query checks pass on Windows and Mac. A loaded repeat before this fix has a 51.795ms host-event span; final performance validation remains open. + - **2026-10-21 (BRIEF-4 cap edges):** Apply the display ceiling after supersampling and resolve the physical-monitor supersample policy after creating the window, including fixed presets. Saved large factors cannot exceed the Match display ceiling; legacy unset scales stay intact. Resolution tests pass on Windows/Mac; all 21 R4 production-build CTests pass. Final regeneration and receipt verification pending. - **2026-10-21 (BRIEF-4 storage reuse):** Desktop GL retains ceiling storage and rescales in place, preserving cleared depth/stencil and unwritten wide regions. Apple keeps level-sized allocations by default. Windows old/new output controls and forced-reuse Mac fixtures pass. With corrected presenter accounting, R4 60s fullscreen quiet/loaded runs have zero completion gaps >25ms, p99 busy 44.6/48.8%, 17x..18x scales. R4 final pin/default checks remain in progress. diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index 1886a98f..ca84b85a 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -395,6 +395,60 @@ static int load_modern_gl(void) { /* ---- state ------------------------------------------------------------- */ static SDL_Window *s_win = NULL; +static uint64_t s_window_owner; +static _Atomic uint64_t s_drawable_extent; +static uint64_t s_drawable_queries; +#if defined(PSX_SDL3) +static _Atomic uint32_t s_drawable_window_id; +static int s_drawable_watch; +static bool SDLCALL drawable_event_watch(void *unused, SDL_Event *event) { + (void)unused; + if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED && + event->window.windowID == atomic_load_explicit(&s_drawable_window_id, memory_order_relaxed) && + event->window.data1 >= 0 && event->window.data2 >= 0) + atomic_store_explicit(&s_drawable_extent, + ((uint64_t)(uint32_t)event->window.data1 << 32) | (uint32_t)event->window.data2, + memory_order_release); + return true; +} +#endif +static int drawable_events_enabled(void) { +#if defined(PSX_SDL3) + if (!s_drawable_watch) return 0; + static int enabled = -1; + if (enabled < 0) { + const char *e = getenv("PSX_GL_DRAWABLE_EVENTS"); + enabled = !(e && e[0] == '0'); + } + return enabled; +#else + return 0; /* SDL2 resize events are logical, not drawable-pixel extents. */ +#endif +} +static uint64_t window_thread_id(void) { +#if defined(PSX_SDL3) + return (uint64_t)SDL_GetCurrentThreadID(); +#else + return (uint64_t)SDL_ThreadID(); +#endif +} +/* Query only at context creation (or in the legacy control). SDL3 pixel-size + * events publish one consistent pair without taking SDL's window-properties + * lock, which the swapping thread can hold. No worker queries the window. */ +static void gl_drawable_refresh(void) { + if (!s_win || window_thread_id() != s_window_owner) return; + int w = 0, h = 0; + SDL_GL_GetDrawableSize(s_win, &w, &h); + s_drawable_queries++; + atomic_store_explicit(&s_drawable_extent, + ((uint64_t)(uint32_t)w << 32) | (uint32_t)h, memory_order_release); +} +static void gl_drawable_size(int *w, int *h) { + if (!drawable_events_enabled()) gl_drawable_refresh(); + uint64_t extent = atomic_load_explicit(&s_drawable_extent, memory_order_acquire); + if (w) *w = (int)(extent >> 32); + if (h) *h = (int)(uint32_t)extent; +} static SDL_GLContext s_ctx = NULL; static uint16_t *s_vram = NULL; /* CPU VRAM array (gpu.c's storage) */ static int s_swap_interval = 1; /* SDL_GL swap interval (vsync mode) */ @@ -1295,7 +1349,7 @@ static void hold_capture_drawable(void) { int ww = 0, wh = 0; if (!s_ctx || !s_win) return; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww < 1 || wh < 1) return; hold_ensure_tex(ww, wh); @@ -1326,7 +1380,7 @@ static void hiw_capture_size(int sw, int sh, int force_4_3, int *cw, int *ch) { int ww = 0, wh = 0, lx, ly, lw, lh; *cw = sw; *ch = sh; if (!s_hiw || !s_win) return; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww < 1 || wh < 1) return; if (force_4_3) letterbox_rect_aspect(ww, wh, 4, 3, &lx, &ly, &lw, &lh); else letterbox_rect(ww, wh, &lx, &ly, &lw, &lh); @@ -1406,7 +1460,7 @@ static void postfx_present(int fresh, int with_aa, int lx, int ly, int lw, int l static void pres_record(int path, int dx, int dy, int w, int h, int lx, int ly, int lw, int lh) { int drawable_w = 0, drawable_h = 0; - SDL_GL_GetDrawableSize(s_win, &drawable_w, &drawable_h); + gl_drawable_size( &drawable_w, &drawable_h); psx_video_telemetry_compose(PSX_VIDEO_OPENGL, s_out_scale, w, h, drawable_w, drawable_h, lw, lh); /* The composed game image, before hold-last capture and the OSD: the @@ -1538,7 +1592,7 @@ static int s_post_copy_w = 0, s_post_copy_h = 0; /* Clip [lx,ly,lw,lh] to the drawable; 0 when less than min px remain. */ static int post_clip_rect(int *lx, int *ly, int *lw, int *lh, int min) { int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (*lx < 0) { *lw += *lx; *lx = 0; } if (*ly < 0) { *lh += *ly; *ly = 0; } if (*lx + *lw > ww) *lw = ww - *lx; @@ -6337,7 +6391,7 @@ int gl_renderer_scale_info(GlScaleInfo *out) { int wmax = s_gl_max_dim / VRAM_H; out->max_scale = wmax > GL_MAX_INTERNAL_SCALE ? GL_MAX_INTERNAL_SCALE : wmax; } - if (s_ctx && s_win) SDL_GL_GetDrawableSize(s_win, &out->drawable_w, &out->drawable_h); + if (s_ctx && s_win) gl_drawable_size( &out->drawable_w, &out->drawable_h); return s_raster_ok; } @@ -6446,6 +6500,13 @@ int gl_renderer_select_texture_bank_live_clut(uint16_t id) { int gl_renderer_init_context(SDL_Window *win) { s_win = win; + s_window_owner = window_thread_id(); +#if defined(PSX_SDL3) + if (s_drawable_watch) SDL_RemoveEventWatch(drawable_event_watch, NULL); + atomic_store(&s_drawable_window_id, SDL_GetWindowID(win)); + s_drawable_watch = SDL_AddEventWatch(drawable_event_watch, NULL) ? 1 : 0; +#endif + gl_drawable_refresh(); s_present_w = 0; s_present_h = 0; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -6565,6 +6626,11 @@ static void pass_resources_release(void); static void present_image_ring_gl_forget(void); void gl_renderer_shutdown(void) { gl_renderer_render_thread_stop(); /* drains; the context comes back to this thread */ +#if defined(PSX_SDL3) + if (s_drawable_watch) SDL_RemoveEventWatch(drawable_event_watch, NULL); + s_drawable_watch = 0; + atomic_store(&s_drawable_window_id, 0); +#endif /* After the worker has stopped (it presents, and captures, on its own * thread) and before the context dies: forget the ring's GL names. */ present_image_ring_gl_forget(); @@ -6623,7 +6689,7 @@ void gl_renderer_present(const uint32_t *pixels, int src_w, int src_h, int linea GL_RT_SYNC("present"); if (!s_ctx) return; interp_reset_history(); - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); glDisable(GL_SCISSOR_TEST); glViewport(0, 0, ww, wh); glClearColor(0.f,0.f,0.f,1.f); glClear(GL_COLOR_BUFFER_BIT); @@ -6725,7 +6791,7 @@ void gl_renderer_present_blank(void) { GL_RT_SYNC("present_blank"); if (!s_ctx) return; interp_reset_history(); - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); glDisable(GL_SCISSOR_TEST); glViewport(0, 0, ww, wh); glClearColor(0.f,0.f,0.f,1.f); glClear(GL_COLOR_BUFFER_BIT); pres_record(GL_PRES_BLANK, 0, 0, 0, 0, 0, 0, ww, wh); @@ -7865,7 +7931,7 @@ static int interp_present_pair(GLuint a, GLuint b, float t, int blend_mode) { if (!s_ctx || !s_interp_enabled || interp_suspended_now() || s_interp_valid < 1) return 0; uint64_t present_t0 = SDL_GetPerformanceCounter(); - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); int lx, ly, lw, lh; if (s_interp_force_4_3) letterbox_rect_aspect(ww, wh, 4, 3, &lx, &ly, &lw, &lh); @@ -9189,7 +9255,7 @@ static void openxr_present_native(void) { int ww=0,wh=0,*r=s_native_surface_rect; if(!s_native_surface_enabled || !s_native_surface_pending || s_pass_active)return; s_native_surface_pending=0; - SDL_GL_GetDrawableSize(s_win,&ww,&wh); + gl_drawable_size(&ww,&wh); if(r[0]<0 || r[1]<0 || r[2]<1 || r[3]<1 || r[0]+r[2]>ww || r[1]+r[3]>wh)return; /* No replay or retained pair: this exact native present is the source. * Direct lifecycle permits native 24-bit videos as well as 15-bit UI. */ @@ -9217,7 +9283,7 @@ static int stereo_present(int w, int h) { if (!s_stereo_mode || !s_stereo_valid || s_pass_active || p->w != w || p->h != h || p->tw[0] != w * s_hr_scale || gl_renderer_stereo_unavailable() != PSX_MOD_RENDER_PASS_READY) return 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); gl_perf_present_enter(); /* Desktop spectator layout while XR runs: default single left eye filled to * the window, PSX_VR_DESKTOP_MIRROR selects 0 legacy / 1 left / 2 SBS / 3 right. */ @@ -9554,7 +9620,7 @@ static void present_image_ring_capture_gl(void) { if (s_pir_disabled || !p_glMapBuffer || !p_glUnmapBuffer) return; if (!present_image_ring_accepting()) return; int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); const int tw = present_image_ring_thumb_w(ww, wh); const int th = PRESENT_IMAGE_RING_H; if (tw <= 0) return; @@ -9683,7 +9749,7 @@ static GLuint pt_target_fbo(void) { const int i = pt_current(); if (i < 0) return 0; int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww < 1) ww = 1; if (wh < 1) wh = 1; if (s_pts_fbo[i] && s_pts_w[i] == ww && s_pts_h[i] == wh) return s_pts_fbo[i]; @@ -9759,7 +9825,7 @@ static void *pt_cb_present(void *user, int slot, void *ready) { } const int sw = s_pts_w[slot], sh = s_pts_h[slot]; int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); p_glBindFramebuffer_raw(PSXGL_READ_FRAMEBUFFER, s_ptp_fbo[slot]); p_glBindFramebuffer_raw(PSXGL_DRAW_FRAMEBUFFER, 0); glDisable(GL_SCISSOR_TEST); @@ -9895,7 +9961,7 @@ static void gl_swap_with_osd(void) { #endif if (s_present_prog && s_ctx) { int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww > 0 && wh > 0) { const uint32_t *px = NULL; int ow = 0, oh = 0; @@ -9968,7 +10034,7 @@ static void gl_swap_with_osd(void) { } if (burst && b_i > 0) { int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww == b_w && wh == b_h && (b_px[b_i] = (uint8_t *)malloc((size_t)ww * wh * 3))) { glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, ww, wh, GL_RGB, GL_UNSIGNED_BYTE, b_px[b_i]); @@ -9999,7 +10065,7 @@ static void gl_swap_with_osd(void) { present_shot_done(ok); } } else if (burst && present_shot_take(shot_path, (int)sizeof(shot_path))) { - SDL_GL_GetDrawableSize(s_win, &b_w, &b_h); + gl_drawable_size( &b_w, &b_h); snprintf(b_path, sizeof b_path, "%s", shot_path); if (b_w > 0 && b_h > 0 && (b_px[0] = (uint8_t *)malloc((size_t)b_w * b_h * 3))) { glPixelStorei(GL_PACK_ALIGNMENT, 1); @@ -10014,7 +10080,7 @@ static void gl_swap_with_osd(void) { present_shot_take(shot_path, (int)sizeof(shot_path))) { int ww = 0, wh = 0; int wrote = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww > 0 && wh > 0) { uint8_t *rows = (uint8_t *)malloc((size_t)ww * wh * 3); uint8_t *flip = rows ? (uint8_t *)malloc((size_t)ww * wh * 3) : NULL; @@ -10210,7 +10276,7 @@ int gl_renderer_present_hold_last(void) { int lx, ly, lw, lh; if (!s_ctx || !s_win || s_hold_kind == HOLD_NONE || !s_hold_tex) return 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww < 1 || wh < 1) return 0; glDisable(GL_SCISSOR_TEST); @@ -10311,7 +10377,7 @@ static void present_vram_impl(int disp_x, int disp_y, int w, int h, int linear, return; } gl_perf_present_enter(); /* per-frame backdrop-phase reset + dbg snapshot live in here */ - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); int lx, ly, lw, lh; if (force_4_3) letterbox_rect_aspect(ww, wh, 4, 3, &lx, &ly, &lw, &lh); @@ -10515,7 +10581,7 @@ static int present_wide_fbo_impl(int disp_x, int disp_y, int disp_h, int linear) return 1; } gl_perf_present_enter(); - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); int lx, ly, lw, lh; letterbox_rect(ww, wh, &lx, &ly, &lw, &lh); wide_blit_center(fbo, disp_x, disp_y, disp_h); /* fast-path: authoritative centre */ @@ -12485,7 +12551,7 @@ static void fg_replay(const FgList *a, const FgList *b, double t, GLuint gen_wid * interpolation capture, no present bookkeeping), then swapped. */ static void fg_compose(const FgList *b, GLuint wide_fbo, GLuint wide_tex, int linear, int force_4_3) { - int ww = 0, wh = 0; SDL_GL_GetDrawableSize(s_win, &ww, &wh); + int ww = 0, wh = 0; gl_drawable_size( &ww, &wh); int lx, ly, lw, lh; if (b->wide) { letterbox_rect(ww, wh, &lx, &ly, &lw, &lh); @@ -13300,7 +13366,7 @@ static void fg_generate_timed(double t) { /* Keep the composed back buffer (a real frame) for later. 1 when kept. */ static int fg_keep_real(void) { int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); if (ww <= 0 || wh <= 0) return 0; if (!s_fg_real_fbo || s_fg_real_w != ww || s_fg_real_h != wh) { if (s_fg_real_fbo) { p_glDeleteFramebuffers(1, &s_fg_real_fbo); glDeleteTextures(1, &s_fg_real_tex); } @@ -13328,7 +13394,7 @@ static void fg_present_real(void) { if (!s_fg_real_kept) return; /* nothing changed: the present was skipped */ s_fg_real_kept = 0; int ww = 0, wh = 0; - SDL_GL_GetDrawableSize(s_win, &ww, &wh); + gl_drawable_size( &ww, &wh); p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, s_fg_real_fbo); p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0); glDisable(GL_SCISSOR_TEST); @@ -14261,6 +14327,7 @@ int gl_renderer_render_thread_active(void) { return s_rth_on; } * to the render thread when the frame may run asynchronously, take it when it * may not, then close the frame (the in-flight bound applies here). */ void gl_renderer_render_thread_frame_boundary(void) { + if (!drawable_events_enabled()) gl_drawable_refresh(); if (!s_rth_on) return; fg_note_guest_frame(); if (rt_held()) { diff --git a/runtime/tests/test_gl_render_thread.c b/runtime/tests/test_gl_render_thread.c index 7cba4dd3..c5028002 100644 --- a/runtime/tests/test_gl_render_thread.c +++ b/runtime/tests/test_gl_render_thread.c @@ -110,6 +110,11 @@ static void check(int ok, const char *label) { checks++; if (!ok) { fprintf(stderr, "FAIL %s\n", label); failures++; } } +static int drawable_worker(void *data) { + int *size = data; + gl_drawable_size(&size[0], &size[1]); + return 0; +} static uint64_t fnv(const void *pp, size_t n, uint64_t h) { const uint8_t *p = (const uint8_t *)pp; for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 0x100000001b3ull; } @@ -319,6 +324,7 @@ int main(int argc, char **argv) { if (timing_idle && threaded) gl_renderer_render_thread_measure(1); static const int levels[] = { 2, 1, 3, 1, 4, 2 }; int nsteps = 0, level = scale; + const uint64_t drawable_queries_before = s_drawable_queries; for (int f = 0; f < frames; f++) { frame(f, (f & 1) ? 512 : 0); if (dynres && f % 7 == 6) { /* includes frames held at a sync point */ @@ -402,6 +408,22 @@ int main(int argc, char **argv) { check(st.acquires >= 2 && st.acquires <= (uint64_t)frames + 4, "acquires bounded"); check(st.bytes > ((uint64_t)32 << 20), "ring wrapped (more than its size recorded)"); } + if (drawable_events_enabled()) + check(s_drawable_queries == drawable_queries_before, "no per-frame drawable queries"); + /* Pixel-size events publish resizes without querying the window on a + * worker or taking its property lock on every frame boundary. */ + for (int k = 0; k < 2; ++k) { + SDL_SetWindowSize(win, 400 + k * 80, 300 + k * 60); + SDL_PumpEvents(); + gl_renderer_render_thread_frame_boundary(); + int expected[2] = {0}, seen[2] = {0}; + SDL_GL_GetDrawableSize(win, &expected[0], &expected[1]); + SDL_Thread *worker = SDL_CreateThread(drawable_worker, "drawable-test", seen); + check(worker != NULL, "drawable reader thread created"); + if (worker) SDL_WaitThread(worker, NULL); + check(seen[0] == expected[0] && seen[1] == expected[1], "worker sees resized drawable"); + } + gl_renderer_render_thread_sync("test_done"); check(glGetError() == GL_NO_ERROR, "GL error"); printf("rb=%016llx\n", (unsigned long long)rb_digest); printf("digest=%016llx\n", (unsigned long long)digest);