Reuse desktop GL storage across dynamic-resolution steps

This commit is contained in:
Shokunin
2026-10-21 09:29:47 -07:00
parent f377b03add
commit b570da1a42
3 changed files with 64 additions and 3 deletions
+2
View File
@@ -213,6 +213,8 @@ on a fixed region -> next.
## 5. Status / Log (update every session)
- **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.
- **2026-10-21 (BRIEF-4 presenter accounting):** Publish composer swap/slot wait separately and exclude that pacing from dynamic-resolution work backpressure. Windows and Mac real-context checks pass. Quiet fullscreen route: 60.020 Hz, four completion gaps >25ms, p99 busy 48.0%; allocation stalls remain. Raw completion and queue-wait metrics are unchanged.
- **2026-10-21 (BRIEF-4 GPU timing):** Split TIME_ELAPSED brackets around command starvation and presentation waits. Real-context 30ms producer-idle control reports 35.141ms with the old bracket / 5.620ms active GPU work; Windows and Mac GL fixtures and render-thread unit checks pass. Completion stalls remain; dynamic-resolution reallocations are the next target. No guest or completion-clock changes.
+57 -3
View File
@@ -10809,6 +10809,49 @@ static void dyn_stats_lock(void) { while (atomic_flag_test_and_set_explicit(&s_d
static void dyn_stats_unlock(void) { atomic_flag_clear_explicit(&s_dyn_stats_lock, memory_order_release); }
static int s_rthm_scale = 1; /* emulation-side level (recording) */
/* Desktop immediate-mode GL can reuse the ceiling allocation. On Apple's
* GL-on-Metal, attachment load/store bandwidth scales with allocation size,
* so keep allocating at the active level there. The control retains the old
* allocation path for exact image comparisons on either backend. */
static int dyn_reuse_storage(void) {
static int enabled = -1;
if (enabled < 0) {
const char *e = getenv("PSX_GL_DYNRES_REUSE_STORAGE");
#ifdef __APPLE__
enabled = e && e[0] == '1';
#else
enabled = !(e && e[0] == '0');
#endif
}
return enabled;
}
static void dyn_clear_wide_rest(GLuint fbo, const int (*cols)[2], int ncols,
int y0, int y1, int scale) {
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, fbo);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(0, 0, 0, 0);
glEnable(GL_SCISSOR_TEST);
if (y0 > 0) {
glScissor(0, 0, g_wide_w * scale, y0 * scale); glClear(GL_COLOR_BUFFER_BIT);
}
if (y1 < VRAM_H) {
glScissor(0, y1 * scale, g_wide_w * scale, (VRAM_H - y1) * scale);
glClear(GL_COLOR_BUFFER_BIT);
}
int x = 0;
for (int c = 0; c <= ncols; ++c) {
int end = c < ncols ? cols[c][0] : g_wide_w;
if (end > x) {
glScissor(x * scale, y0 * scale, (end - x) * scale, (y1 - y0) * scale);
glClear(GL_COLOR_BUFFER_BIT);
}
if (c < ncols) x = cols[c][1];
}
glDisable(GL_SCISSOR_TEST);
glStencilMask(0xFF); glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT);
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
}
static int dyn_apply(int snew) {
/* Never in the windowed high-resolution mode (s_hiw): dyn_eligible keeps
* s_alloc_scale 0 there; refuse here too rather than step its tiles. */
@@ -10904,7 +10947,14 @@ static int dyn_apply(int snew) {
* step down reseeds the old surface's corner in place; a step up past
* the old allocation is refused. */
GLuint ntex = 0, nrb = 0, nfbo = 0;
if (!hr_alloc_surface(snew, &ntex, &nrb, &nfbo)) {
if (dyn_reuse_storage() && snew <= s_hr_alloc) {
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_hr_fbo);
glDisable(GL_SCISSOR_TEST);
glDepthMask(GL_TRUE); glClearDepth(1.0);
glStencilMask(0xFF); glClearStencil(0);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
} else if (!hr_alloc_surface(snew, &ntex, &nrb, &nfbo)) {
if (snew > s_hr_alloc) {
s_dyn_stats.refused++;
fprintf(stdout, "psxrecomp: dynamic resolution %dx -> %dx refused (the "
@@ -10960,7 +11010,11 @@ static int dyn_apply(int snew) {
* before and the surface keeps its larger allocation; a step up
* past it drops the surface (recreated at the next target). */
GLuint ntex = 0, nrb = 0, nfbo = 0;
if (wide_alloc_surface(snew, &ntex, &nrb, &nfbo)) {
if (dyn_reuse_storage() && snew <= s_wide_as[i]) {
dyn_rescale_in_place(s_wide_fbo[i], g_wide_w, (const int (*)[2])cols, nc,
y0, y1, sold, snew);
dyn_clear_wide_rest(s_wide_fbo[i], (const int (*)[2])cols, nc, y0, y1, snew);
} else if (wide_alloc_surface(snew, &ntex, &nrb, &nfbo)) {
for (int c = 0; c < nc; c++)
if (cols[c][1] > cols[c][0])
dyn_rescale_rect(s_wide_tex[i], cols[c][0] * sold, y0 * sold, sold,
@@ -10993,7 +11047,7 @@ static int dyn_apply(int snew) {
int hw = VRAM_W * snew, hh = VRAM_H * snew;
int sw = snew <= 2 ? hw : (hw < GL_SCRATCH_TILE ? hw : GL_SCRATCH_TILE);
int sh = snew <= 2 ? hh : (hh < GL_SCRATCH_TILE ? hh : GL_SCRATCH_TILE);
if (s_scratch_w > sw || s_scratch_h > sh) {
if (!dyn_reuse_storage() && (s_scratch_w > sw || s_scratch_h > sh)) {
p_glActiveTexture(PSXGL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, s_scratch_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, sw, sh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+5
View File
@@ -341,6 +341,11 @@ int main(int argc, char **argv) {
gl_renderer_dynres_stats(&ds);
printf("dyn_steps=%llu asked=%d\n", (unsigned long long)ds.steps, nsteps);
check(ds.steps == (uint64_t)nsteps && ds.level == scale, "every step applied");
printf("dyn_allocs hr=%llu wide=%llu\n", (unsigned long long)ds.hr_reallocs,
(unsigned long long)ds.wide_reallocs);
if (dyn_reuse_storage())
check(ds.hr_reallocs == 0 && ds.wide_reallocs == 0,
"ceiling storage reused at every level");
if (threaded) {
GlRthCosts co;
gl_renderer_render_thread_costs(&co);