mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
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:
@@ -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;
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user