From 36108c9bc3dedaf7a74c3c65e68b5bf2a526bb67 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:31:13 -0700 Subject: [PATCH] Cache display geometry within each GPU execution context --- docs/internal/FAITHFUL_TIMING_PLAN.md | 17 +++++++ runtime/src/gpu.c | 39 ++++++++++++++- runtime/tests/test_gpu_snapshot_scratch.c | 60 +++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/docs/internal/FAITHFUL_TIMING_PLAN.md b/docs/internal/FAITHFUL_TIMING_PLAN.md index 078d0b43..a28539a9 100644 --- a/docs/internal/FAITHFUL_TIMING_PLAN.md +++ b/docs/internal/FAITHFUL_TIMING_PLAN.md @@ -2260,3 +2260,20 @@ common emulation/context regression work while retaining the bar, or explicitly revise load/bar. No further code changes or pushes; all code is committed. Receipts: R4 local brief5-worker-idle and brief5-loaded-profile-diff (including unprofiled-prefix-proof.json). Cadence, title ctest and both pushes remain pending. + +2026-10-21 BRIEF-5 scope resumed: Claude authorizes the common-cost comparison. +An exact da09fac/8130e6fd archive build with production options, loaded setting 0, +frames 3450..4350, has 60.96% sampled source CPU and unprofiled-prefix p99 67.4% +(32 windows >=60), versus current 59.39% / 65.9% (18). Native execution is +positive with zero interpreted calls/misses; the historical 58.7% is not +reproduced. Reuse the existing current profile rather than repeat it. The initial +archive profile used netplay OFF and a wrong stop variable; exclude it. +The paired profile shows gpu_get_display_info at 1.003% current wall samples +versus zero separate old leaf samples. Cache the derived display geometry per +execution context/reader until GP1, reset or snapshot restore changes registers; +private invalidation does not touch live readers. The production GPU fixture +checks all resolution modes, repeated queries, private/source separation, +recapture, checkpoint and savestate restore, reset and power-on PAL. It passes. +Continue failing loaded gate, cadence, title ctest and both pushes; quiet remains +passed. No acceptance or current blocker. Receipts: R4 local +brief5-stage2-regression/valid-* (first run explicitly INVALID). diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 3a360b1b..0ebf90cc 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -50,6 +50,7 @@ #include "ws_screen_mask.h" #include "ws_radial_screen_mask.h" #include +#include #include #include #include @@ -69,14 +70,30 @@ extern uint32_t psx_read_word(uint32_t addr); /* ---- VRAM ---- */ /* Default execution and render-thread queries keep the live objects. Only * a bound replay thread selects private regions; no live state is swapped. */ +typedef struct GpuDisplayCache { + uint64_t epoch; + GpuDisplayInfo info; +} GpuDisplayCache; struct GpuExecutionSnapshot { uint64_t frame; int recent_mdec; void *slots[323]; void *storage; int ready; + uint64_t display_epoch; + GpuDisplayCache display_cache; }; static PSX_THREAD_LOCAL GpuExecutionSnapshot *s_gpu_execution; +/* Display registers change at GP1/state boundaries, not per primitive. Keep + * the derived view local to its reader; worker invalidation never touches the + * source epoch. The epoch also invalidates other live readers' local caches. */ +static _Atomic uint64_t s_display_epoch = 1; +static PSX_THREAD_LOCAL GpuDisplayCache t_display_cache; +static void gpu_display_state_changed(void) { + if (s_gpu_execution) ++s_gpu_execution->display_epoch; + else atomic_fetch_add_explicit(&s_display_epoch, 1, memory_order_release); +} + static uint16_t vram[1024 * 512]; typedef uint16_t GpuContextType_vram[524288]; static inline GpuContextType_vram *gpu_context_vram(void) { @@ -5265,6 +5282,7 @@ static inline GpuContextType_g_pollhack_vblank_count *gpu_context_g_pollhack_vbl /* ---- Initialization ---- */ static void gpu_reset_state(int clear_vram) { + gpu_display_state_changed(); ws_reset_scene_history(); if (clear_vram) { memset((*gpu_context_vram()), 0, sizeof((*gpu_context_vram()))); @@ -5365,12 +5383,15 @@ void gpu_init(void) { gpu_reset_state(1); (*gpu_context_video_mode()) = (*gpu_context_s_power_on_video_mode()); (void)psx_video_timing_set_pal((int)(*gpu_context_video_mode())); + gpu_display_state_changed(); } void gpu_set_power_on_video_mode(int pal) { + gpu_display_state_changed(); (*gpu_context_s_power_on_video_mode()) = pal ? 1u : 0u; (*gpu_context_video_mode()) = (*gpu_context_s_power_on_video_mode()); (void)psx_video_timing_set_pal((int)(*gpu_context_video_mode())); + gpu_display_state_changed(); } /* ---- GPUSTAT read (0x1F801814) ---- */ @@ -5932,7 +5953,7 @@ int gpu_display_is_depth24(void) { return (int)((*gpu_context_display_depth()) & 1u); } -void gpu_get_display_info(GpuDisplayInfo* out) { +static void gpu_build_display_info(GpuDisplayInfo* out) { out->display_x = (*gpu_context_display_area_x()); out->display_y = (*gpu_context_display_area_y()); out->depth24 = (int)((*gpu_context_display_depth()) & 1u); @@ -6012,6 +6033,18 @@ void gpu_get_display_info(GpuDisplayInfo* out) { if ((*gpu_context_vres())) out->screen_offset_y *= 2; } +void gpu_get_display_info(GpuDisplayInfo* out) { + GpuExecutionSnapshot *execution = s_gpu_execution; + uint64_t epoch = execution ? execution->display_epoch : + atomic_load_explicit(&s_display_epoch, memory_order_acquire); + GpuDisplayCache *cache = execution ? &execution->display_cache : &t_display_cache; + if (cache->epoch != epoch) { + gpu_build_display_info(&cache->info); + cache->epoch = epoch; + } + *out = cache->info; +} + /* Debug accessors for GP1 display-range / mode (TCP gpu_state). */ void gpu_get_crtc_debug(uint32_t *x1, uint32_t *x2, uint32_t *y1, uint32_t *y2, uint32_t *hres1_out, uint32_t *hres2_out) { @@ -9421,6 +9454,7 @@ static inline GpuContextType_gpu_local_gpu_write_gp1_reason *gpu_context_gpu_loc return s_gpu_execution ? (GpuContextType_gpu_local_gpu_write_gp1_reason *)s_gpu_execution->slots[322] : &gpu_local_gpu_write_gp1_reason; } void gpu_write_gp1(uint32_t val) { + gpu_display_state_changed(); uint32_t cmd = (val >> 24) & 0x3F; switch (cmd) { @@ -9445,6 +9479,7 @@ void gpu_write_gp1(uint32_t val) { psx_fatal_halt((*gpu_context_gpu_local_gpu_write_gp1_reason())); } } + gpu_display_state_changed(); } /* ---- boot snapshot: complete GPU register state (see boot_state.h) ---- */ @@ -9526,6 +9561,7 @@ static int gpu_snap_emit(PstW *w) { return 1; } static int gpu_snap_parse(PstR *r) { + gpu_display_state_changed(); uint32_t u; int32_t i; uint16_t h; #define RU(f) do { if (!pst_r_u32(r, &u)) return 0; (f) = u; } while (0) #define RI(f) do { if (!pst_r_i32(r, &i)) return 0; (f) = i; } while (0) @@ -10225,6 +10261,7 @@ int gpu_execution_snapshot_capture(GpuExecutionSnapshot *snapshot) { ColorLut **lut = (ColorLut **)gpu_execution_region(snapshot, &s_screen_lut); if (*lut) color_lut_destroy(*lut); snapshot->ready = 0; + ++snapshot->display_epoch; snapshot->frame = s_frame_count; snapshot->recent_mdec = mdec_recently_active(WS_FMV_HYSTERESIS); for (size_t i = 0; i < sizeof s_gpu_execution_regions / sizeof s_gpu_execution_regions[0]; ++i) { diff --git a/runtime/tests/test_gpu_snapshot_scratch.c b/runtime/tests/test_gpu_snapshot_scratch.c index 5c4e68c4..7fc7de9d 100644 --- a/runtime/tests/test_gpu_snapshot_scratch.c +++ b/runtime/tests/test_gpu_snapshot_scratch.c @@ -33,5 +33,65 @@ int main(void) { gpu_pass_checkpoint_restore(); assert(gpu_execution_snapshot_capture(image)); gpu_execution_snapshot_destroy(image); + /* Derived display caches follow register writes, state restore and private + * snapshot ownership. Compare each cached result with the uncached view. */ + gpu_init(); + GpuDisplayInfo actual, display_expected, source; + const uint32_t modes[] = {0, 1, 2, 3, 0x40, 0x0C, 0x18}; + for (unsigned i = 0; i < sizeof modes / sizeof modes[0]; ++i) { + gpu_write_gp1(0x08000000u | modes[i]); + gpu_write_gp1(0x0500180Cu); + gpu_write_gp1(0x060C0200u); + gpu_write_gp1(0x07040010u); + gpu_build_display_info(&display_expected); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &display_expected, sizeof actual)); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &display_expected, sizeof actual)); + } + source = actual; + image = gpu_execution_snapshot_create(); + assert(image && gpu_execution_snapshot_capture(image)); + assert(gpu_execution_snapshot_bind(image)); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &source, sizeof actual)); + gpu_write_gp1(0x08000003u); + gpu_write_gp1(0x05000029u); + gpu_build_display_info(&display_expected); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &display_expected, sizeof actual)); + assert(actual.display_x == 41); + assert(gpu_pass_checkpoint_save()); + gpu_write_gp1(0); + gpu_get_display_info(&actual); + assert(actual.display_x == 0); + gpu_pass_checkpoint_restore(); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &display_expected, sizeof actual)); + gpu_execution_snapshot_unbind(); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &source, sizeof actual)); + gpu_write_gp1(0x08000001u); + assert(gpu_execution_snapshot_capture(image)); + gpu_get_display_info(&source); + assert(gpu_execution_snapshot_bind(image)); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &source, sizeof actual)); + gpu_execution_snapshot_unbind(); + gpu_execution_snapshot_destroy(image); + uint32_t bytes = gpu_snapshot_bytes(); + uint8_t *saved = malloc(bytes); + assert(saved); + gpu_snapshot_write(saved); + gpu_write_gp1(0); + gpu_get_display_info(&actual); + assert(gpu_snapshot_read(saved, bytes)); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &source, sizeof actual)); + free(saved); + gpu_set_power_on_video_mode(1); + gpu_build_display_info(&display_expected); + gpu_get_display_info(&actual); + assert(!memcmp(&actual, &display_expected, sizeof actual)); return 0; }