diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index e7a59670..32a7c872 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -460,7 +460,7 @@ struct RuntimeConfig { float video_bloom = 0.0f; // post_shader: a post-effect chain (.pfx, docs/POST_EFFECTS.md), "" = none. // Relative paths resolve against the game root. - std::string video_post_shader; + std::filesystem::path video_post_shader; // supersample: factor on the internal-resolution target (Match display or // a line preset), 1.0..4.0, kept in thousandths (internal_resolution.h). int video_supersample_milli = 1000; diff --git a/runtime/include/gpu_gl_renderer.h b/runtime/include/gpu_gl_renderer.h index b8550eeb..036cee25 100644 --- a/runtime/include/gpu_gl_renderer.h +++ b/runtime/include/gpu_gl_renderer.h @@ -199,7 +199,10 @@ typedef struct { float strength, threshold; int view; uint64_t passes, us_n; - double us_mean; /* glFinish-bracketed pass cost while timing, then reset */ + double us_mean; /* glFinish-bracketed pass cost on real frames while timing, then reset */ + double gen_us_mean; /* the same on Smooth motion generated frames */ + uint64_t gen_us_n; + uint64_t reused; /* passes generated frames took from the last real frame */ } GlBloomInfo; /* Debug: negative arguments keep the current value. view 0 normal, 1 bloom * only, 2 split (stock | bloom). */ diff --git a/runtime/include/postfx_chain.h b/runtime/include/postfx_chain.h index cc9cd8c7..53a24c55 100644 --- a/runtime/include/postfx_chain.h +++ b/runtime/include/postfx_chain.h @@ -14,6 +14,9 @@ * format rgba8|rgba16f # target format (default rgba8) * filter linear|nearest # how later passes sample this target (default linear) * uniform [f [f [f]]] # float, vec2, vec3 or vec4 + * generated reuse|run # Smooth motion's generated frames: reuse this + * # pass's target from the last real frame (not + * # for the last pass), or run it (default) * * A shader body is GLSL 330 appended to a header that declares * in vec2 v_uv; out vec4 o; @@ -50,6 +53,7 @@ typedef struct { char *shader; /* fragment body (owned) */ float scale; int format, nearest; + int reuse_generated; /* generated frames reuse the last real frame's target */ int ninputs, nuniforms; PfxInput in[PFX_MAX_INPUTS]; PfxUniform u[PFX_MAX_UNIFORMS]; diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 63d31a1e..e5625db8 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -8612,9 +8612,11 @@ static void handle_bloom(int id, const char *json) gl_renderer_bloom_debug(strength, threshold, json_get_int(json, "view", -1), json_get_int(json, "time", -1), &bi); send_fmt("{\"id\":%d,\"ok\":true,\"strength\":%.3f,\"threshold\":%.3f,\"view\":%d," - "\"passes\":%llu,\"timed\":%llu,\"us_mean\":%.1f}", + "\"passes\":%llu,\"timed\":%llu,\"us_mean\":%.1f,\"gen_timed\":%llu," + "\"gen_us_mean\":%.1f,\"reused\":%llu}", id, bi.strength, bi.threshold, bi.view, (unsigned long long)bi.passes, - (unsigned long long)bi.us_n, bi.us_mean); + (unsigned long long)bi.us_n, bi.us_mean, (unsigned long long)bi.gen_us_n, + bi.gen_us_mean, (unsigned long long)bi.reused); } static void handle_post_aa(int id, const char *json) diff --git a/runtime/src/gl_postfx.inc b/runtime/src/gl_postfx.inc index aa1a8f77..81359739 100644 --- a/runtime/src/gl_postfx.inc +++ b/runtime/src/gl_postfx.inc @@ -45,7 +45,9 @@ static float s_bloom_thr = 0.55f; /* bloom bright-pass threshold */ static int s_bloom_view = 0; /* debug: 1 bloom only, 2 split */ static uint64_t s_bloom_passes = 0; static int s_bloom_time = 0; /* debug: glFinish-bracketed cost */ -static double s_bloom_us_sum = 0.0; static uint64_t s_bloom_us_n = 0; +static double s_bloom_us_sum = 0.0; static uint64_t s_bloom_us_n = 0; /* real frames */ +static double s_bloom_gus_sum = 0.0; static uint64_t s_bloom_gus_n = 0; /* generated frames */ +static uint64_t s_pfx_reused = 0; /* passes skipped on generated frames */ /* The mask (see above). */ static GLuint s_mask_src_fbo = 0; @@ -201,8 +203,11 @@ static int pfx_mask_build(int fresh, int lw, int lh) { return s_mask_ok; } -/* Run one stage's chain over the window rect. 0 when it could not run. */ -static int pfx_stage_run(PfxStage *st, int lx, int ly, int lw, int lh) { +/* Run one stage's chain over the window rect. 0 when it could not run. + * generated: a Smooth motion in-between frame; passes marked `generated + * reuse` keep the target the last real frame left (when it is there at this + * size) instead of running. */ +static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, int lh) { PfxChain *c = &st->chain; post_copy_bind(lw, lh); post_copy_window(lx, ly, lw, lh); @@ -223,6 +228,10 @@ static int pfx_stage_run(PfxStage *st, int lx, int ly, int lw, int lh) { w = (int)((float)lw * ps->scale); h = (int)((float)lh * ps->scale); if (w < 1) w = 1; if (h < 1) h = 1; + if (generated && ps->reuse_generated && st->fbo[i] && st->tw[i] == w && st->th[i] == h) { + s_pfx_reused++; + continue; + } if (st->tw[i] != w || st->th[i] != h) { pfx_tex_alloc(&st->tex[i], w, h, ps->format, ps->nearest); pfx_fbo_attach(&st->fbo[i], st->tex[i]); @@ -299,7 +308,7 @@ static void pfx_state_restore(const PfxGlState *st) { } /* fresh: the noted source is this present's (0: reuse the last mask). - * with_aa: run the AA stage too (real frames). */ + * with_aa: a real frame (runs AA too); 0: a Smooth motion generated frame. */ static void postfx_present(int fresh, int with_aa, int lx, int ly, int lw, int lh) { const int src_fresh = fresh; if (!s_ctx) { s_mask_src_valid = 0; return; } @@ -335,13 +344,14 @@ static void postfx_present(int fresh, int with_aa, int lx, int ly, int lw, int l } if (s_paa_time) { reps = s_paa_time > 1 ? s_paa_time : 1; sum = &s_paa_gpu_us_sum; cnt = &s_paa_gpu_n; } } else if (i == PFX_STAGE_BLOOM && s_bloom_time) { - sum = &s_bloom_us_sum; cnt = &s_bloom_us_n; + sum = with_aa ? &s_bloom_us_sum : &s_bloom_gus_sum; + cnt = with_aa ? &s_bloom_us_n : &s_bloom_gus_n; } uint64_t t0 = 0; if (sum) { glFinish(); t0 = SDL_GetPerformanceCounter(); } #endif int ran = 0; - for (int r = 0; r < reps; r++) ran = pfx_stage_run(st, lx, ly, lw, lh); + for (int r = 0; r < reps; r++) ran = pfx_stage_run(st, !with_aa, lx, ly, lw, lh); #ifndef PSX_NO_DEBUG_TOOLS if (sum && ran) { glFinish(); @@ -418,6 +428,9 @@ void gl_renderer_bloom_debug(float strength, float threshold, int view, int time out->passes = s_bloom_passes; out->us_mean = s_bloom_us_n ? s_bloom_us_sum / (double)s_bloom_us_n : 0.0; out->us_n = s_bloom_us_n; - s_bloom_us_sum = 0.0; s_bloom_us_n = 0; + out->gen_us_mean = s_bloom_gus_n ? s_bloom_gus_sum / (double)s_bloom_gus_n : 0.0; + out->gen_us_n = s_bloom_gus_n; + out->reused = s_pfx_reused; + s_bloom_us_sum = 0.0; s_bloom_us_n = 0; s_bloom_gus_sum = 0.0; s_bloom_gus_n = 0; } } diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index fb8cc72e..fbc3ffb0 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -3540,21 +3540,14 @@ void gl_renderer_batch_diag(uint64_t out[9]) { * painter ordered like the colour; semi-transparent texels and primitives * leave it as it was. Bloom off: none of this runs and no state changes. */ static GLint s_tex_uBcls = -1, s_geo_uBcls = -1, s_geo_uBadd = -1; -typedef struct { - GLboolean st, bl, dm, cm[4]; - GLint func, ref, vmask, wmask, sf, zf, zp; -} BclsSave; -static void bcls_begin(BclsSave *v, int tex, int additive) { - v->st = glIsEnabled(GL_STENCIL_TEST); v->bl = glIsEnabled(GL_BLEND); - glGetBooleanv(GL_DEPTH_WRITEMASK, &v->dm); - glGetBooleanv(GL_COLOR_WRITEMASK, v->cm); - glGetIntegerv(GL_STENCIL_FUNC, &v->func); glGetIntegerv(GL_STENCIL_REF, &v->ref); - glGetIntegerv(GL_STENCIL_VALUE_MASK, &v->vmask); glGetIntegerv(GL_STENCIL_WRITEMASK, &v->wmask); - glGetIntegerv(GL_STENCIL_FAIL, &v->sf); glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &v->zf); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &v->zp); +/* No glGet here: on a threaded driver every query is a round trip, and these + * run per batch. The passes set what they need; the caller's draw state is + * then put back from what the caller knows (bcls_end_geo / tex_bcls_end). + * Depth writes need no care: mode-1 batches already have them off and + * mode-0 batches have no depth test; mode-2 (depth clear) draws mark nothing. */ +static void bcls_begin(int tex, int additive) { glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glDisable(GL_BLEND); - glDepthMask(GL_FALSE); glEnable(GL_STENCIL_TEST); glStencilMask(0x02); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); @@ -3565,34 +3558,25 @@ static void bcls_pass(int tex, int pass) { glStencilFunc(GL_ALWAYS, pass == 1 ? 0x02 : 0x00, 0xFF); p_glUniform1i(tex ? s_tex_uBcls : s_geo_uBcls, pass); } -static void bcls_end(const BclsSave *v, int tex) { - p_glUniform1i(tex ? s_tex_uBcls : s_geo_uBcls, 0); - if (!tex) p_glUniform1i(s_geo_uBadd, 0); - glColorMask(v->cm[0], v->cm[1], v->cm[2], v->cm[3]); - if (v->bl) glEnable(GL_BLEND); - glDepthMask(v->dm); - glStencilFunc((GLenum)v->func, v->ref, (GLuint)v->vmask); - glStencilMask((GLuint)v->wmask); - glStencilOp((GLenum)v->sf, (GLenum)v->zf, (GLenum)v->zp); - if (!v->st) glDisable(GL_STENCIL_TEST); +/* Back to an untextured draw's state: its blend and mask-bit stencil. */ +static void bcls_end_geo(int semi, int mask, int check) { + p_glUniform1i(s_geo_uBcls, 0); + p_glUniform1i(s_geo_uBadd, 0); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if (semi >= 0) apply_psx_blend(semi); else glDisable(GL_BLEND); + mask_stencil_ex(mask, check); } -/* A draw with colour writes off (the PGXP depth-clear pass) draws nothing - * one sees, so it marks nothing either. */ -static int bcls_colour_draw(void) { - GLboolean cm[4]; glGetBooleanv(GL_COLOR_WRITEMASK, cm); - return cm[0] || cm[1] || cm[2]; -} -/* DRAW, then (bloom on) its two marking passes. GEO semi modes 1 and 3 add. - * A semi-transparent primitive does not clear: what shows through it (a lamp - * under a fog or shade quad) keeps glowing. */ -#define BCLS_DRAW(tex, semi, DRAW) do { \ +/* DRAW (an untextured draw set up with blend `semi`, mask_stencil_ex(mask, + * check) and PGXP depth mode `dmode`), then (mask on) its two marking passes. + * GEO semi modes 1 and 3 add. A semi-transparent primitive does not clear: + * what shows through it (a lamp under a fog or shade quad) keeps glowing. */ +#define BCLS_DRAW(semi, dmode, mask, check, DRAW) do { \ DRAW; \ - if (s_bcls_on && bcls_colour_draw()) { \ - BclsSave bcls_sv_; \ - bcls_begin(&bcls_sv_, (tex), (semi) == 1 || (semi) == 3); \ - if ((semi) < 0) { bcls_pass((tex), 2); DRAW; } \ - bcls_pass((tex), 1); DRAW; \ - bcls_end(&bcls_sv_, (tex)); \ + if (s_bcls_on && (dmode) != 2) { \ + bcls_begin(0, (semi) == 1 || (semi) == 3); \ + if ((semi) < 0) { bcls_pass(0, 2); DRAW; } \ + bcls_pass(0, 1); DRAW; \ + bcls_end_geo((semi), (mask), (check)); \ } \ } while (0) @@ -3643,11 +3627,27 @@ static void tex_draw_passes_ex(int nverts, int semi, int tb_mask, int check, glDrawArrays(GL_TRIANGLES, 0, nverts); } if (s_bcls_on) { - BclsSave sv; - bcls_begin(&sv, 1, 0); + bcls_begin(1, 0); bcls_pass(1, 2); glDrawArrays(GL_TRIANGLES, 0, nverts); bcls_pass(1, 1); glDrawArrays(GL_TRIANGLES, 0, nverts); - bcls_end(&sv, 1); + /* Back to the state the branch above ended in. */ + p_glUniform1i(s_tex_uBcls, 0); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if (semi < 0) { + glDisable(GL_BLEND); + mask_stencil_ex(check ? 1 : tb_mask, check); + p_glUniform1i(s_uSemipass, check ? 2 : 0); + } else if (!check && semi == 4) { + glEnable(GL_BLEND); + p_glBlendEquationSeparate(PSXGL_FUNC_ADD, PSXGL_FUNC_ADD); + 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); + } else { + apply_psx_blend(semi); + mask_stencil_ex(1, check); + p_glUniform1i(s_uSemipass, 2); + } } } static void tex_batch_draw_passes(int nverts, int semi) { @@ -3894,7 +3894,7 @@ static void hiw_replay_wide(void) { p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)), s_hq_v + c->vfirst, PSXGL_STREAM_DRAW); depth_apply_ex(c->depth, 0); - BCLS_DRAW(0, c->semi, glDrawArrays(GL_TRIANGLES, 0, c->vcount)); + BCLS_DRAW(c->semi, c->depth, c->mask, c->check, glDrawArrays(GL_TRIANGLES, 0, c->vcount)); depth_restore_ex(c->depth, 0, c->vcount); } } @@ -3975,7 +3975,7 @@ static void hiw_flush_queue(void) { p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((size_t)c->vcount * 6 * sizeof(float)), s_hq_v + c->vfirst, PSXGL_STREAM_DRAW); depth_apply_ex(c->depth, 0); - BCLS_DRAW(0, c->semi, glDrawArrays(GL_TRIANGLES, 0, c->vcount)); + BCLS_DRAW(c->semi, c->depth, c->mask, c->check, glDrawArrays(GL_TRIANGLES, 0, c->vcount)); depth_restore_ex(c->depth, 0, c->vcount); } } @@ -4161,7 +4161,7 @@ static void flush_flat_batch_impl(void) { p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)((nverts + 2 * nl) * 6 * sizeof(float)), s_fb, PSXGL_STREAM_DRAW); depth_apply_ex(dmode, 0); - BCLS_DRAW(0, semi, if (nl) flat_batch_draw_hr_lines(nverts, nl); + BCLS_DRAW(semi, dmode, mask, s_mask_check, if (nl) flat_batch_draw_hr_lines(nverts, nl); else glDrawArrays(fmode, 0, nverts)); depth_restore_ex(dmode, 0, nverts); int mirror = g_wide_cur && !s_wide_suppress && s_ws_ablate != 1 && @@ -4183,7 +4183,7 @@ static void flush_flat_batch_impl(void) { wide_target_begin(dx, s_geo_uXoff, s_geo_uXhalf); wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter); depth_apply_ex(dmode, 0); - if (s_ws_ablate != 2) BCLS_DRAW(0, semi, glDrawArrays(fmode, 0, nverts)); + if (s_ws_ablate != 2) BCLS_DRAW(semi, dmode, mask, s_mask_check, glDrawArrays(fmode, 0, nverts)); depth_restore_ex(dmode, 0, nverts); wide_clear_bd_scale(s_geo_uXscale, s_geo_uXcenter); wide_target_end(s_geo_uXoff, s_geo_uXhalf); @@ -4540,7 +4540,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys, p_glBindBuffer(PSXGL_ARRAY_BUFFER, s_geo_vbo); p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)(draw_n * 6 * sizeof(float)), draw_verts, PSXGL_STREAM_DRAW); - BCLS_DRAW(0, semi, glDrawArrays(draw_mode, 0, draw_n)); + BCLS_DRAW(semi, 0, (int)s_mask_set, s_mask_check, glDrawArrays(draw_mode, 0, draw_n)); if (is_line && s_out_scale > 1 && draw_mode == GL_LINES) { /* windowed: hr drew a 1x line; the S surfaces take the quad */ p_glBufferData(PSXGL_ARRAY_BUFFER, (ptrdiff_t)(6 * 6 * sizeof(float)), @@ -4564,7 +4564,7 @@ static void gpu_geometry(GLenum mode, const int *xs, const int *ys, gl_perf_mirror_begin(); wide_target_begin(dx, s_geo_uXoff, s_geo_uXhalf); wide_set_bd_scale(s_geo_uXscale, s_geo_uXcenter); - if (s_ws_ablate != 2) BCLS_DRAW(0, semi, glDrawArrays(draw_mode, 0, draw_n)); + if (s_ws_ablate != 2) BCLS_DRAW(semi, 0, (int)s_mask_set, s_mask_check, glDrawArrays(draw_mode, 0, draw_n)); wide_clear_bd_scale(s_geo_uXscale, s_geo_uXcenter); wide_target_end(s_geo_uXoff, s_geo_uXhalf); gl_perf_mirror_end(); @@ -4841,7 +4841,7 @@ static void wide_flat_rect_direct(int wx, int y, int ww, int h, uint16_t c, int p_glBindVertexArray(s_geo_vao); p_glBindBuffer(PSXGL_ARRAY_BUFFER, s_geo_vbo); p_glBufferData(PSXGL_ARRAY_BUFFER, sizeof verts, verts, PSXGL_STREAM_DRAW); - BCLS_DRAW(0, semi, glDrawArrays(GL_TRIANGLES, 0, 6)); + BCLS_DRAW(semi, 0, (int)s_mask_set, s_mask_check, glDrawArrays(GL_TRIANGLES, 0, 6)); if (s_hiw) p_glUniform1f(s_geo_uShift, s_shift_hr); p_glUniform1f(s_geo_uXoff, 0.0f); p_glUniform1f(s_geo_uXhalf, 512.0f); diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 88fa5afb..207e7881 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -16335,7 +16335,10 @@ int main(int argc, char** argv) { g_video_aa = gc.runtime.video_antialiasing; g_video_post_aa = gc.runtime.video_antialiasing_mode; g_video_bloom = gc.runtime.video_bloom; - g_video_post_shader = gc.runtime.video_post_shader; + { /* UTF-8 bytes on every host (C++17: string, C++20: u8string) */ + const auto u8 = gc.runtime.video_post_shader.u8string(); + g_video_post_shader.assign(u8.begin(), u8.end()); + } g_video_ss_milli = gc.runtime.video_supersample_milli; g_video_texfilter = gc.runtime.video_texture_filter; g_video_fmv_filter = gc.runtime.video_fmv_filter; diff --git a/runtime/src/postfx_chain.c b/runtime/src/postfx_chain.c index 0f775a21..0f63a6ac 100644 --- a/runtime/src/postfx_chain.c +++ b/runtime/src/postfx_chain.c @@ -120,18 +120,21 @@ const char *pfx_builtin_shader(const char *name) { static const char BLOOM_PFX[] = "pfx 1\n" "# [video] bloom: glow from 3D and additive primitives only (the mask).\n" + "# Smooth motion's generated frames reuse the real frame's glow (bright and\n" + "# blur) and run only the masked composite over their own picture.\n" "pass bright\n" " shader builtin:bloom_bright\n" " input u_tex source\n" " input u_mask mask\n" " scale 0.25\n format rgba16f\n" " uniform u_thr 0.55\n" - "pass h1\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 1 0\n" - "pass v1\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 0 1\n" - "pass h2\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 2 0\n" - "pass v2\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 0 2\n" - "pass h3\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 3 0\n" - "pass v3\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n uniform u_dir 0 3\n" + " generated reuse\n" + "pass h1\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 1 0\n" + "pass v1\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 0 1\n" + "pass h2\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 2 0\n" + "pass v2\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 0 2\n" + "pass h3\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 3 0\n" + "pass v3\n shader builtin:blur\n input u_tex previous\n scale 0.25\n format rgba16f\n generated reuse\n uniform u_dir 0 3\n" "pass composite\n" " shader builtin:bloom_composite\n" " input u_tex source\n" @@ -262,6 +265,11 @@ int pfx_chain_parse(const char *text, const char *name, PfxShaderLoader load, vo if (!strcmp(v, "linear")) cur->nearest = 0; else if (!strcmp(v, "nearest")) cur->nearest = 1; else { pfx_chain_free(out); return pfx_fail(err, errlen, line, "filter must be linear or nearest"); } + } else if (!strcmp(kw, "generated")) { + char v[16]; pfx_word(&q, v, sizeof v); + if (!strcmp(v, "reuse")) cur->reuse_generated = 1; + else if (!strcmp(v, "run")) cur->reuse_generated = 0; + else { pfx_chain_free(out); return pfx_fail(err, errlen, line, "generated must be reuse or run"); } } else if (!strcmp(kw, "uniform")) { if (cur->nuniforms == PFX_MAX_UNIFORMS) { pfx_chain_free(out); return pfx_fail(err, errlen, line, "more than %d uniforms", PFX_MAX_UNIFORMS); } PfxUniform *u = &cur->u[cur->nuniforms]; @@ -284,8 +292,9 @@ int pfx_chain_parse(const char *text, const char *name, PfxShaderLoader load, vo if (!out->npasses) return pfx_fail(err, errlen, line, "no passes"); for (int i = 0; i < out->npasses; i++) if (!out->pass[i].shader) { int n = out->npasses; snprintf(err, (size_t)errlen, "pass '%s' has no shader", out->pass[i].name); (void)n; pfx_chain_free(out); return 0; } - /* The window pass draws over the game rect at its own size. */ + /* The window pass draws over the game rect at its own size, every time. */ out->pass[out->npasses - 1].scale = 1.0f; + out->pass[out->npasses - 1].reuse_generated = 0; return 1; } diff --git a/runtime/tests/test_postfx_chain.c b/runtime/tests/test_postfx_chain.c index b97bba3f..e0db8ed9 100644 --- a/runtime/tests/test_postfx_chain.c +++ b/runtime/tests/test_postfx_chain.c @@ -24,6 +24,8 @@ int main(void) { check(c.pass[0].scale == 0.25f && c.pass[0].format == PFX_FMT_RGBA16F, "bloom: quarter-res float bright pass"); check(c.pass[7].ninputs == 3 && c.pass[7].in[1].from == 6 && c.pass[7].in[2].from == PFX_IN_MASK, "bloom: composite reads source, previous and mask"); + check(c.pass[0].reuse_generated && c.pass[6].reuse_generated && !c.pass[7].reuse_generated, + "bloom: generated frames reuse bright/blur, run the composite"); const float k = 1.7f; check(pfx_chain_set_uniform(&c, "u_strength", 1, &k) == 1 && c.pass[7].u[0].v[0] == 1.7f, "uniform override"); pfx_chain_free(&c); @@ -34,10 +36,11 @@ int main(void) { check(!pfx_chain_builtin("nope", &c, err, sizeof err), "unknown preset refused"); check(parse("pfx 1\n# c\npass a\n shader builtin:blur\n input t source\n scale 0.5\n filter nearest\n" - " uniform u_dir 1 2\npass b\n shader builtin:blur\n input t a\n input m mask\n", &c, err), + " uniform u_dir 1 2\n generated reuse\npass b\n generated reuse\n shader builtin:blur\n input t a\n input m mask\n", &c, err), "custom chain parses"); check(c.pass[0].nearest && c.pass[0].u[0].count == 2 && c.pass[1].in[0].from == 0 && - c.pass[1].scale == 1.0f && c.uses_mask, "custom chain fields"); + c.pass[1].scale == 1.0f && c.uses_mask && c.pass[0].reuse_generated && + !c.pass[1].reuse_generated, "custom chain fields (the window pass always runs)"); pfx_chain_free(&c); static const struct { const char *text, *want; } bad[] = { @@ -53,6 +56,7 @@ int main(void) { { "pfx 1\npass a\npass a\n", "line 3: duplicate pass 'a'" }, { "pfx 1\npass a\n input t source\n", "pass 'a' has no shader" }, { "pfx 1\npass a\n wobble 1\n", "line 3: unknown directive 'wobble'" }, + { "pfx 1\npass a\n generated maybe\n", "line 3: generated must be reuse or run" }, }; for (size_t i = 0; i < sizeof bad / sizeof bad[0]; i++) { int ok = parse(bad[i].text, &c, err);