post effects: MinGW build fix, generated frames reuse bloom's glow, no glGet in marking

- config: [video] post_shader is a std::filesystem::path (like memcard_dir);
  the frontend turns it into UTF-8 bytes for the GL API. Assigning the path
  to a std::string did not compile on MinGW.
- .pfx `generated reuse`: a Smooth motion generated frame keeps that pass's
  target from the last real frame. The bloom preset marks its bright and
  blur passes so; generated frames run only the masked composite (over their
  own picture and mask). bloom debug reports real/generated pass times and
  reused passes.
- Mask marking no longer queries GL state per batch: each call site puts
  back the blend and mask-bit stencil it set (no driver round trips on the
  render thread).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-13 01:25:23 -07:00
parent 74dadc6004
commit 18e10693c4
9 changed files with 108 additions and 70 deletions
+1 -1
View File
@@ -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;
+4 -1
View File
@@ -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). */
+4
View File
@@ -14,6 +14,9 @@
* format rgba8|rgba16f # target format (default rgba8)
* filter linear|nearest # how later passes sample this target (default linear)
* uniform <name> <f> [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];
+4 -2
View File
@@ -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)
+20 -7
View File
@@ -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;
}
}
+49 -49
View File
@@ -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);
+4 -1
View File
@@ -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;
+16 -7
View File
@@ -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;
}
+6 -2
View File
@@ -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);