Files
wsg/docs/user/hdr.md
T
Jérôme Bousquié 805babe53d HDR
2026-09-24 11:21:35 +02:00

2.9 KiB

HDR & Tone Mapping

Étape 20 — Opt-in HDR rendering with tone mapping.

What it does

By default, the WSG renderer draws directly to the window's sRGB surface. Color values above 1.0 are clipped (saturated to white) — you lose all information in bright areas.

When HDR is enabled, the pipeline becomes:

Main pass → offscreen Rgba16Float texture (unbounded float)
TM pass   → fullscreen triangle samples HDR texture, applies curve, writes to sRGB surface

The tone mapping compresses the [0, ∞) range to [0, 1] with a perceptual curve, so bright areas are smoothly rolled off instead of clipping.

Enabling HDR

use wsg_lib::core::ToneMapper;
use wsg_lib::app::AppBuilder;

let app = AppBuilder::new()
    .title("My HDR App")
    .with_hdr(ToneMapper::Aces)  // ← enables HDR
    .build()
    .await?;

Without .with_hdr(...), the renderer operates in LDR mode (direct to surface, zero overhead).

Tone mapping curves

Variant Curve Use case
ToneMapper::Aces ACES Filmic (Narkowicz 2015) Cinematic look, soft highlight rolloff, good contrast
ToneMapper::Reinhard x / (1 + x) Simple, flat; less contrast but computationally trivial

The curve is compiled into the pipeline at construction time (one WGSL entry point per variant) — there is no runtime branching cost.

Cost

HDR state Extra per-frame cost
Disabled (default) Zero — no texture, no pass, no pipeline
Enabled +1 fullscreen render pass (triangle, 3 verts) + 1 offscreen texture (same size as window)

The extra pass is negligible on any GPU (a few hundred microseconds). The offscreen texture costs ~12 bytes/pixel of VRAM (RGBA16F = 8 bytes/px + the surface's own buffer).

How it works (technical)

  • Offscreen texture: Rgba16Float, same size as the window. Created in Renderer::new, recreated on resize.
  • Main pass: the color attachment targets the HDR texture instead of the surface. The standard_shader.wgsl fragment output (linear float, unbounded) is stored as-is.
  • TM pass: a fullscreen triangle (3 vertices, no vertex buffer) samples the HDR texture, multiplies by exposure (currently fixed at 1.0), applies the tone curve, and writes to the sRGB surface. The hardware performs the linear→sRGB gamma conversion automatically (the surface format is Rgba8UnormSrgb).
  • No double gamma: the shader outputs linear [0,1]; the sRGB surface encoding is handled by the rasterizer.

Exposure

Currently fixed at 1.0 (no user control yet). A future step will expose an exposure field in a HdrConfig struct for live adjustment.

See also