DirectX 12 VRS: Per-Draw, Per-Primitive, and Image-Based
Variable Rate Shading (VRS) in DirectX 12 allows developers to optimize rendering performance by dynamically altering the pixel shading rate across a render target without sacrificing geometry resolution or edge quality. This article examines the core differences between the three shading rate delivery mechanisms defined in the DirectX 12 specification: per-draw, per-primitive, and screen-space image-based shading. Understanding these mechanisms—along with their tier requirements, granularity, and typical implementations—enables developers to maximize frame rates while preserving visual fidelity.
Overview of Variable Rate Shading in DX12
VRS decouples the pixel shader execution frequency from the rasterization rate. Instead of evaluating the pixel shader for every single pixel, the GPU can run the pixel shader once across a block of pixels (such as 1x2, 2x1, 2x2, 2x4, 4x2, or 4x4) and broadcast the output color across the entire footprint.
DirectX 12 organizes VRS capabilities into two hardware tiers:
- Tier 1: Supports per-draw shading rates only.
- Tier 2: Supports per-draw, per-primitive, and screen-space image-based shading rates, as well as shading rate combiners.
1. Per-Draw VRS (Tier 1)
Per-draw VRS applies a single, uniform shading rate across all primitives submitted within an individual draw call.
- Mechanism: The shading rate is set on the command
list via the
ID3D12GraphicsCommandList5::RSSetShadingRateAPI call prior to issuing a draw. - Granularity: Coarse. Every pixel produced by the draw call is shaded at the specified rate unless clipped or culled.
- Overhead: Extremely low. Setting the rate carries negligible CPU and GPU execution overhead because it does not require additional memory reads or shader attribute processing.
- Ideal Use Cases:
- Background scenery or non-essential geometry known to lack high-frequency detail.
- User interfaces or large, flat-shaded decals.
- Objects heavily blurred by depth of field or volumetric fog, where high-frequency pixel shading is wasted.
2. Per-Primitive VRS (Tier 2)
Per-primitive VRS enables the application to assign different shading rates to individual triangles within the same draw call.
- Mechanism: The shading rate is written by a
programmable shader stage prior to rasterization—typically a Geometry
Shader or Mesh Shader—using the
SV_ShadingRatesystem-generated value. - Granularity: Fine. A single mesh can shade fine details at 1x1 while shading flat, low-contrast, or back-facing regions at 2x2 or 4x4 within the exact same draw submission.
- Overhead: Moderate. Requires shader logic to calculate or fetch the target rate per primitive, along with vertex/mesh shader pipeline support.
- Ideal Use Cases:
- Complex continuous meshes where edge silhouettes require full fidelity (1x1), but internal, low-variance surfaces can be shaded coarsely.
- Dynamic level of detail (LOD) systems based on primitive size, viewing angle, or distance from the camera.
3. Screen-Space Image-Based VRS (Tier 2)
Screen-space image-based VRS dictates shading rates using a low-resolution texture—the Shading Rate Image (SRI)—projected across the screen.
- Mechanism: The developer binds a 2D surface to the
pipeline via
RSSetShadingRateImage. The hardware divides the render target into uniform tiles (typically 8x8, 16x16, or 32x32 pixels, determined byD3D12_FEATURE_DATA_D3D12_OPTIONS6), and each texel in the SRI defines the shading rate for its corresponding screen-space tile. - Granularity: Spatially adaptive. Shading rates adapt to screen-space conditions regardless of the number of draw calls or primitives overlapping the area.
- Overhead: Requires a separate compute pass to generate the SRI texture each frame (or per pass), as well as texture memory and cache bandwidth during rasterization.
- Ideal Use Cases:
- Foveated Rendering: In Virtual Reality (VR) applications, eye-tracking data can direct 1x1 shading to the user's focal point and aggressive 2x2/4x4 shading to peripheral areas.
- Motion Blur & Temporal Effects: High-velocity regions identified via velocity buffers can use lower shading rates since temporal blur naturally masks high-frequency detail.
- Luminance and Contrast Adaptation: Areas of extreme darkness, bright highlights, or low color variance can be shaded at reduced rates based on luminance analysis.
Summary of Differences
| Feature | Per-Draw | Per-Primitive | Screen-Space Image-Based |
|---|---|---|---|
| DX12 Hardware Tier | Tier 1 & Tier 2 | Tier 2 Only | Tier 2 Only |
| Scope | Entire draw call | Individual triangle / primitive | Screen-space tile (e.g., 16x16) |
| Control Point | API Command
(RSSetShadingRate) |
Shader Semantic
(SV_ShadingRate) |
Texture Resource
(RSSetShadingRateImage) |
| Pipeline Stage | Command Processor / Rasterizer | Geometry or Mesh Shader | Rasterizer (via Texture Lookup) |
| Dynamic Adaptation | Low (State changes per draw) | Medium (Object-space/geometric) | High (Post-process/screen-space) |
Shading Rate Combiners
When using Tier 2 hardware, DirectX 12 does not force an exclusive
choice among these three sources. Developers can combine up to two rates
simultaneously using D3D12_SHADING_RATE_COMBINER.
For instance, the global draw call rate and the screen-space image
rate can be combined using operators such as MIN,
MAX, SUM, or OVERRIDE. This
flexibility ensures that critical geometric features marked via
per-primitive rates are not degraded by an aggressive screen-space image
mask, allowing precise control over rendering performance and visual
fidelity.