Skip to main content

GPU Compositor

vv_render::Compositor turns the stack of layers active at one timeline frame into a single RGBA texture on the GPU (wgpu). The same code serves the viewer, the export, compound clips and the Color window; only the clear color and what happens to the result differ.

Compositing one frame

compose-frame.png

Layer contents

LayerContentSource of the pixels
VideoDecoded Y/U/V planes (+ optional alpha) uploaded as R8/RG8 textures; YUV → RGB in the shader
TextureAn already composed texture: the nested timeline of a compound clip, never read back to the CPU
SolidColor from the uniform, treated as a source as large as the timeline
TextCoverage masks from text.rs, one pass per mask (see Text Rendering)
AdjustmentA copy of the stack below, redrawn with the layer's transform and filters; it replaces the stack and mixes with it by opacity

Transform semantics

  • The source is first fitted into the output keeping its aspect ratio: bars (letterbox / pillarbox) instead of stretching. Bars have alpha 0, so the layer below shows through.
  • Position and anchor are in timeline pixels; crop and crop softness in media pixels at native resolution, so a proxy crops exactly like the original. The model's Y axis points up, as in an NLE.
  • Zoom (per axis) and rotation act around the anchor; flip mirrors; crop softness feathers the edge through alpha.

Filters and blending

FeatureImplementation
Per-pixel filters (Color correction, Exposure)Inside the transform shader, up to 8 per layer, in the user's order. The color correction reads its own uniform (binding 8, see Color Correction & Scopes).
Box / Gaussian blurSeparate passes on an intermediate texture (they need neighbours); keyframable radius and direction; taps capped at 96 per side
OpacityUniform multiplier on the layer alpha (clip fades)
Blend modesNormal uses fixed alpha blending; the other 14 (Add, Multiply, Screen, Overlay…) read a copy of the stack as backdrop and blend in the shader

Work format and precision

Layers are composed into work textures whose format follows the project's ProcessingPrecision, so a chain of passes (blurs, adjustment clips, blend modes) does not round to 8 bits at every step.

PrecisionWork format
High (default)Rgba16Float, if the adapter can render, blend and filter it; otherwise falls back to 8 bits
StandardRgba8Unorm: half the memory and bandwidth

What leaves the compositor as 8 bits is resolved once, with spatial triangular dither (dither.wgsl, a pcg2d hash: the same frame always gives the same bytes). Values already on an 8-bit step are left alone, so flat colors and black bars stay clean. In Standard precision the work texture already is the output and the resolve pass is skipped. set_precision rebuilds the pipelines for the new format; textures already handed out keep theirs.

Rec.709 luma and Cb/Cr live in color.wgsl, prepended to the transform, scopes and I420 shaders; vv_core::luma and cb_cr are their CPU mirrors.

Outputs

MethodUsed by
render_layers_to_textureViewer: resolved to Rgba8Unorm, stays on the GPU, registered in egui. Composes at the decoded frame's resolution widened to the timeline aspect (fit_output_size), so the content is never upscaled.
render_layers_i420Export: timeline resolution; a compute shader reads the work texture and converts to BT.709 limited I420, dithering once in YUV and halving the readback.
render_layers_to_owned_texture_transparentCompound clips and the Color window's scopes: transparent clear, left in the work format; the texture returns to a pool when the holder drops it.

Plane, output and work textures are pooled by size and format (up to 32 each): allocating them every frame cost more than the drawing. With no layers the result is just the clear color (a black frame).