mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Review integration: final-pass history, chain clock and GPU regressions
This commit is contained in:
@@ -98,8 +98,8 @@ even next to a lamp.
|
||||
size, rebuilt from the surfaces' PGXP depth (`zn = 1 - 512/(sz + 256)`); 0 where nothing 3D drew.
|
||||
Only built while a loaded chain reads it.
|
||||
- `input <sampler> history`: this pass's own target from the last real frame (trails, temporal
|
||||
filters). Generated frames do not update it.
|
||||
- `u_time` (seconds since the chain loaded) and `u_proj` (latest GTE projection: `240/H`, screen
|
||||
filters), including the final window pass. Generated frames do not update it. History starts empty after loading or resizing.
|
||||
- `u_time` (host seconds since the chain first drew, reset on reload) and `u_proj` (latest GTE projection: `240/H`, screen
|
||||
centre in uv, aspect) are declared in every pass.
|
||||
- Live uniforms: a title sets a preset `uniform` from game state with
|
||||
`psx_host_set_post_uniform(name, x, y, z, w)` (`host_overlay.h`), e.g. a light source projected
|
||||
@@ -115,3 +115,7 @@ even next to a lamp.
|
||||
thread and on the render thread.
|
||||
- `gl_post_aa_test`: FXAA on the pipeline. Its output is byte-identical to
|
||||
the former fixed pass.
|
||||
|
||||
The projection is a coherent latest CPU value. It is not associated with a
|
||||
queued frame or an individual replay view; shaders requiring an exact camera
|
||||
for a delayed or stereo image need corresponding frame/view metadata.
|
||||
|
||||
+23
-12
@@ -1,5 +1,8 @@
|
||||
#include "gte_postfx_projection.h"
|
||||
#include "postfx_debug_json.h"
|
||||
#ifndef PFX_NOW_TICKS
|
||||
#define PFX_NOW_TICKS() SDL_GetPerformanceCounter()
|
||||
#endif
|
||||
/* ---- Post-effect pipeline (docs/POST_EFFECTS.md) ---------------------------
|
||||
* One executor for every post effect on the composed game image: chains of
|
||||
* full-screen fragment passes (postfx_chain.h). Three stages, in order, each
|
||||
@@ -43,6 +46,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
PfxChain chain;
|
||||
int loaded, failed;
|
||||
uint64_t started_at;
|
||||
GLuint prog[PFX_MAX_PASSES];
|
||||
PfxLocs loc[PFX_MAX_PASSES];
|
||||
GLuint tex[PFX_MAX_PASSES], fbo[PFX_MAX_PASSES];
|
||||
@@ -92,7 +96,6 @@ static GLuint s_depth_tex = 0, s_depth_fbo = 0, s_depth_map_prog = 0;
|
||||
static GLint s_dmap_uUv = -1, s_dmap_uS = -1, s_dmap_uSrc = -1, s_dmap_uDst = -1;
|
||||
static int s_depth_w = 0, s_depth_h = 0, s_depth_ok = 0;
|
||||
static int s_pfx_depth_on = 0; /* a loaded chain reads `depth` */
|
||||
static uint64_t s_pfx_t0 = 0; /* u_time origin */
|
||||
/* Live values for preset `uniform` lines, set by the title from game state
|
||||
* (psx_host_set_post_uniform, host_overlay.h): a live value replaces the
|
||||
* preset default of the same name in every pass that declares it. */
|
||||
@@ -160,6 +163,7 @@ static void pfx_stage_unload(PfxStage *st) {
|
||||
}
|
||||
pfx_chain_free(&st->chain);
|
||||
st->loaded = st->failed = 0;
|
||||
st->started_at = 0;
|
||||
}
|
||||
|
||||
/* Which stages read the mask decides whether draws keep it. */
|
||||
@@ -465,6 +469,7 @@ static int pfx_depth_build(int fresh, int lw, int lh) {
|
||||
static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, int lh) {
|
||||
PfxChain *c = &st->chain;
|
||||
int copied = 0;
|
||||
if (!st->started_at) st->started_at = PFX_NOW_TICKS();
|
||||
for (int i = 0; i < c->npasses; i++) {
|
||||
const PfxPass *ps = &c->pass[i];
|
||||
const int last = i == c->npasses - 1;
|
||||
@@ -523,6 +528,17 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
p_glBlitFramebuffer(lx, ly, lx + lw, ly + lh, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
}
|
||||
}
|
||||
/* Allocate/reset before sampling, including the final window pass.
|
||||
* A resize starts an empty history rather than sampling the old size. */
|
||||
if (st->hist[i] && (st->hw[i] != w || st->hh[i] != h)) {
|
||||
pfx_tex_alloc(&st->htex[i], w, h, ps->format, ps->nearest);
|
||||
pfx_fbo_attach(&st->hfbo[i], st->htex[i]);
|
||||
st->hw[i] = w; st->hh[i] = h;
|
||||
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, st->hfbo[i]);
|
||||
GLfloat clear[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, clear);
|
||||
glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT);
|
||||
glClearColor(clear[0],clear[1],clear[2],clear[3]);
|
||||
}
|
||||
if (last) { p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0); glViewport(lx, ly, lw, lh); }
|
||||
else { p_glBindFramebuffer(PSXGL_FRAMEBUFFER, st->fbo[i]); glViewport(0, 0, w, h); }
|
||||
const GLuint prog = st->prog[i];
|
||||
@@ -543,7 +559,6 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
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);
|
||||
if (!s_pfx_t0) s_pfx_t0 = SDL_GetPerformanceCounter();
|
||||
if (L->proj >= 0) {
|
||||
/* u_proj: x/z = (uv.x - y) * w * x, y/z = (uv.y - z) * x for a view-space
|
||||
* point at depth sz (240-line reference, same scale on both axes). */
|
||||
@@ -556,7 +571,7 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
1.0f - (ofy > 0.0f ? ofy : 120.0f) / 240.0f, aspect);
|
||||
}
|
||||
if (L->time >= 0)
|
||||
p_glUniform1f(L->time, (float)((double)(SDL_GetPerformanceCounter() - s_pfx_t0) /
|
||||
p_glUniform1f(L->time, (float)((double)(PFX_NOW_TICKS() - st->started_at) /
|
||||
(double)SDL_GetPerformanceFrequency()));
|
||||
for (int k = 0; k < ps->nuniforms; k++) {
|
||||
const PfxUniform *u = &ps->u[k];
|
||||
@@ -578,16 +593,12 @@ static int pfx_stage_run(PfxStage *st, int generated, int lx, int ly, int lw, in
|
||||
}
|
||||
post_fullscreen(prog, L->fixed[PFX_LOC_UV]);
|
||||
if (ps->blend != PFX_BLEND_REPLACE) glDisable(GL_BLEND);
|
||||
if (!last && !generated && st->hist[i]) {
|
||||
/* This pass's target becomes its `history` for the next real frame. */
|
||||
if (st->hw[i] != w || st->hh[i] != h) {
|
||||
pfx_tex_alloc(&st->htex[i], w, h, ps->format, ps->nearest);
|
||||
pfx_fbo_attach(&st->hfbo[i], st->htex[i]);
|
||||
st->hw[i] = w; st->hh[i] = h;
|
||||
}
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, st->fbo[i]);
|
||||
if (!generated && st->hist[i]) {
|
||||
/* Capture the final window target as well as intermediate FBOs. */
|
||||
p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER, last ? 0 : st->fbo[i]);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, st->hfbo[i]);
|
||||
p_glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
const int x = last ? lx : 0, y = last ? ly : 0;
|
||||
p_glBlitFramebuffer(x, y, x+w, y+h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
for (int k = PFX_MAX_INPUTS - 1; k >= 0; k--) {
|
||||
|
||||
@@ -11,10 +11,17 @@
|
||||
* bottom-up) so a run against another tree can be compared byte for byte
|
||||
* (BLOOM_FIXTURE_NO_BLOOM builds it without the bloom API). */
|
||||
#define GL_PRESENT_TEST_HOOK(gen) bloom_capture_hook(gen)
|
||||
#include <stdint.h>
|
||||
static uint64_t pfx_fixture_clock(void);
|
||||
#define PFX_NOW_TICKS() pfx_fixture_clock()
|
||||
static void bloom_capture_hook(int gen);
|
||||
#define main scale_fixture_main
|
||||
#include "test_gl_scale_invariance.c"
|
||||
#undef main
|
||||
static uint64_t s_pfx_test_clock;
|
||||
static uint64_t pfx_fixture_clock(void) {
|
||||
return s_pfx_test_clock ? s_pfx_test_clock : SDL_GetPerformanceCounter();
|
||||
}
|
||||
void gpu_hd_textures_begin_upload(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
||||
void gpu_hd_textures_track_upload(int x,int y,int w,int h,const uint16_t* p){(void)x;(void)y;(void)w;(void)h;(void)p;}
|
||||
void gpu_hd_textures_invalidate(int x,int y,int w,int h){(void)x;(void)y;(void)w;(void)h;}
|
||||
@@ -54,6 +61,7 @@ static void bloom_capture_hook(int gen) {
|
||||
}
|
||||
|
||||
enum { SC_2D, SC_ADD, SC_3D, SC_HUD_OVER_3D, SC_PARTIAL, SC_N };
|
||||
enum { SC_DEPTH_ONLY = 99 };
|
||||
static const char *k_scene[SC_N] = { "2d", "additive", "3d", "hud-over-3d", "partial" };
|
||||
/* SC_PARTIAL's grey HUD panels (native x, y, w, h): one beside the lamp, one
|
||||
* covering part of it. */
|
||||
@@ -77,8 +85,10 @@ static uint8_t *frame(int scene, float bloom, uint64_t *vram_hash) {
|
||||
glb_set_mask_bits(0, 0); glb_set_semi_transparency(0, 0);
|
||||
glb_fill_rect(0, 0, FRAME_W, FRAME_H, 0x0000);
|
||||
/* A dark 3D ground everywhere: lit (may take a halo), below the threshold. */
|
||||
tri3d(0, 0, FRAME_W, 0, 0, FRAME_H, 0x0842);
|
||||
tri3d(FRAME_W, 0, FRAME_W, FRAME_H, 0, FRAME_H, 0x0842);
|
||||
if (scene != SC_DEPTH_ONLY) {
|
||||
tri3d(0, 0, FRAME_W, 0, 0, FRAME_H, 0x0842);
|
||||
tri3d(FRAME_W, 0, FRAME_W, FRAME_H, 0, FRAME_H, 0x0842);
|
||||
}
|
||||
switch (scene) {
|
||||
case SC_2D: /* a white HUD panel */
|
||||
glb_draw_flat_rect(120, 90, 80, 60, 0x7fff);
|
||||
@@ -88,6 +98,7 @@ static uint8_t *frame(int scene, float bloom, uint64_t *vram_hash) {
|
||||
glb_draw_flat_triangle(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
glb_set_semi_transparency(0, 0);
|
||||
break;
|
||||
case SC_DEPTH_ONLY:
|
||||
case SC_3D: /* a lit 3D polygon */
|
||||
tri3d(120, 150, 200, 150, 160, 80, 0x7fff);
|
||||
break;
|
||||
@@ -234,6 +245,7 @@ int main(int argc, char **argv) {
|
||||
fputs("void main(){ o = this is not glsl; }\n", f);
|
||||
fclose(f);
|
||||
}
|
||||
gl_renderer_render_thread_stop();
|
||||
for (int pass = 0; pass < 2; pass++) { /* 0: this thread, 1: render thread */
|
||||
uint64_t h = 0;
|
||||
gl_renderer_set_post_shader("pfx_invert.pfx");
|
||||
@@ -261,6 +273,69 @@ int main(int argc, char **argv) {
|
||||
check(lit > s_cap_w * s_cap_h / 2, "mask input: the 3D ground and lamp are lit");
|
||||
free(m);
|
||||
|
||||
/* A history input also works on the final window pass. The blue
|
||||
* channel observes the shader clock, so reload must reset its origin. */
|
||||
FILE *history_file=fopen("pfx_history.glsl","w");
|
||||
check(history_file!=NULL,"open history shader");
|
||||
if(history_file) {
|
||||
fputs("void main(){ float r=texelFetch(u_prev,ivec2(gl_FragCoord.xy)-u_org,0).r;"
|
||||
"o=vec4(r*0.5+u_add,0.0,clamp(u_time/4.0,0.0,1.0),1.0); }\n",history_file);
|
||||
fclose(history_file);
|
||||
}
|
||||
history_file=fopen("pfx_history.pfx","w");
|
||||
if(history_file) {
|
||||
fputs("pfx 1\npass history\n shader pfx_history.glsl\n input u_prev history\n uniform u_add 0.25\n",history_file);
|
||||
fclose(history_file);
|
||||
}
|
||||
psx_host_set_post_uniform("u_add",0.25f,0,0,0);
|
||||
s_pfx_test_clock=100*SDL_GetPerformanceFrequency();
|
||||
gl_renderer_set_post_shader("pfx_history.pfx");
|
||||
uint8_t *hist=frame(SC_PARTIAL,0,&h);
|
||||
size_t center=((size_t)(s_cap_h/2)*s_cap_w+s_cap_w/2)*3;
|
||||
check(abs(hist[center]-64)<=1 && hist[center+2]<=1,"first history frame is cleared and starts at time zero");
|
||||
free(hist);
|
||||
s_pfx_test_clock=102*SDL_GetPerformanceFrequency();
|
||||
hist=frame(SC_PARTIAL,0,&h);
|
||||
check(abs(hist[center]-96)<=1 && abs(hist[center+2]-128)<=1,"final pass samples the previous real image and its own elapsed time");
|
||||
free(hist);
|
||||
if(!s_rth_on) {
|
||||
for(int repeat=0;repeat<2;++repeat) {
|
||||
postfx_present(0,0,0,0,s_cap_w,s_cap_h);
|
||||
bloom_capture_hook(1);
|
||||
check(abs(s_cap[center]-112)<=1,"generated presents do not advance history");
|
||||
}
|
||||
}
|
||||
psx_host_set_post_uniform("u_add",0.125f,0,0,0);
|
||||
hist=frame(SC_PARTIAL,0,&h);
|
||||
check(abs(hist[center]-80)<=1,"live uniform update reaches the actual shader");
|
||||
free(hist);
|
||||
s_pfx_test_clock=104*SDL_GetPerformanceFrequency();
|
||||
gl_renderer_set_post_shader("pfx_history.pfx");
|
||||
hist=frame(SC_PARTIAL,0,&h);
|
||||
check(abs(hist[center]-32)<=1 && hist[center+2]<=1,"reload clears history and resets shader time while retaining live values");
|
||||
free(hist);
|
||||
s_pfx_test_clock=0;
|
||||
|
||||
FILE *depth_file=fopen("pfx_depth.glsl","w");
|
||||
if(depth_file) {
|
||||
fputs("void main(){ float d=texelFetch(u_depth,ivec2(gl_FragCoord.xy)-u_org,0).r;"
|
||||
"o=vec4(clamp(d/4000.0,0.0,1.0),0.0,0.0,1.0); }\n",depth_file);
|
||||
fclose(depth_file);
|
||||
}
|
||||
depth_file=fopen("pfx_depth.pfx","w");
|
||||
if(depth_file) {
|
||||
fputs("pfx 1\npass depth\n shader pfx_depth.glsl\n input u_depth depth\n",depth_file);
|
||||
fclose(depth_file);
|
||||
}
|
||||
gl_renderer_set_pgxp_depth(1);
|
||||
gl_renderer_set_post_shader("pfx_depth.pfx");
|
||||
uint8_t *depth=frame(SC_DEPTH_ONLY,0,&h);
|
||||
size_t outside=((size_t)(s_cap_h-1-20)*s_cap_w+20)*3;
|
||||
check(abs(depth[center]-128)<=3,"depth input reconstructs the triangle's GTE screen depth");
|
||||
check(depth[outside]<=1,"depth input is zero where no 3D primitive drew");
|
||||
free(depth);
|
||||
gl_renderer_set_pgxp_depth(0);
|
||||
|
||||
gl_renderer_set_post_shader("pfx_screen.pfx");
|
||||
check(gl_renderer_post_shader_loaded(), "source_down + blend screen chain loads");
|
||||
uint8_t *sc = frame(SC_PARTIAL, 0.0f, &h);
|
||||
|
||||
@@ -105,6 +105,15 @@ int psx_netplay_active(void){return 0;}
|
||||
GrBackend gr_backend(void){return GR_BACKEND_OPENGL;}
|
||||
void gr_refresh_backend(void){}
|
||||
#endif
|
||||
/* These renderer-only fixtures have no frontend telemetry/menu consumer. */
|
||||
void psx_video_telemetry_compose(int r,int s,int sw,int sh,int dw,int dh,int vw,int vh) {
|
||||
(void)r;(void)s;(void)sw;(void)sh;(void)dw;(void)dh;(void)vw;(void)vh;
|
||||
}
|
||||
void psx_video_telemetry_present(int result) {(void)result;}
|
||||
int psx_host_menu_overlay_image(const uint32_t **p,int *w,int *h) {
|
||||
*p=NULL;*w=*h=0;return 0;
|
||||
}
|
||||
|
||||
int gpu_display_is_depth24(void){return 0;}
|
||||
void gpu_get_display_info(GpuDisplayInfo *out){memset(out,0,sizeof(*out));out->width=320;out->height=240;}
|
||||
int psx_ws_prim_in_backdrop(void){return 0;}
|
||||
|
||||
Reference in New Issue
Block a user