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

TAAPass

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 i samples target_views[i]). None before 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_view is the post-forward HDR colour the resolve samples (binding 0); the renderer updates it per frame via :meth:set_inputs. width / height are the INPUT (internal) extent; output_extent sizes 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). Passing None reverts 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.

jitter is 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