6.5 KiB
Shader Feedback Target Idea
This note summarizes a possible feature where a shader can request a persistent render target for storing and reusing its own internal information across frames.
Goal
Allow a shader to keep shader-local state without writing arbitrary values back into host-owned parameters.
This is useful for cases like:
- storing sampled color information across frames
- storing luminance or mask values per pixel
- keeping running filtered values
- tracking simple analysis data
- reserving a small texel region as an array-like metadata store
Core Idea
A shader may opt in, via shader.json, to receive one persistent RGBA16F render target at client/output resolution.
The intended model is:
- the shader writes into the feedback target this frame
- the shader reads the previous frame’s feedback target on the next frame
This makes it a shader-local “previous frame state” surface.
Why This Makes Sense
This is a better fit than letting shaders push arbitrary values back into the host because:
- state stays inside the render domain
- shaders remain render-focused rather than becoming host-state mutators
- host-owned parameters, UI state, and persistence remain predictable
- timing is easier to reason about
- it fits naturally with multipass and temporal rendering patterns
Recommended Behavior
1. Make it opt-in
Shaders should explicitly request this capability in shader.json.
Reasons:
- most shaders will not need it
- it avoids unnecessary VRAM/bandwidth cost
- it keeps shader capabilities explicit
- it avoids silently changing the contract for every shader
The runtime should only allocate/bind the feedback surface for shaders that request it.
2. Start with previous-frame feedback only
The first version should expose only one frame of history:
- current frame writes
- next frame reads previous state
Reasons:
- simpler mental model
- lower memory cost
- easier to document
- enough for many practical use cases
If a shader wants longer memory, it can accumulate or encode that over time into the same persistent surface.
3. Keep it shader-local
The feedback target should be treated as internal shader state, not as host-visible parameter state.
That means:
- it does not automatically update exposed parameters
- it does not automatically show up in the UI
- it does not automatically persist to runtime state
4. Keep it separate from normal multipass chaining
This feedback target should not replace or blur the meaning of the existing multipass system.
The clean model is:
- normal multipass outputs are for same-frame chaining
- the feedback target is for previous-frame persistent state
In other words:
- pass A can write an output that pass B reads later in the same frame
- the feedback target written during frame
Nis read back during frameN + 1
That means the feedback target should be thought of as a separate cross-frame resource, not as “another pass output.”
Recommended behavior for multipass shaders that request feedback:
- all passes in the shader may read the same previous-frame feedback surface
- one designated pass should produce the next feedback surface for the following frame
- feedback writes should not be interpreted as same-frame pass-to-pass communication
This avoids ambiguity such as:
- whether pass 2 sees pass 1’s feedback writes from the same frame
- whether multiple passes are racing to write the persistent surface
- whether feedback is supposed to mean same-frame scratch space or next-frame state
The intended separation is:
- use named pass outputs and
previousPassfor same-frame chaining - use the feedback target for persistent previous-frame state
What It Could Store
Because the target would be a full-resolution RGBA16F texture, a shader could use it in a few ways.
Full-frame per-pixel storage
Examples:
- luminance per pixel
- confidence/mask values
- filtered or decayed image information
- rolling per-pixel state used by a temporal effect
Small array-like metadata regions
A shader could reserve a few texels or a small block as a logical data region.
Example:
- pixel
(0, 0)stores value 0 - pixel
(1, 0)stores value 1 - pixel
(2, 0)stores value 2
Because each texel is RGBA16F, one texel can hold up to four scalar values.
This makes it possible to emulate a small array-like structure inside the texture.
Important Caveats
This is not a true random-access structured buffer. It is still a texture-backed GPU surface.
That means:
- it is best suited to texel- or pixel-oriented storage
- per-pixel “write to your own location” patterns are natural
- many-to-one reductions or arbitrary scatter writes are harder
- precision is limited to half-float storage
So the main question is usually not “can the shader store this?” but “can the shader update it cleanly with fragment-style GPU access?”
Example Use Case
For a greenscreen workflow, a shader could:
- sample a small box region of the input
- compute an average or representative screen color
- store that color in reserved texels of the feedback target
- reuse that stored value next frame as its internal key color
This would let the shader maintain its own sampled screen color over time without mutating the exposed host-side screenColor parameter.
Multipass Interaction Summary
For a multipass shader, the most sensible mental model is:
- same-frame intermediate images still flow through the existing pass system
- previous-frame persistent state flows through the feedback target
So if a shader has multiple passes:
- pass outputs are still used for within-frame work
- the feedback target is read as last frame’s stored state
- the feedback target is written once for use on the next frame
This keeps the feature understandable and prevents the feedback surface from becoming a confusing second pass graph.
Recommended First Version
The simplest strong first version would be:
- opt-in via
shader.json - one persistent
RGBA16Ftarget - full client/output resolution
- shader reads previous frame’s feedback
- shader writes current frame’s feedback
- no deeper history at first
- no automatic host writeback
Summary
This feature would give shaders a safe, GPU-native way to hold internal state across frames.
The recommended approach is:
- make it opt-in per shader
- keep it shader-local
- expose only previous-frame feedback initially
- treat it as a persistent render-state surface, not host parameter state
That keeps the design powerful without crossing the architectural boundary into shader-driven host mutation.