Files
psxrecomp/runtime/include/postfx_chain.h
T
Shokunin 74dadc6004 GL: one post-effect pipeline; bloom and FXAA as built-in chains, user chains via [video] post_shader
Post effects now run as chains of full-screen fragment passes on one
executor (gl_postfx.inc), as asked in review. A chain is a small .pfx preset
(postfx_chain.c, GL-free): passes with a shader (a file next to the preset or
builtin:<name>), inputs (source, the optional primitive-derived mask,
previous or a named earlier pass), a scale, format, filter and uniforms.
The executor owns target allocation and lifetime, program build/cache,
uniform binding (u_rcp, u_size, u_org) and GL state save/restore.

Stages, in order, each from a fresh copy of the image: the built-in "bloom"
chain ([video] bloom), a user chain ([video] post_shader / PSX_POST_SHADER,
elective, off by default), and the built-in "fxaa"/"fxaa_hq" chain
([video] antialiasing_mode). FXAA folds in as a one-pass chain and its
output is byte-identical to the former fixed pass; it still runs on real
frames only. Generated (Smooth motion) frames run bloom and the user chain.

The "may glow" mask is an optional pipeline input: draws keep stencil bit 1
only while an active chain reads `mask`, and the window-sized mask texture is
built once per present for the chains that use it.

Tests: postfx_chain_test (format, built-ins, errors, file import, no GL);
gl_bloom_test gains an imported two-pass chain (byte-exact invert), the mask
input (HUD unlit, 3D lit), a bad preset, a pass that does not compile, and GL
state restored with bloom + a mask chain + FXAA on, all on the caller thread
and the render thread. docs/POST_EFFECTS.md documents the format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00

87 lines
3.5 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|mask|previous|<pass name>>
* 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
*
* 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 };
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 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