mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
8dbe2165ff
Smooth motion lost most of its generated frames with bloom on (R4 race, 120 Hz: ~30% of bloom-off). Measured and fixed in order of cost: - Depth-stencil blits (CPU milliseconds each on macOS) are gone: generation surfaces no longer copy the marks and are not marked while drawn (generated frames reuse the newer real frame's mask); the native-wide centre copy is colour only again and the mask takes the centre's marks from the hr surface; the mask itself is resolved in the presented surface's own pixels through an R8 target that shares the surface's depth-stencil renderbuffer, then mapped to the window. - Marking needs one pass per batch, not two: 3D batches only set, 2D opaque ones only clear, semi-transparent ones only set; only batches of unknown make (queued replays, lines) still do both. - No full-size copy of the image: `source_down` (a blit straight to the pass's size) feeds the bright pass, and the composite screen-blends onto the window (`blend screen`; unlit pixels are discarded, so they keep their exact value). The debug split/bloom-only views go with the copy. M4, R4 race, render thread + reprojection, PSX_FRAME_GEN_FORCE, a host loaded by other work (absolute rates low): bloom pass on real / generated frames 1.2 / 0.7 ms at 1080p, 1.3 / 0.9 ms at 1440p, 2.6 / 1.6 ms at 4K (glFinish-bracketed); generated frames with bloom 79-80% of bloom-off at all three (was ~30%). A 1/8-resolution blur chain was tried and kept no more frames, so the 1/4 chain (the look) stays. Tests: parser (blend, source_down, bloom preset reads no full copy), GL fixture (source_down + blend screen chain exact; HUD and mask checks unchanged), FXAA unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
4.4 KiB
C
97 lines
4.4 KiB
C
/* postfx_chain.h — post-processing pass chains (docs/POST_EFFECTS.md).
|
|
*
|
|
* A chain is a list of full-screen fragment passes run on the composed game
|
|
* image by the OpenGL executor (gl_postfx.inc). This header is the GL-free
|
|
* part: the preset format and its parser, so it can be unit tested alone.
|
|
*
|
|
* Preset (.pfx) format, one directive per line, '#' comments:
|
|
*
|
|
* pfx 1 # format version (required first)
|
|
* pass <name> # starts a pass; the last pass writes the window
|
|
* shader <file.glsl|builtin:name> # fragment body (see below)
|
|
* input <sampler> <source|source_down|mask|previous|<pass name>>
|
|
* # source_down: the image scaled to this pass's
|
|
* # target (one bilinear blit, no full-size copy)
|
|
* scale <f> # target size, fraction of the game rect (default 1)
|
|
* 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
|
|
* blend replace|screen # how the window pass lands (default replace):
|
|
* # screen = o + dst * (1 - o); discard leaves dst
|
|
* 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;
|
|
* uniform vec2 u_rcp; // 1 / this pass's target size in px
|
|
* uniform vec2 u_size; // this pass's target size in px
|
|
* uniform ivec2 u_org; // window origin of the game rect (gl_FragCoord - u_org = px in it)
|
|
* plus one `uniform sampler2D <sampler>;` per input. v_uv is 0..1 over the
|
|
* game rect, bottom-up like the window. */
|
|
#ifndef PSX_POSTFX_CHAIN_H
|
|
#define PSX_POSTFX_CHAIN_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
enum { PFX_MAX_PASSES = 16, PFX_MAX_INPUTS = 4, PFX_MAX_UNIFORMS = 8, PFX_NAME = 32 };
|
|
/* Input sources; a value >= 0 is an earlier pass's index. */
|
|
enum { PFX_IN_SOURCE = -1, PFX_IN_MASK = -2, PFX_IN_SOURCE_DOWN = -3 };
|
|
enum { PFX_BLEND_REPLACE = 0, PFX_BLEND_SCREEN = 1 };
|
|
enum { PFX_FMT_RGBA8 = 0, PFX_FMT_RGBA16F = 1 };
|
|
|
|
typedef struct {
|
|
char sampler[PFX_NAME];
|
|
int from; /* PFX_IN_* or an earlier pass index */
|
|
} PfxInput;
|
|
|
|
typedef struct {
|
|
char name[PFX_NAME];
|
|
int count; /* 1..4 */
|
|
float v[4];
|
|
} PfxUniform;
|
|
|
|
typedef struct {
|
|
char name[PFX_NAME];
|
|
char *shader; /* fragment body (owned) */
|
|
float scale;
|
|
int format, nearest;
|
|
int reuse_generated; /* generated frames reuse the last real frame's target */
|
|
int blend; /* PFX_BLEND_*: the window pass only */
|
|
int ninputs, nuniforms;
|
|
PfxInput in[PFX_MAX_INPUTS];
|
|
PfxUniform u[PFX_MAX_UNIFORMS];
|
|
} PfxPass;
|
|
|
|
typedef struct {
|
|
char name[PFX_NAME];
|
|
int npasses;
|
|
int uses_mask; /* some pass reads `mask` */
|
|
PfxPass pass[PFX_MAX_PASSES];
|
|
} PfxChain;
|
|
|
|
/* Resolves `shader` arguments: builtin:<name> and file paths (relative to the
|
|
* preset). Returns a malloc'd body or NULL (err filled). */
|
|
typedef char *(*PfxShaderLoader)(const char *arg, void *user, char *err, int errlen);
|
|
|
|
/* Parse preset text into *out (zeroed first). Returns 1, or 0 with a
|
|
* "line N: ..." message in err. */
|
|
int pfx_chain_parse(const char *text, const char *name, PfxShaderLoader load, void *user,
|
|
PfxChain *out, char *err, int errlen);
|
|
/* Load a preset file; shader files resolve relative to its directory. */
|
|
int pfx_chain_load_file(const char *path, PfxChain *out, char *err, int errlen);
|
|
/* A built-in preset ("bloom", "fxaa", "fxaa_hq"); 0 if unknown. */
|
|
int pfx_chain_builtin(const char *name, PfxChain *out, char *err, int errlen);
|
|
/* A built-in shader body by name, or NULL. */
|
|
const char *pfx_builtin_shader(const char *name);
|
|
/* Set every pass's uniform `name` (count values); returns how many were set. */
|
|
int pfx_chain_set_uniform(PfxChain *c, const char *name, int count, const float *v);
|
|
void pfx_chain_free(PfxChain *c);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif
|