simvx.graphics.renderer.taa_pass¶
Temporal anti-aliasing resolve pass (desktop Vulkan).
Full-screen HDR pass that runs AFTER the forward pass (and volumetric fog) and BEFORE tonemap. Each frame it:
samples the current (jittered) HDR colour + depth,
reconstructs the per-pixel CAMERA motion vector from depth + the previous view-projection (depth -> world -> prev-clip -> prev_uv), covering camera + static-geometry motion,
reprojects the previous resolved frame (history) through that motion vector,
applies a 3x3 YCoCg neighborhood AABB clamp to the reprojected history (Karis/Lottes), falling back to the current colour where the reprojection lands offscreen (disocclusion),
blends current vs clamped-history (~0.9 history weight),
writes the resolved HDR into one of two ping-pong history targets.
Two persistent R16G16B16A16_SFLOAT targets ping-pong roles each frame: the
target written this frame is sampled as history next frame, with no copy in
between. Each parity has its own pre-written descriptor set (history = the
other target) AND its own uniform buffer, so per frame the pass only uploads
one small UBO and binds the parity’s set – zero descriptor rewrites. The
per-parity UBO also removes a cross-frame race: a single UBO rewritten at
record time could be read by the still-executing previous frame (whose jitter
differs), one source of non-deterministic resolves. The tonemap follows the
parity via its own per-parity descriptor sets (PostProcessPass
tonemap_set_index), wired once when TAA is toggled.
Cross-submit ordering: the target written this frame was read as history by
the PREVIOUS frame’s resolve, which may still be executing (frames in flight).
RenderTarget.begin_frame_barrier is recorded before the resolve to order
this frame’s attachment writes after those reads (the WAR/WAW hazards the
offscreen pass’s own dependencies do not cover).
Matches the web taa.wgsl resolve math (YCoCg clamp + blend) so both backends
behave alike; the velocity here is reconstructed from depth rather than sampled
from a precomputed velocity texture.
SCOPE: camera + static-geometry motion only. Skinned meshes / GPU particles have no prev-frame joint/particle state at this boundary, so they reproject as static (mild self-motion ghosting; camera motion still resolves). Per-object velocity (MRT + prev-transform SSBO + prev-joint buffer) is a flagged follow-up.
TAAU: with a render scale below 1.0 the resolve doubles as
the upscaler. The output + history targets are allocated at the OUTPUT extent
while the current-frame HDR input stays at the internal extent; a TAAU
shader permutation un-jitters the internal-res sample so the output-res history
accumulates sub-pixel detail across the Halton cycle (temporal super-
resolution), replacing the tonemap’s plain bilinear upscale. When the extents
match (render_scale 1.0), the base shader permutation and the original
same-size targets are used: that path is byte-identical to plain TAA.
Module Contents¶
Classes¶
Temporal AA resolve over the post-forward HDR target. |
Data¶
API¶
- simvx.graphics.renderer.taa_pass.__all__¶
[‘TAAPass’]
- simvx.graphics.renderer.taa_pass.log¶
‘getLogger(…)’
- class simvx.graphics.renderer.taa_pass.TAAPass(engine: Any)¶
Temporal AA resolve over the post-forward HDR target.
Initialization
- property enabled: bool¶
Whether the resolve runs. Turning it back ON after a disabled spell invalidates history: the stored resolve is frames old, and reprojecting it would ghost. The first re-enabled frame passes through instead.
- property target_views: tuple[Any, Any] | None¶
Both ping-pong colour views, indexed by write parity (tonemap set
isamplestarget_views[i]).Nonebefore setup.
- property written_index: int¶
Parity of the most recently resolved target: which per-parity tonemap descriptor set samples this frame’s output.
- property output_extent: tuple[int, int]¶
Extent of the resolved output target (tonemap sampling space).
- property is_upsampling: bool¶
True when the resolve writes at the output extent from internal-res input (TAAU); False for same-size plain TAA.
- setup(width: int, height: int, cur_colour_view: Any, depth_view: Any, colour_format: int, output_extent: tuple[int, int] | None = None) None¶
Allocate the two history targets, UBO, descriptors and pipeline.
cur_colour_viewis the post-forward HDR colour the resolve samples (binding 0); the renderer updates it per frame via :meth:set_inputs.width/heightare the INPUT (internal) extent;output_extentsizes the resolved output + history targets when it differs (TAAU).
- set_inputs(cur_colour_view: Any, depth_view: Any) None¶
Re-point binding 0 (current HDR) + 2 (depth) at new views.
Called when the current-frame HDR source changes (fog toggled on/off, so the resolve samples the fog output instead of the raw HDR colour) or on resize. Cheap: a single batched descriptor update.
- set_velocity_view(velocity_view: Any) None¶
Point binding 4 at the per-object velocity buffer (or clear it).
Called each TAA frame by the renderer with
VelocityPass.velocity_view(the RG16F per-object motion target). PassingNonereverts to the sentinel dummy (depth fallback everywhere). Only rewrites the descriptor when the view actually changes, so the steady state is a flag-only update.
- set_frame_matrices(inv_vp: numpy.ndarray, prev_vp: numpy.ndarray, jitter: tuple[float, float] = (0.0, 0.0)) None¶
Stash the current inverse VP + previous VP for the next
render.jitteris this frame’s sub-pixel offset (input pixels, as applied to the forward projection); the TAAU permutation uses it to un-jitter the internal-res current sample. Ignored by the same-size resolve.
- render(cmd: Any) None¶
Resolve TAA into this parity’s target, reading the other as history.
No-op when off. Per frame this uploads one small UBO and binds the parity’s pre-written descriptor set – no descriptor rewrites and no copy: this frame’s output simply IS next frame’s history. The frame barrier orders the attachment write after the previous (possibly still-executing) frame’s read of this same target as its history.
- resize(width: int, height: int, cur_colour_view: Any, depth_view: Any, colour_format: int, output_extent: tuple[int, int] | None = None) None¶
- cleanup() None¶