mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Reuse queued vertex uploads and expose renderer completion costs
This commit is contained in:
committed by
Matthew Stanley
parent
64feb76658
commit
14e20d67f7
@@ -23,6 +23,18 @@ Optional bounded benchmark controls:
|
||||
`PSX_RUNTIME_PERF_DIAG=1` for guest/pacer counters. Times use microseconds;
|
||||
differences between adjacent rows describe the intervening guest interval.
|
||||
A companion `.spans.csv` exports the bounded host attribution ring.
|
||||
`.render.csv` exports GL work counts. With `PSX_GL_CPU_PERF=1` it also
|
||||
reports inclusive CPU wall spans (calls, total and maximum) for depth
|
||||
restoration, queue replay, VRAM copies, interpolation captures/intervals and
|
||||
centre splices. These spans overlap; do not sum them as exclusive costs.
|
||||
Collection is off by default and its queue drain/export happens after the
|
||||
measured route window. Use separate profiling and acceptance runs.
|
||||
`PSX_GL_CPU_PERF=2` additionally finishes queued GPU work before and after
|
||||
queue replay, VRAM copies, captures and centre splices. These spans include
|
||||
GPU completion; `_prior_drain` rows measure earlier queued work separately.
|
||||
This serializes the pipeline and is attribution only. Totals cover the
|
||||
whole replay, including warmup. `.rth.json` records queue waits and recent
|
||||
context-acquisition reasons before the report's own drain.
|
||||
`PSX_GL_PERF=1` additionally enables GPU queries in production and exports
|
||||
rolling GPU aggregates to `.gpu.csv`; use this for separate attribution
|
||||
trials, since queries can perturb the measured workload.
|
||||
|
||||
@@ -213,6 +213,15 @@ on a fixed region -> next.
|
||||
|
||||
## 5. Status / Log (update every session)
|
||||
|
||||
- **2026-10-21 (queued vertex uploads):** Pack replay vertices at offsets
|
||||
aligned to each layout and upload once per used VBO per flush, reusing the
|
||||
data across canonical tiles and wide mirrors. Native/readback/hires/wide
|
||||
and stencil hashes match the per-batch upload control, 1x/4x, both allocation
|
||||
modes and renderer thread off/on. R4's 30-second Ultra probe improves to
|
||||
55.684 guest Hz / p99 busy 63.3%, still below the owner's acceptance bar.
|
||||
Broader bloom fusion passes the same matrix for mask-set and translucent
|
||||
sources; production timing is pending.
|
||||
|
||||
- **2026-10-21 (render coverage and driver waits):** Enabled temporal
|
||||
presentation on the existing renderer context/thread. Queued wide mirrors
|
||||
retain bloom classification; eligible opaque marking is fused with colour,
|
||||
|
||||
@@ -568,6 +568,14 @@ int gl_renderer_pres_get(uint64_t seq, GlPresEvent *out);
|
||||
* sets/frame avg, [17]=wide FBO creations/frame avg. GPU phases are true
|
||||
* GL_TIME_ELAPSED times (CPU-overhead independent). Returns the count. */
|
||||
int gl_renderer_perf_aggregate(int wide_filter, double out[18]);
|
||||
/* Bounded benchmark attribution. CPU spans require PSX_GL_CPU_PERF=1;
|
||||
* reading drains the render queue, so read outside the measured interval. */
|
||||
typedef struct {
|
||||
uint64_t counts[5];
|
||||
uint64_t calls[6], ticks[6], max_ticks[6], frequency;
|
||||
uint64_t prior_ticks[6], prior_max_ticks[6];
|
||||
} GlRenderWorkStats;
|
||||
void gl_renderer_work_stats(GlRenderWorkStats *out);
|
||||
|
||||
/* Native-wide mirror ablation (perf attribution, debug cmd gl_ws_ablate):
|
||||
* 0 = normal, 1 = skip the whole mirror pass (incl. wide_clear), 2 = full mirror
|
||||
|
||||
+191
-50
@@ -117,6 +117,34 @@
|
||||
#include <stdatomic.h>
|
||||
#include "png_write.h" /* png_write_rgb — present_shot readback */
|
||||
static uint64_t s_fg_flips; /* frame generation (below): game frames seen */
|
||||
static GlRenderWorkStats s_cpu_work;
|
||||
static int s_cpu_perf_mode = -1;
|
||||
static int gl_cpu_probe_gpu(int kind) {
|
||||
return s_cpu_perf_mode == 2 && kind != 0 && kind != 4;
|
||||
}
|
||||
static uint64_t gl_cpu_begin(int kind) {
|
||||
if (s_cpu_perf_mode < 0) {
|
||||
const char *e = getenv("PSX_GL_CPU_PERF");
|
||||
s_cpu_perf_mode = e && (e[0] == '1' || e[0] == '2') ? e[0] - '0' : 0;
|
||||
}
|
||||
if (!s_cpu_perf_mode) return 0;
|
||||
if (gl_cpu_probe_gpu(kind)) {
|
||||
uint64_t start = SDL_GetPerformanceCounter();
|
||||
glFinish();
|
||||
uint64_t ticks = SDL_GetPerformanceCounter() - start;
|
||||
s_cpu_work.prior_ticks[kind] += ticks;
|
||||
if (ticks > s_cpu_work.prior_max_ticks[kind]) s_cpu_work.prior_max_ticks[kind] = ticks;
|
||||
}
|
||||
return SDL_GetPerformanceCounter();
|
||||
}
|
||||
static void gl_cpu_end(int kind, uint64_t start) {
|
||||
if (!start) return;
|
||||
if (gl_cpu_probe_gpu(kind)) glFinish();
|
||||
uint64_t ticks = SDL_GetPerformanceCounter() - start;
|
||||
s_cpu_work.calls[kind]++;
|
||||
s_cpu_work.ticks[kind] += ticks;
|
||||
if (ticks > s_cpu_work.max_ticks[kind]) s_cpu_work.max_ticks[kind] = ticks;
|
||||
}
|
||||
|
||||
#ifndef GL_BGRA
|
||||
#define GL_BGRA 0x80E1
|
||||
@@ -2472,7 +2500,7 @@ static int depth_state_fast(void) {
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
static void depth_restore_ex(int mode, int tex, int n, int semi, int mask) {
|
||||
static void depth_restore_ex_timed_impl(int mode, int tex, int n, int semi, int mask, int first) {
|
||||
if (mode == 0) return;
|
||||
if (mode == 1) {
|
||||
p_glUniform1f(tex ? s_tex_uZbias : s_geo_uZbias, 0.0f);
|
||||
@@ -2493,7 +2521,7 @@ static void depth_restore_ex(int mode, int tex, int n, int semi, int mask) {
|
||||
p_glUniform1i(s_tex_uBcls, 3);
|
||||
s_coverage_depth_draws++;
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLES, 0, n);
|
||||
glDrawArrays(GL_TRIANGLES, first, n);
|
||||
if (tex && coverage_fast_enabled()) p_glUniform1i(s_tex_uBcls, 0);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
if (st) glEnable(GL_STENCIL_TEST);
|
||||
@@ -2504,6 +2532,12 @@ static void depth_restore_ex(int mode, int tex, int n, int semi, int mask) {
|
||||
if (mode == 2) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
}
|
||||
|
||||
static void depth_restore_ex(int mode, int tex, int n, int semi, int mask, int first) {
|
||||
uint64_t start = gl_cpu_begin(0);
|
||||
depth_restore_ex_timed_impl(mode, tex, n, semi, mask, first);
|
||||
gl_cpu_end(0, start);
|
||||
}
|
||||
|
||||
/* ---- coherency: CPU -> GPU upload flush --------------------------------- */
|
||||
/* CPU-side VRAM writes (GP0 A0 transfers, DMA, single pixel pokes) land in
|
||||
* the CPU array immediately and accumulate s_up_rects. Flushing before the
|
||||
@@ -3750,16 +3784,30 @@ static int bcls_passes(int semi, int b3d) {
|
||||
return BCLS_CLEAR | BCLS_SET;
|
||||
}
|
||||
static uint64_t s_bcls_fused_draws;
|
||||
static int bcls_fuse_extended(void) {
|
||||
static int enabled = -1;
|
||||
if (enabled < 0) {
|
||||
const char *e = getenv("PSX_GL_BLOOM_FUSE_EXTEND");
|
||||
enabled = !(e && e[0] == '0');
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
/* Opaque, uniformly classified batches can write bloom bit 1 with their
|
||||
* colour, also clearing mask bit 0 as the unfused colour draw does.
|
||||
* Destination masking / set-mask and mixed
|
||||
* or translucent batches keep the existing coverage passes. */
|
||||
static int bcls_fuse_begin(int semi, int dmode, int mask, int check, int b3d) {
|
||||
if (!coverage_fast_enabled() || !s_bcls_on || s_fg_drawing || semi >= 0 ||
|
||||
dmode == 2 || mask || check || b3d < 0) return 0;
|
||||
static int bcls_fuse_begin(int semi, int dmode, int mask, int check, int b3d, int tex) {
|
||||
if (!coverage_fast_enabled() || !s_bcls_on || s_fg_drawing ||
|
||||
dmode == 2 || check || b3d < 0) return 0;
|
||||
if (bcls_fuse_extended()) {
|
||||
/* Translucent 3D sources set bloom on every covered fragment, just
|
||||
* like their separate marking pass. A 2D translucent draw must keep
|
||||
* the underlying bloom marks, so it retains the old path. */
|
||||
if (semi >= 0 && b3d != 1) return 0;
|
||||
} else if (semi >= 0 || mask) return 0;
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilMask(0x03);
|
||||
glStencilFunc(GL_ALWAYS, b3d ? 0x02 : 0x00, 0xFF);
|
||||
glStencilMask(tex && semi == 4 && !mask ? 0x02 : 0x03);
|
||||
glStencilFunc(GL_ALWAYS, (b3d ? 0x02 : 0x00) | (mask ? 1 : 0), 0xFF);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
s_bcls_fused_draws++;
|
||||
return 1;
|
||||
@@ -3770,10 +3818,13 @@ static int bcls_fuse_begin(int semi, int dmode, int mask, int check, int b3d) {
|
||||
* does not clear: what shows through it (a lamp under a fog or shade quad)
|
||||
* keeps glowing. */
|
||||
#define BCLS_DRAW(semi, dmode, mask, check, b3d, DRAW) do { \
|
||||
const int bcls_fused_ = bcls_fuse_begin((semi), (dmode), (mask), (check), (b3d)); \
|
||||
const int bcls_add_ = bcls_fuse_extended() && ((semi) == 1 || (semi) == 3); \
|
||||
const int bcls_fused_ = bcls_fuse_begin((semi), (dmode), (mask), (check), bcls_add_ ? 1 : (b3d), 0); \
|
||||
DRAW; \
|
||||
if (bcls_fused_) mask_stencil_ex((mask), (check)); \
|
||||
else if (s_bcls_on && (dmode) != 2 && !s_fg_drawing) { \
|
||||
else if (s_bcls_on && (dmode) != 2 && !s_fg_drawing && \
|
||||
!(bcls_fuse_extended() && (semi) >= 0 && (b3d) == 0 && \
|
||||
(semi) != 1 && (semi) != 3)) { \
|
||||
const int bcls_p_ = bcls_passes((semi), (b3d)); \
|
||||
bcls_begin(0, (semi) == 1 || (semi) == 3); \
|
||||
if (bcls_p_ & BCLS_CLEAR) { bcls_pass(0, 2); DRAW; } \
|
||||
@@ -3788,20 +3839,20 @@ static int bcls_fuse_begin(int semi, int dmode, int mask, int check, int b3d) {
|
||||
* drawn under; track_stencil lets the canonical pass record deferred stencil
|
||||
* (a replay into the high-resolution window must not touch that flag). */
|
||||
static void tex_draw_passes_ex(int nverts, int semi, int tb_mask, int check,
|
||||
int track_stencil, int b3d, int dmode) {
|
||||
int track_stencil, int b3d, int dmode, int first) {
|
||||
int fused = 0;
|
||||
p_glUniform1i(s_uSemimode, semi < 0 ? 0 : semi);
|
||||
if (semi < 0) {
|
||||
glDisable(GL_BLEND);
|
||||
mask_stencil_ex(tb_mask, check);
|
||||
p_glUniform1i(s_uSemipass, 0); /* all texels, one ordered colour pass */
|
||||
fused = bcls_fuse_begin(semi, dmode, tb_mask, check, b3d);
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
fused = bcls_fuse_begin(semi, dmode, tb_mask, check, b3d, 1);
|
||||
glDrawArrays(GL_TRIANGLES, first, nverts);
|
||||
if (check) {
|
||||
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); /* stencil-only fixup */
|
||||
mask_stencil_ex(1, check);
|
||||
p_glUniform1i(s_uSemipass, 2); /* STP=1 texels set the mask bit */
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
glDrawArrays(GL_TRIANGLES, first, nverts);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
} else if (!tb_mask) {
|
||||
/* Alpha is already exact; defer its duplicate stencil encoding
|
||||
@@ -3818,24 +3869,30 @@ static void tex_draw_passes_ex(int nverts, int semi, int tb_mask, int check,
|
||||
p_glBlendFuncSeparate(GL_ONE, PSXGL_SRC1_ALPHA, GL_ONE, GL_ZERO);
|
||||
if (tb_mask) mask_stencil_ex(1, check); else glDisable(GL_STENCIL_TEST);
|
||||
p_glUniform1i(s_uSemipass, 0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
fused = bcls_fuse_begin(semi, dmode, tb_mask, check, b3d, 1);
|
||||
glDrawArrays(GL_TRIANGLES, first, nverts);
|
||||
if (!tb_mask && track_stencil) s_stencil_valid = 0;
|
||||
} else {
|
||||
glDisable(GL_BLEND); /* Pass 1: STP=0 texels (opaque) */
|
||||
mask_stencil_ex(tb_mask, check);
|
||||
p_glUniform1i(s_uSemipass, 1);
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
fused = bcls_fuse_begin(semi, dmode, tb_mask, check, b3d, 1);
|
||||
glDrawArrays(GL_TRIANGLES, first, nverts);
|
||||
apply_psx_blend(semi); /* Pass 2: STP=1 texels (blended) */
|
||||
mask_stencil_ex(1, check);
|
||||
p_glUniform1i(s_uSemipass, 2);
|
||||
glDrawArrays(GL_TRIANGLES, 0, nverts);
|
||||
fused &= bcls_fuse_begin(semi, dmode, 1, check, b3d, 1);
|
||||
glDrawArrays(GL_TRIANGLES, first, nverts);
|
||||
}
|
||||
if (fused) {
|
||||
if (semi == 4 && !check && !tb_mask) glDisable(GL_STENCIL_TEST);
|
||||
else mask_stencil_ex(semi < 0 || semi == 4 ? tb_mask : 1, check);
|
||||
}
|
||||
if (fused) mask_stencil_ex(tb_mask, check);
|
||||
else if (s_bcls_on && !s_fg_drawing) {
|
||||
const int bp = bcls_passes(semi, b3d);
|
||||
bcls_begin(1, 0);
|
||||
if (bp & BCLS_CLEAR) { bcls_pass(1, 2); glDrawArrays(GL_TRIANGLES, 0, nverts); }
|
||||
if (bp & BCLS_SET) { bcls_pass(1, 1); glDrawArrays(GL_TRIANGLES, 0, nverts); }
|
||||
if (bp & BCLS_CLEAR) { bcls_pass(1, 2); glDrawArrays(GL_TRIANGLES, first, nverts); }
|
||||
if (bp & BCLS_SET) { bcls_pass(1, 1); glDrawArrays(GL_TRIANGLES, first, nverts); }
|
||||
/* Back to the state the branch above ended in. */
|
||||
p_glUniform1i(s_tex_uBcls, 0);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
@@ -3857,7 +3914,7 @@ static void tex_draw_passes_ex(int nverts, int semi, int tb_mask, int check,
|
||||
}
|
||||
}
|
||||
static void tex_batch_draw_passes(int nverts, int semi) {
|
||||
tex_draw_passes_ex(nverts, semi, s_tb_mask, s_mask_check, 1, s_tb_b3d, s_tb_depth);
|
||||
tex_draw_passes_ex(nverts, semi, s_tb_mask, s_mask_check, 1, s_tb_b3d, s_tb_depth, 0);
|
||||
}
|
||||
|
||||
/* ---- frame_perf CPU attribution (native-wide wedge hunt) ----------------- *
|
||||
@@ -3944,18 +4001,55 @@ static int s_hq_n = 0, s_hq_cap = 0;
|
||||
static float *s_hq_v = NULL;
|
||||
static size_t s_hq_vn = 0, s_hq_vcap = 0;
|
||||
static uint64_t s_hq_flushes = 0, s_hq_cmds = 0;
|
||||
static uint64_t s_hq_uploads, s_hq_saved_uploads;
|
||||
static int hiw_upload_fast(void) {
|
||||
static int enabled = -1;
|
||||
if (enabled < 0) {
|
||||
const char *e = getenv("PSX_GL_QUEUE_UPLOAD_FAST");
|
||||
enabled = !(e && e[0] == '0');
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
/* Each VAO retains its existing vertex layout/buffer. Upload the packed queue
|
||||
* once to each used buffer, then reuse it for every tile and wide target.
|
||||
* Entries start at a multiple of their stride, so DrawArrays can select them
|
||||
* without changing attribute pointers or orphaning a buffer per batch. */
|
||||
static void hiw_upload_queue(void) {
|
||||
if (!hiw_upload_fast()) return;
|
||||
int used[3] = {0};
|
||||
for (int i = 0; i < s_hq_n; ++i) used[s_hq[i].kind] = 1;
|
||||
for (int kind = HQ_TEX; kind <= HQ_GEO; ++kind) {
|
||||
if (!used[kind]) continue;
|
||||
p_glBindBuffer(PSXGL_ARRAY_BUFFER, kind == HQ_TEX ? s_tex_vbo : s_geo_vbo);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)(s_hq_vn * sizeof(float)),
|
||||
s_hq_v, PSXGL_STREAM_DRAW);
|
||||
s_hq_uploads++;
|
||||
}
|
||||
}
|
||||
static int hiw_upload_cmd(const HiCmd *c) {
|
||||
int stride = c->kind == HQ_TEX ? TEXV : 6;
|
||||
if (hiw_upload_fast()) {
|
||||
s_hq_saved_uploads++;
|
||||
return (int)(c->vfirst / (size_t)stride);
|
||||
}
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * stride * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
s_hq_uploads++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static HiCmd *hiw_enqueue(int kind, const float *verts, int nverts, int stride) {
|
||||
size_t nf = (size_t)nverts * (size_t)stride;
|
||||
size_t first = (s_hq_vn + (size_t)stride - 1) / (size_t)stride * (size_t)stride;
|
||||
if (s_hq_n == s_hq_cap) {
|
||||
int cap = s_hq_cap ? s_hq_cap * 2 : 256;
|
||||
HiCmd *q = (HiCmd *)realloc(s_hq, (size_t)cap * sizeof(HiCmd));
|
||||
if (!q) return NULL;
|
||||
s_hq = q; s_hq_cap = cap;
|
||||
}
|
||||
if (s_hq_vn + nf > s_hq_vcap) {
|
||||
if (first + nf > s_hq_vcap) {
|
||||
size_t cap = s_hq_vcap ? s_hq_vcap : 65536;
|
||||
while (cap < s_hq_vn + nf) cap *= 2;
|
||||
while (cap < first + nf) cap *= 2;
|
||||
float *v = (float *)realloc(s_hq_v, cap * sizeof(float));
|
||||
if (!v) return NULL;
|
||||
s_hq_v = v; s_hq_vcap = cap;
|
||||
@@ -3963,10 +4057,11 @@ static HiCmd *hiw_enqueue(int kind, const float *verts, int nverts, int stride)
|
||||
HiCmd *c = &s_hq[s_hq_n++];
|
||||
memset(c, 0, sizeof *c);
|
||||
c->kind = (uint8_t)kind;
|
||||
c->vfirst = s_hq_vn;
|
||||
c->vfirst = first;
|
||||
c->vcount = nverts;
|
||||
memcpy(s_hq_v + s_hq_vn, verts, nf * sizeof(float));
|
||||
s_hq_vn += nf;
|
||||
memset(s_hq_v + s_hq_vn, 0, (first - s_hq_vn) * sizeof(float));
|
||||
memcpy(s_hq_v + first, verts, nf * sizeof(float));
|
||||
s_hq_vn = first + nf;
|
||||
c->check = (uint8_t)s_mask_check;
|
||||
c->depth = (uint8_t)s_hq_depth_cur;
|
||||
c->ax0 = s_area_x1; c->ay0 = s_area_y1; c->ax1 = s_area_x2; c->ay1 = s_area_y2;
|
||||
@@ -4114,13 +4209,12 @@ static void hiw_replay_wide(void) {
|
||||
p_glUniform1f(s_tex_uXcenter, c->wcenter);
|
||||
bind_textured_resources(c->tex, c->palette, c->hd_tex);
|
||||
p_glUniform1i(s_uMaskset, c->mask);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * TEXV * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
int first = hiw_upload_cmd(c);
|
||||
for (int r=0; r<scissors; ++r) {
|
||||
glScissor(sx[r] * S, c->wsy * S, sw[r] * S, c->wsh * S);
|
||||
depth_apply_ex(c->depth, 1);
|
||||
tex_draw_passes_ex(c->vcount, c->semi, c->mask, c->check, 0, c->b3d, c->depth);
|
||||
depth_restore_ex(c->depth, 1, c->vcount, c->semi, c->mask);
|
||||
tex_draw_passes_ex(c->vcount, c->semi, c->mask, c->check, 0, c->b3d, c->depth, first);
|
||||
depth_restore_ex(c->depth, 1, c->vcount, c->semi, c->mask, first);
|
||||
}
|
||||
} else {
|
||||
if (cur != HQ_GEO) {
|
||||
@@ -4132,15 +4226,14 @@ static void hiw_replay_wide(void) {
|
||||
p_glUniform1f(s_geo_uXoff, (float)c->wdx);
|
||||
p_glUniform1f(s_geo_uXscale, c->wscale);
|
||||
p_glUniform1f(s_geo_uXcenter, c->wcenter);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
int first = hiw_upload_cmd(c);
|
||||
for (int r=0; r<scissors; ++r) {
|
||||
glScissor(sx[r] * S, c->wsy * S, sw[r] * S, c->wsh * S);
|
||||
if (c->semi >= 0) apply_psx_blend(c->semi); else glDisable(GL_BLEND);
|
||||
mask_stencil_ex(c->mask, c->check);
|
||||
depth_apply_ex(c->depth, 0);
|
||||
BCLS_DRAW(c->semi, c->depth, c->mask, c->check, c->b3d, glDrawArrays(GL_TRIANGLES, 0, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount, c->semi, c->mask);
|
||||
BCLS_DRAW(c->semi, c->depth, c->mask, c->check, c->b3d, glDrawArrays(GL_TRIANGLES, first, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount, c->semi, c->mask, first);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4158,8 +4251,9 @@ static void hiw_replay_wide(void) {
|
||||
* and each command's draw area (clipped to the tile) as the scissor. A tile no
|
||||
* command touches is not bound. */
|
||||
static void hiw_flush_tail(void);
|
||||
static void hiw_flush_queue(void) {
|
||||
static void hiw_flush_queue_timed_impl(void) {
|
||||
if (s_hq_n == 0) return;
|
||||
hiw_upload_queue();
|
||||
if (!hiw_on()) { /* native-wide mirrors only (wide_queue_live) */
|
||||
s_hq_flushes++;
|
||||
hiw_flush_tail();
|
||||
@@ -4204,11 +4298,10 @@ static void hiw_flush_queue(void) {
|
||||
}
|
||||
bind_textured_resources(c->tex, c->palette, c->hd_tex);
|
||||
p_glUniform1i(s_uMaskset, c->mask);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * TEXV * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
int first = hiw_upload_cmd(c);
|
||||
depth_apply_ex(c->depth, 1);
|
||||
tex_draw_passes_ex(c->vcount, c->semi, c->mask, c->check, 0, c->b3d, c->depth);
|
||||
depth_restore_ex(c->depth, 1, c->vcount, c->semi, c->mask);
|
||||
tex_draw_passes_ex(c->vcount, c->semi, c->mask, c->check, 0, c->b3d, c->depth, first);
|
||||
depth_restore_ex(c->depth, 1, c->vcount, c->semi, c->mask, first);
|
||||
} else {
|
||||
if (cur != HQ_GEO) {
|
||||
p_glUseProgram(s_geo_prog);
|
||||
@@ -4218,17 +4311,22 @@ static void hiw_flush_queue(void) {
|
||||
}
|
||||
if (c->semi >= 0) apply_psx_blend(c->semi); else glDisable(GL_BLEND);
|
||||
mask_stencil_ex(c->mask, c->check);
|
||||
p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)),
|
||||
s_hq_v + c->vfirst, PSXGL_STREAM_DRAW);
|
||||
int first = hiw_upload_cmd(c);
|
||||
depth_apply_ex(c->depth, 0);
|
||||
BCLS_DRAW(c->semi, c->depth, c->mask, c->check, c->b3d, glDrawArrays(GL_TRIANGLES, 0, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount, c->semi, c->mask);
|
||||
BCLS_DRAW(c->semi, c->depth, c->mask, c->check, c->b3d, glDrawArrays(GL_TRIANGLES, first, c->vcount));
|
||||
depth_restore_ex(c->depth, 0, c->vcount, c->semi, c->mask, first);
|
||||
}
|
||||
}
|
||||
}
|
||||
hiw_flush_tail();
|
||||
}
|
||||
|
||||
static void hiw_flush_queue(void) {
|
||||
uint64_t start = gl_cpu_begin(1);
|
||||
hiw_flush_queue_timed_impl();
|
||||
gl_cpu_end(1, start);
|
||||
}
|
||||
|
||||
/* The native-wide mirrors (both modes), then the canonical projection back
|
||||
* on both programs. */
|
||||
static void hiw_flush_tail(void) {
|
||||
@@ -4269,7 +4367,7 @@ static void flush_tex_batch_impl(void) {
|
||||
|
||||
depth_apply_ex(dmode, 1);
|
||||
tex_batch_draw_passes(nverts, semi);
|
||||
depth_restore_ex(dmode, 1, nverts, semi, s_tb_mask);
|
||||
depth_restore_ex(dmode, 1, nverts, semi, s_tb_mask, 0);
|
||||
|
||||
/* Native-wide mirror — skipped for a batch fully inside the 4:3 frame (its
|
||||
* centre content comes from the present-time canonical blit; nothing to add
|
||||
@@ -4298,7 +4396,7 @@ static void flush_tex_batch_impl(void) {
|
||||
wide_set_bd_scale(s_tex_uXscale, s_tex_uXcenter);
|
||||
depth_apply_ex(dmode, 1);
|
||||
if (s_ws_ablate != 2) tex_batch_draw_passes(nverts, semi);
|
||||
depth_restore_ex(dmode, 1, nverts, semi, s_tb_mask);
|
||||
depth_restore_ex(dmode, 1, nverts, semi, s_tb_mask, 0);
|
||||
wide_clear_bd_scale(s_tex_uXscale, s_tex_uXcenter);
|
||||
wide_target_end(s_tex_uXoff, s_tex_uXhalf);
|
||||
gl_perf_mirror_end();
|
||||
@@ -4410,7 +4508,7 @@ static void flush_flat_batch_impl(void) {
|
||||
depth_apply_ex(dmode, 0);
|
||||
BCLS_DRAW(semi, dmode, mask, s_mask_check, b3d, if (nl) flat_batch_draw_hr_lines(nverts, nl);
|
||||
else glDrawArrays(fmode, 0, nverts));
|
||||
depth_restore_ex(dmode, 0, nverts, semi, mask);
|
||||
depth_restore_ex(dmode, 0, nverts, semi, mask, 0);
|
||||
int mirror = g_wide_cur && !s_wide_suppress && s_ws_ablate != 1 &&
|
||||
!(!g_ws_bd_stretch_on && mirror_flat_batch_center_only(nverts));
|
||||
if (mirror) {
|
||||
@@ -4431,7 +4529,7 @@ static void flush_flat_batch_impl(void) {
|
||||
wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
depth_apply_ex(dmode, 0);
|
||||
if (s_ws_ablate != 2) BCLS_DRAW(semi, dmode, mask, s_mask_check, b3d, glDrawArrays(fmode, 0, nverts));
|
||||
depth_restore_ex(dmode, 0, nverts, semi, mask);
|
||||
depth_restore_ex(dmode, 0, nverts, semi, mask, 0);
|
||||
wide_clear_bd_scale(s_geo_uXscale, s_geo_uXcenter);
|
||||
wide_target_end(s_geo_uXoff, s_geo_uXhalf);
|
||||
gl_perf_mirror_end();
|
||||
@@ -5559,7 +5657,7 @@ static void glb_fill_rect(int x,int y,int w,int h,uint16_t c){
|
||||
if (!s_raster_ok) return;
|
||||
gpu_fill(x,y,w,h,c);
|
||||
}
|
||||
static void glb_copy_rect(int sx,int sy,int dx,int dy,int w,int h){
|
||||
static void glb_copy_rect_timed_impl(int sx,int sy,int dx,int dy,int w,int h){
|
||||
if (pass_refuse_write("copy", dx, dy, w, h)) return;
|
||||
if (cpu_raster_required()) {
|
||||
int faithful = native_draw_begin();
|
||||
@@ -5569,6 +5667,12 @@ static void glb_copy_rect(int sx,int sy,int dx,int dy,int w,int h){
|
||||
if (!s_raster_ok) return;
|
||||
gpu_copy_rect(sx,sy,dx,dy,w,h);
|
||||
}
|
||||
|
||||
static void glb_copy_rect(int sx,int sy,int dx,int dy,int w,int h) {
|
||||
uint64_t start = gl_cpu_begin(2);
|
||||
glb_copy_rect_timed_impl(sx,sy,dx,dy,w,h);
|
||||
gl_cpu_end(2, start);
|
||||
}
|
||||
static void glb_draw_textured_triangle(int x0,int y0,int u0,int v0,int x1,int y1,int u1,int v1,int x2,int y2,int u2,int v2,uint16_t cx,uint16_t cy,uint16_t tp){
|
||||
const int native_required = cpu_raster_required();
|
||||
if (native_required && !s_hd_native_authority)
|
||||
@@ -6702,9 +6806,25 @@ int gl_renderer_get_swap_interval(void) {
|
||||
static void pass_resources_release(void);
|
||||
|
||||
static void present_image_ring_gl_forget(void);
|
||||
void gl_renderer_work_stats(GlRenderWorkStats *out) {
|
||||
if (!out) return;
|
||||
GL_RT_SYNC("work_stats");
|
||||
*out = s_cpu_work;
|
||||
out->frequency = SDL_GetPerformanceFrequency();
|
||||
out->counts[0] = s_hq_uploads; out->counts[1] = s_hq_saved_uploads;
|
||||
out->counts[2] = s_bcls_fused_draws; out->counts[3] = s_coverage_depth_draws;
|
||||
out->counts[4] = s_hq_center_pruned;
|
||||
}
|
||||
void gl_renderer_shutdown(void) {
|
||||
gl_renderer_render_thread_stop(); /* drains; the context comes back to this thread */
|
||||
psx_host_set_hud_draw_cb(NULL, NULL);
|
||||
const char *perf = getenv("PSX_RUNTIME_PERF_DIAG");
|
||||
if (s_ctx && perf && perf[0] && perf[0] != '0')
|
||||
fprintf(stdout, "psxrecomp: GL work totals: queue_uploads=%llu replay_draws=%llu "
|
||||
"bloom_fused=%llu coverage_depth=%llu centre_pruned=%llu\n",
|
||||
(unsigned long long)s_hq_uploads, (unsigned long long)s_hq_saved_uploads,
|
||||
(unsigned long long)s_bcls_fused_draws, (unsigned long long)s_coverage_depth_draws,
|
||||
(unsigned long long)s_hq_center_pruned);
|
||||
/* 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();
|
||||
@@ -7903,7 +8023,7 @@ void gl_renderer_interpolation_diag(int *enabled, int *suspended,
|
||||
|
||||
/* Copy a stable display image out of the mutable VRAM/wide render target.
|
||||
* Returns true when temporal blending owns this source-frame interval. */
|
||||
static int interp_capture(GLuint fbo, int x, int y, int w, int h,
|
||||
static int interp_capture_timed_impl(GLuint fbo, int x, int y, int w, int h,
|
||||
int linear, int force_4_3, int source_path,
|
||||
int origin_x, int origin_y, int redrawn) {
|
||||
if (!s_interp_enabled || interp_suspended_now() || !fbo || w <= 0 || h <= 0) return 0;
|
||||
@@ -7980,6 +8100,15 @@ static int interp_capture(GLuint fbo, int x, int y, int w, int h,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int interp_capture(GLuint fbo, int x, int y, int w, int h,
|
||||
int linear, int force_4_3, int source_path,
|
||||
int origin_x, int origin_y, int redrawn) {
|
||||
uint64_t start = gl_cpu_begin(3);
|
||||
int result = interp_capture_timed_impl(fbo,x,y,w,h,linear,force_4_3,source_path,origin_x,origin_y,redrawn);
|
||||
gl_cpu_end(3, start);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void interp_draw_textures(GLuint prev_tex, GLuint curr_tex, float alpha,
|
||||
int blend_mode, int lx, int ly, int lw, int lh) {
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
@@ -8075,7 +8204,7 @@ static void interp_wait_until(uint64_t deadline, uint64_t frequency) {
|
||||
}
|
||||
}
|
||||
|
||||
static void interp_present_source_interval(void) {
|
||||
static void interp_present_source_interval_timed_impl(void) {
|
||||
static uint64_t diag_start, diag_swaps, diag_captures;
|
||||
uint64_t frequency = SDL_GetPerformanceFrequency();
|
||||
uint64_t now = SDL_GetPerformanceCounter();
|
||||
@@ -8120,6 +8249,12 @@ static void interp_present_source_interval(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void interp_present_source_interval(void) {
|
||||
uint64_t start = gl_cpu_begin(4);
|
||||
interp_present_source_interval_timed_impl();
|
||||
gl_cpu_end(4, start);
|
||||
}
|
||||
|
||||
/* ==== Render passes (render_pass.c, docs/RENDER_PASSES.md) ==================
|
||||
*
|
||||
* VRAM transaction. A pass draws into the guest's own display rect, which
|
||||
@@ -10557,7 +10692,7 @@ static void present_vram_impl(int disp_x, int disp_y, int w, int h, int linear,
|
||||
* produced by the mirror; this leaves them untouched. One FBO->FBO blit,
|
||||
* x-translated by the reveal offset. No-op when s_wide_fast is off (then the
|
||||
* mirror drew the full surface, as before). Shared by both present paths. */
|
||||
static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h) {
|
||||
static void wide_blit_center_timed_impl(GLuint wide_fbo, int base_x, int disp_y, int disp_h) {
|
||||
if (!wide_center_is_canonical() || g_wide_w <= 0) return;
|
||||
int native_w = g_wide_w - 2 * g_wide_off;
|
||||
if (native_w <= 0) return;
|
||||
@@ -10622,6 +10757,12 @@ static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
static void wide_blit_center(GLuint wide_fbo, int base_x, int disp_y, int disp_h) {
|
||||
uint64_t start = gl_cpu_begin(5);
|
||||
wide_blit_center_timed_impl(wide_fbo,base_x,disp_y,disp_h);
|
||||
gl_cpu_end(5, start);
|
||||
}
|
||||
|
||||
/* GPU-direct native-wide present: blit the displayed buffer's wide FBO straight
|
||||
* to the window (no glReadPixels / glFinish CPU round-trip). The wide surface is
|
||||
* g_wide_w wide × VRAM_H tall (at scale S); present its [0,g_wide_w] × [disp_y,
|
||||
|
||||
@@ -5065,6 +5065,32 @@ static void input_replay_frontend_tick(uint64_t frame) {
|
||||
s.dur_us, s.frame, host_span_kind_name(s.kind), s.emu, s.arg);
|
||||
}
|
||||
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay span report");
|
||||
if (g_gl_active) {
|
||||
char thread_json[8192];
|
||||
int length = gl_renderer_render_thread_json(thread_json, sizeof thread_json);
|
||||
if (length < 0 || length >= (int)sizeof thread_json)
|
||||
throw std::runtime_error("render thread report exceeds its buffer");
|
||||
std::string thread_path = std::string(report) + ".rth.json";
|
||||
f = std::fopen(thread_path.c_str(), "w");
|
||||
if (!f) throw std::runtime_error("cannot write input replay render thread report");
|
||||
std::fprintf(f, "{%.*s}\n", length, thread_json);
|
||||
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay render thread report");
|
||||
GlRenderWorkStats stats{};
|
||||
gl_renderer_work_stats(&stats);
|
||||
std::string path = std::string(report) + ".render.csv";
|
||||
f = std::fopen(path.c_str(), "w");
|
||||
if (!f) throw std::runtime_error("cannot write input replay render report");
|
||||
std::fputs("stat,count,total_us,max_us\n", f);
|
||||
const char *counts[5] = {"queue_uploads", "replay_draws", "bloom_fused", "coverage_depth", "centre_pruned"};
|
||||
for (int i=0; i<5; ++i) std::fprintf(f, "%s,%llu,0,0\n", counts[i], (unsigned long long)stats.counts[i]);
|
||||
const char *spans[6] = {"depth_restore", "queue_replay", "vram_copy", "interp_capture", "interp_interval", "centre_copy"};
|
||||
double us = stats.frequency ? 1e6 / stats.frequency : 0;
|
||||
for (int i=0; i<6; ++i) std::fprintf(f, "%s,%llu,%.3f,%.3f\n", spans[i],
|
||||
(unsigned long long)stats.calls[i], stats.ticks[i]*us, stats.max_ticks[i]*us);
|
||||
for (int i=0; i<6; ++i) std::fprintf(f, "%s_prior_drain,%llu,%.3f,%.3f\n", spans[i],
|
||||
(unsigned long long)stats.calls[i], stats.prior_ticks[i]*us, stats.prior_max_ticks[i]*us);
|
||||
if (std::fclose(f) != 0) throw std::runtime_error("cannot finish input replay render report");
|
||||
}
|
||||
double gpu[18];
|
||||
if (g_gl_active && gl_renderer_perf_aggregate(-1, gpu) > 0) {
|
||||
std::string path = std::string(report) + ".gpu.csv";
|
||||
|
||||
@@ -157,11 +157,13 @@ def main():
|
||||
ok = False
|
||||
runs[threaded] = p
|
||||
if args.wide_present:
|
||||
for margin, coverage, state in ((0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 0, 1)):
|
||||
for margin, coverage, state, upload, bloom in ((0, 0, 0, 0, 0), (0, 0, 1, 1, 1), (0, 1, 1, 1, 1), (1, 0, 1, 1, 1), (1, 1, 1, 0, 1), (1, 1, 1, 1, 0)):
|
||||
reference = run([probe, s, threaded, args.frames, *mode],
|
||||
{"PSX_GL_WIDE_MARGIN_SCISSOR": str(margin),
|
||||
"PSX_GL_COVERAGE_FAST": str(coverage),
|
||||
"PSX_GL_STATE_QUERY_FAST": str(state)})
|
||||
"PSX_GL_STATE_QUERY_FAST": str(state),
|
||||
"PSX_GL_QUEUE_UPLOAD_FAST": str(upload),
|
||||
"PSX_GL_BLOOM_FUSE_EXTEND": str(bloom)})
|
||||
baseline = parse(reference.stdout)
|
||||
if reference.returncode or baseline["failures"] != 0 or any(baseline[k] != p[k] for k in KEYS):
|
||||
print(f"FAIL scale {s}: margin={margin} coverage={coverage} changed rendered output", baseline, p)
|
||||
|
||||
@@ -462,6 +462,11 @@ int main(int argc, char **argv) {
|
||||
check(s_hq_center_pruned > 0, "queued centre shading was elided");
|
||||
printf("pruned_cmds=%llu\n", (unsigned long long)s_hq_center_pruned);
|
||||
}
|
||||
if (wide_present && hiw_upload_fast()) {
|
||||
check(s_hq_saved_uploads > s_hq_uploads, "queue uploaded once for multiple replay draws");
|
||||
printf("queue_uploads=%llu replay_draws=%llu\n", (unsigned long long)s_hq_uploads,
|
||||
(unsigned long long)s_hq_saved_uploads);
|
||||
}
|
||||
if (quality_scene && coverage_fast_enabled()) {
|
||||
check(s_bcls_fused_draws > 0, "bloom stencil fused with colour draws");
|
||||
printf("fused_draws=%llu\n", (unsigned long long)s_bcls_fused_draws);
|
||||
|
||||
Reference in New Issue
Block a user