mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -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>
105 lines
4.7 KiB
Markdown
105 lines
4.7 KiB
Markdown
# Post effects
|
|
|
|
Elective effects on the composed game image (OpenGL). All of them are off by
|
|
default. With none active, nothing below runs and the frame is the stock one.
|
|
|
|
Every effect is a **chain** of full-screen fragment passes run by one
|
|
executor (`runtime/src/gl_postfx.inc`). Three stages run in order, each from
|
|
a fresh copy of the image:
|
|
|
|
| stage | switch | chain |
|
|
|---|---|---|
|
|
| bloom | `[video] bloom = 0..2` (`PSX_BLOOM`) | built-in `bloom` |
|
|
| user | `[video] post_shader = "<file>.pfx"` (`PSX_POST_SHADER`) | your preset |
|
|
| AA | `[video] antialiasing_mode = "fxaa" \| "fxaa_hq"` | built-in `fxaa` / `fxaa_hq` |
|
|
|
|
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. Passes
|
|
marked `generated reuse` (bloom's bright and blur passes) are not run again
|
|
on generated frames: those reuse the last real frame's glow and mask and run
|
|
only the rest (bloom's masked composite, over their own picture).
|
|
|
|
`source` is a full-size copy of the image, made only when a pass that runs
|
|
reads it; `source_down` is the image blitted straight to that pass's size.
|
|
Bloom reads only `source_down` and composites with `blend screen`, so it never
|
|
copies the full image. The executor
|
|
owns targets, programs, uniforms and GL state, and puts back everything it
|
|
touches.
|
|
|
|
## Preset format (`.pfx`)
|
|
|
|
```
|
|
pfx 1 # required first line
|
|
pass <name> # the last pass writes the window
|
|
shader <file.glsl|builtin:name> # file paths resolve next to the preset
|
|
input <sampler> <source|source_down|mask|previous|<earlier pass>>
|
|
scale <f> # target size as a fraction of the game rect (default 1)
|
|
format rgba8|rgba16f # default rgba8
|
|
filter linear|nearest # how later passes sample this target
|
|
uniform <name> <f> [f [f [f]]] # float .. vec4
|
|
blend replace|screen # last pass: write, or screen o + dst*(1-o)
|
|
# onto the window (discard keeps dst exactly)
|
|
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:
|
|
|
|
```glsl
|
|
in vec2 v_uv; out vec4 o; // v_uv: 0..1 over the game rect, bottom-up
|
|
uniform vec2 u_rcp, u_size; // this pass's target: 1/size and size in px
|
|
uniform ivec2 u_org; // window origin of the game rect
|
|
uniform sampler2D <sampler>; // one per input
|
|
```
|
|
|
|
Built-in shaders: `blur` (9-tap Gaussian along `u_dir`, in px), `fxaa`,
|
|
`bloom_bright` and `bloom_composite`. The built-in presets are listed in
|
|
`runtime/src/postfx_chain.c`. A preset that fails to parse, or a pass that
|
|
fails to compile, is logged and its stage skipped.
|
|
|
|
Example: invert the image.
|
|
|
|
```
|
|
pfx 1
|
|
pass invert
|
|
shader invert.glsl
|
|
input u_tex source
|
|
```
|
|
|
|
with `invert.glsl` containing:
|
|
|
|
```glsl
|
|
void main(){ o = vec4(1.0 - texelFetch(u_tex, ivec2(gl_FragCoord.xy) - u_org, 0).rgb, 1.0); }
|
|
```
|
|
|
|
## The mask input
|
|
|
|
`mask` is 1 where the last thing drawn was a 3D primitive (proven from GTE
|
|
data, so it needs PGXP geometry) or an additive semi-transparent primitive
|
|
(blend modes 1 and 3). It is 0 where an opaque 2D primitive drew last, such as
|
|
a sprite, a fill or the HUD. Only while an active chain reads `mask` does every
|
|
draw keep that mark in stencil bit 1 (bit 0 is the PS1 mask-bit mirror). The
|
|
mark follows the picture through native-wide centre copies and Smooth motion
|
|
surfaces. At a real frame's present it is resolved in the presented surface's
|
|
own pixels (an R8 target sharing that surface's depth-stencil renderbuffer,
|
|
so no depth-stencil blit, which costs the CPU milliseconds on macOS) and mapped
|
|
to a window-sized R8 texture. Native-wide takes the centre's marks from the
|
|
hr surface directly. Generated frames reuse the newer real frame's mask. A chain that
|
|
does not read `mask` costs none of this.
|
|
|
|
Bloom uses the mask at both ends: only lit pixels feed its bright pass, and
|
|
only lit pixels take the glow back. HUD pixels are therefore never changed,
|
|
even next to a lamp.
|
|
|
|
## Tests
|
|
|
|
- `postfx_chain_test`: the format, built-in presets, errors and file import.
|
|
- `gl_bloom_test`: bloom on 2D, additive and 3D primitives, plus a HUD beside
|
|
and over a lamp. Also an imported chain (byte-exact invert), the mask input
|
|
(the HUD unlit, the 3D scene lit), a bad preset, a pass that does not
|
|
compile, and GL state restored with every stage on. It runs on the caller
|
|
thread and on the render thread.
|
|
- `gl_post_aa_test`: FXAA on the pipeline. Its output is byte-identical to
|
|
the former fixed pass.
|