mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
post effects: no per-frame GL queries; CRLF presets tested
- Uniform locations are looked up once per program (each lookup can be a driver round trip on a threaded GL; the executor runs per present, generated frames included). - State save queries only capability flags and the viewport. The mask pass no longer clears (it draws 0 then 1), so the clear colour is never touched; the present's window target and texture unit 0 are left bound. The GL fixture checks exactly that contract. - postfx_chain_test: a preset with CRLF line ends parses the same, and the import check writes its shader in binary mode (it failed on Windows). - docs/POST_EFFECTS.md: `generated reuse`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,10 @@ a fresh copy of the image:
|
||||
|
||||
They run on whichever thread presents (the render thread included), before
|
||||
hold-last capture, screenshots and the OSD. Smooth motion's generated frames
|
||||
run bloom and the user chain; AA stays on real frames, as before. The executor
|
||||
run bloom and the user chain; AA stays on real frames, as before. Passes
|
||||
marked `generated reuse` (bloom's bright and blur passes) are not run again
|
||||
on generated frames: those reuse the last real frame's result and run only
|
||||
the rest (bloom's masked composite, over their own picture and mask). The executor
|
||||
owns targets, programs, uniforms and GL state, and puts back everything it
|
||||
touches.
|
||||
|
||||
@@ -30,6 +33,8 @@ pass <name> # the last pass writes the window
|
||||
format rgba8|rgba16f # default rgba8
|
||||
filter linear|nearest # how later passes sample this target
|
||||
uniform <name> <f> [f [f [f]]] # float .. vec4
|
||||
generated reuse|run # Smooth motion generated frames: keep this pass's
|
||||
# target from the last real frame (default: run)
|
||||
```
|
||||
|
||||
A shader file is a GLSL 330 fragment body. It is appended to this header:
|
||||
|
||||
+46
-21
@@ -29,10 +29,19 @@
|
||||
* source); runtime/tests/test_postfx_chain.c builds it alone. */
|
||||
#include "postfx_chain.c"
|
||||
|
||||
/* Uniform locations, looked up once per program (a lookup per frame is a
|
||||
* driver round trip on a threaded GL). */
|
||||
enum { PFX_LOC_RCP, PFX_LOC_SIZE, PFX_LOC_ORG, PFX_LOC_UV, PFX_LOC_FIXED };
|
||||
typedef struct {
|
||||
GLint fixed[PFX_LOC_FIXED];
|
||||
GLint in[PFX_MAX_INPUTS];
|
||||
GLint u[PFX_MAX_UNIFORMS];
|
||||
} PfxLocs;
|
||||
typedef struct {
|
||||
PfxChain chain;
|
||||
int loaded, failed;
|
||||
GLuint prog[PFX_MAX_PASSES];
|
||||
PfxLocs loc[PFX_MAX_PASSES];
|
||||
GLuint tex[PFX_MAX_PASSES], fbo[PFX_MAX_PASSES];
|
||||
int tw[PFX_MAX_PASSES], th[PFX_MAX_PASSES];
|
||||
} PfxStage;
|
||||
@@ -54,6 +63,7 @@ static GLuint s_mask_src_fbo = 0;
|
||||
static int s_mask_src[4]; /* x0,y0,x1,y1 in source FBO px (y0 = top) */
|
||||
static int s_mask_src_valid = 0, s_mask_ok = 0;
|
||||
static GLuint s_mask_tex = 0, s_mask_rb = 0, s_mask_fbo = 0, s_mask_prog = 0;
|
||||
static GLint s_mask_uUv = -1, s_mask_uV = -1;
|
||||
static int s_mask_w = 0, s_mask_h = 0;
|
||||
|
||||
static int pfx_stage_on(int i) {
|
||||
@@ -171,8 +181,10 @@ static void pfx_fbo_attach(GLuint *f, GLuint tex) {
|
||||
static int pfx_mask_build(int fresh, int lw, int lh) {
|
||||
if (!s_mask_prog) {
|
||||
s_mask_prog = build_program(PRESENT_VS,
|
||||
"#version 330\nin vec2 v_uv; out vec4 o;\nvoid main(){ o = vec4(1.0); }\n");
|
||||
"#version 330\nin vec2 v_uv; out vec4 o; uniform float u_v;\nvoid main(){ o = vec4(u_v); }\n");
|
||||
if (!s_mask_prog) return 0;
|
||||
s_mask_uUv = p_glGetUniformLocation(s_mask_prog, "u_uv_rect");
|
||||
s_mask_uV = p_glGetUniformLocation(s_mask_prog, "u_v");
|
||||
}
|
||||
if (s_mask_w != lw || s_mask_h != lh) {
|
||||
pfx_tex_alloc(&s_mask_tex, lw, lh, -1, 1);
|
||||
@@ -192,11 +204,16 @@ static int pfx_mask_build(int fresh, int lw, int lh) {
|
||||
0, lh, lw, 0, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_mask_fbo);
|
||||
glViewport(0, 0, lw, lh);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
/* 0 everywhere, then 1 where the marks are (no clear: the clear
|
||||
* colour is caller state this pass would have to query back). */
|
||||
p_glUseProgram(s_mask_prog);
|
||||
p_glUniform1f(s_mask_uV, 0.0f);
|
||||
post_fullscreen(s_mask_prog, s_mask_uUv);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilFunc(GL_EQUAL, 0x02, 0x02);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
post_fullscreen(s_mask_prog, p_glGetUniformLocation(s_mask_prog, "u_uv_rect"));
|
||||
p_glUniform1f(s_mask_uV, 1.0f);
|
||||
post_fullscreen(s_mask_prog, s_mask_uUv);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
s_mask_ok = 1;
|
||||
}
|
||||
@@ -216,6 +233,16 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
const int last = i == c->npasses - 1;
|
||||
if (!st->prog[i]) {
|
||||
st->prog[i] = pfx_build_pass(ps);
|
||||
if (st->prog[i]) {
|
||||
PfxLocs *L = &st->loc[i];
|
||||
const GLuint pg = st->prog[i];
|
||||
L->fixed[PFX_LOC_RCP] = p_glGetUniformLocation(pg, "u_rcp");
|
||||
L->fixed[PFX_LOC_SIZE] = p_glGetUniformLocation(pg, "u_size");
|
||||
L->fixed[PFX_LOC_ORG] = p_glGetUniformLocation(pg, "u_org");
|
||||
L->fixed[PFX_LOC_UV] = p_glGetUniformLocation(pg, "u_uv_rect");
|
||||
for (int k = 0; k < ps->ninputs; k++) L->in[k] = p_glGetUniformLocation(pg, ps->in[k].sampler);
|
||||
for (int k = 0; k < ps->nuniforms; k++) L->u[k] = p_glGetUniformLocation(pg, ps->u[k].name);
|
||||
}
|
||||
if (!st->prog[i]) {
|
||||
fprintf(stdout, "psxrecomp: post effect '%s' pass '%s' did not compile; effect off\n",
|
||||
c->name, ps->name);
|
||||
@@ -244,6 +271,7 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
glViewport(lx, ly, lw, lh);
|
||||
}
|
||||
const GLuint prog = st->prog[i];
|
||||
const PfxLocs *L = &st->loc[i];
|
||||
p_glUseProgram(prog);
|
||||
for (int k = 0; k < ps->ninputs; k++) {
|
||||
const int from = ps->in[k].from;
|
||||
@@ -251,15 +279,15 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
: from == PFX_IN_MASK ? s_mask_tex : st->tex[from];
|
||||
p_glActiveTexture(PSXGL_TEXTURE0 + (GLenum)k);
|
||||
glBindTexture(GL_TEXTURE_2D, t);
|
||||
p_glUniform1i(p_glGetUniformLocation(prog, ps->in[k].sampler), k);
|
||||
p_glUniform1i(L->in[k], k);
|
||||
}
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
p_glUniform2f(p_glGetUniformLocation(prog, "u_rcp"), 1.0f / (float)w, 1.0f / (float)h);
|
||||
p_glUniform2f(p_glGetUniformLocation(prog, "u_size"), (float)w, (float)h);
|
||||
p_glUniform2i(p_glGetUniformLocation(prog, "u_org"), last ? lx : 0, last ? ly : 0);
|
||||
p_glUniform2f(L->fixed[PFX_LOC_RCP], 1.0f / (float)w, 1.0f / (float)h);
|
||||
p_glUniform2f(L->fixed[PFX_LOC_SIZE], (float)w, (float)h);
|
||||
p_glUniform2i(L->fixed[PFX_LOC_ORG], last ? lx : 0, last ? ly : 0);
|
||||
for (int k = 0; k < ps->nuniforms; k++) {
|
||||
const PfxUniform *u = &ps->u[k];
|
||||
const GLint loc = p_glGetUniformLocation(prog, u->name);
|
||||
const GLint loc = L->u[k];
|
||||
if (loc < 0) continue;
|
||||
switch (u->count) {
|
||||
case 1: p_glUniform1f(loc, u->v[0]); break;
|
||||
@@ -268,7 +296,7 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
default: p_glUniform4f(loc, u->v[0], u->v[1], u->v[2], u->v[3]); break;
|
||||
}
|
||||
}
|
||||
post_fullscreen(prog, p_glGetUniformLocation(prog, "u_uv_rect"));
|
||||
post_fullscreen(prog, L->fixed[PFX_LOC_UV]);
|
||||
}
|
||||
for (int k = PFX_MAX_INPUTS - 1; k >= 0; k--) {
|
||||
p_glActiveTexture(PSXGL_TEXTURE0 + (GLenum)k);
|
||||
@@ -280,31 +308,29 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
}
|
||||
|
||||
/* The GL state the executor changes, put back afterwards so whatever runs
|
||||
* next (the OSD, the next frame's draws) sees what it left. */
|
||||
* next (the OSD, the next frame's draws) sees what it left. It queries
|
||||
* little (capability flags and the viewport; every query can be a driver
|
||||
* round trip on a threaded GL, per present, generated frames included) and
|
||||
* never touches the clear colour. It runs inside a present, which draws to
|
||||
* the window, so both framebuffer bindings go back to the window (0) and
|
||||
* texture unit 0 is left active. */
|
||||
typedef struct {
|
||||
GLboolean blend, depth, scissor, stencil;
|
||||
GLfloat clear[4];
|
||||
GLint viewport[4], fbo, rfbo, active;
|
||||
GLint viewport[4];
|
||||
} PfxGlState;
|
||||
static void pfx_state_save(PfxGlState *st) {
|
||||
st->blend = glIsEnabled(GL_BLEND); st->depth = glIsEnabled(GL_DEPTH_TEST);
|
||||
st->scissor = glIsEnabled(GL_SCISSOR_TEST); st->stencil = glIsEnabled(GL_STENCIL_TEST);
|
||||
glGetFloatv(GL_COLOR_CLEAR_VALUE, st->clear);
|
||||
glGetIntegerv(GL_VIEWPORT, st->viewport);
|
||||
glGetIntegerv(0x8CA6 /*DRAW_FRAMEBUFFER_BINDING*/, &st->fbo);
|
||||
glGetIntegerv(0x8CAA /*READ_FRAMEBUFFER_BINDING*/, &st->rfbo);
|
||||
glGetIntegerv(0x84E0 /*ACTIVE_TEXTURE*/, &st->active);
|
||||
}
|
||||
static void pfx_state_restore(const PfxGlState *st) {
|
||||
if (st->blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
|
||||
if (st->depth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
|
||||
if (st->scissor) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
|
||||
if (st->stencil) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
|
||||
glClearColor(st->clear[0], st->clear[1], st->clear[2], st->clear[3]);
|
||||
glViewport(st->viewport[0], st->viewport[1], st->viewport[2], st->viewport[3]);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, (GLuint)st->fbo);
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, (GLuint)st->rfbo);
|
||||
p_glActiveTexture((GLenum)st->active);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
}
|
||||
|
||||
/* fresh: the noted source is this present's (0: reuse the last mask).
|
||||
@@ -316,7 +342,6 @@ static void postfx_present(int fresh, int with_aa, int lx, int ly, int lw, int l
|
||||
PfxGlState saved;
|
||||
pfx_state_save(&saved);
|
||||
glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST);
|
||||
glClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
int mask_done = 0, mask_ok = 0;
|
||||
for (int i = 0; i < PFX_NSTAGES; i++) {
|
||||
if (!pfx_stage_on(i)) continue;
|
||||
|
||||
@@ -267,7 +267,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
gl_renderer_render_thread_stop();
|
||||
/* The executor leaves the GL state as it found it, both ways round, with
|
||||
/* The executor leaves the GL state as it found it (capabilities both ways
|
||||
* round, the viewport, the clear colour; the window target bound), with
|
||||
* every stage on (bloom, an imported chain reading the mask, FXAA). */
|
||||
{
|
||||
uint64_t h = 0;
|
||||
@@ -281,13 +282,20 @@ int main(int argc, char **argv) {
|
||||
else { glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); }
|
||||
glClearColor(0.25f, 0.5f, 0.75f, on ? 1.0f : 0.0f);
|
||||
glViewport(3, 5, 101 + on, 77);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, on ? s_hr_fbo : 0);
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0); /* a present draws to the window */
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
pfx_state_save(&want);
|
||||
GLfloat cc0[4], cc1[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, cc0);
|
||||
const uint64_t n0 = s_bloom_passes, a0 = s_paa_passes;
|
||||
postfx_present(0, 1, 0, 0, ww, wh);
|
||||
pfx_state_save(&got);
|
||||
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc1);
|
||||
GLint dfb = -1, rfb = -1, act = -1;
|
||||
glGetIntegerv(0x8CA6, &dfb); glGetIntegerv(0x8CAA, &rfb); glGetIntegerv(0x84E0, &act);
|
||||
check(s_bloom_passes == n0 + 1 && s_paa_passes == a0 + 1, "state test ran bloom and AA");
|
||||
check(memcmp(&want, &got, sizeof want) == 0, "the pipeline leaves GL state unchanged");
|
||||
check(memcmp(&want, &got, sizeof want) == 0, "the pipeline restores caps and viewport");
|
||||
check(!memcmp(cc0, cc1, sizeof cc0), "the pipeline never touches the clear colour");
|
||||
check(dfb == 0 && rfb == 0 && act == (GLint)PSXGL_TEXTURE0, "the pipeline leaves the window target bound");
|
||||
}
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
|
||||
glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST);
|
||||
|
||||
@@ -65,12 +65,16 @@ int main(void) {
|
||||
}
|
||||
|
||||
/* File import: shaders resolve next to the preset. */
|
||||
FILE *f = fopen("pfx_chain_test.glsl", "w"); fputs("void main(){ o = vec4(1.0); }\n", f); fclose(f);
|
||||
FILE *f = fopen("pfx_chain_test.glsl", "wb"); fputs("void main(){ o = vec4(1.0); }\n", f); fclose(f);
|
||||
f = fopen("pfx_chain_test.pfx", "w"); fputs("pfx 1\npass p\n shader pfx_chain_test.glsl\n input t source\n", f); fclose(f);
|
||||
check(pfx_chain_load_file("pfx_chain_test.pfx", &c, err, sizeof err) &&
|
||||
!strcmp(c.pass[0].shader, "void main(){ o = vec4(1.0); }\n"), "file preset and shader import");
|
||||
pfx_chain_free(&c);
|
||||
check(!pfx_chain_load_file("pfx_missing.pfx", &c, err, sizeof err) && strstr(err, "cannot open"), "missing preset");
|
||||
/* A preset written with CRLF line ends (Windows editors) parses the same. */
|
||||
check(parse("pfx 1\r\npass a\r\n shader builtin:blur\r\n input t source\r\n scale 0.5\r\n", &c, err) &&
|
||||
c.pass[0].in[0].from == PFX_IN_SOURCE, "CRLF preset");
|
||||
pfx_chain_free(&c);
|
||||
remove("pfx_chain_test.glsl"); remove("pfx_chain_test.pfx");
|
||||
|
||||
printf("checks=%d failures=%d\n", checks, failures);
|
||||
|
||||
Reference in New Issue
Block a user