wgpu/documentation/color/hdr_surfaces.rs
1/*!
2# HDR Surfaces
3
4A surface can present in different color spaces to get HDR or wide-gamut
5output onto the screen. This is configured through
6[`SurfaceConfiguration::color_space`], and the rest of this section is a
7concept primer on how it works.
8
9### HDR output in a nutshell
10
11By default a surface is standard dynamic range (SDR) with the sRGB gamut: the
12value `1.0` is the brightest white, anything above it clips, and only colors
13within sRGB are expressible. Other [`SurfaceColorSpace`]s opt into a **wider
14gamut** (for example [`DisplayP3`](SurfaceColorSpace::DisplayP3), still SDR
15but with more saturated colors than sRGB), **high dynamic range** (values
16above `1.0` drive brighter-than-white output), or both (for example
17[`Bt2100Pq`](SurfaceColorSpace::Bt2100Pq), aka HDR10), on platforms that
18support it.
19
20Three ideas carry most of the weight:
21
22* **Reference white and headroom.** Brightness is measured in *nits*
23 (cd/m²). On HDR-capable monitors, SDR reference white (plain white, `(1.0, 1.0, 1.0)` in the
24 extended color spaces) sits *below* the display's peak output on purpose, so
25 highlights have room above it. That gap is the display's *headroom*.
26* **The transfer function is a round-trip.** Your shader applies an
27 encoding transfer function (the OETF) to turn the light it computed into a
28 stored signal, and the display applies the inverse decoding transfer
29 function (the EOTF) to turn it back into light. Choosing a color space
30 chooses which transfer functions both ends use.
31* **Who applies the encoding transfer function.** wgpu applies it for you
32 **only** when you render to an `*Srgb` texture view format, where the GPU
33 runs the sRGB OETF when a value is stored to a texture. For every other
34 color space (linear extended sRGB, encoded extended sRGB or P3, PQ, HLG)
35 **the values your shader writes to the surface texture must already be
36 encoded by you**, along with any gamut conversion; in a typical renderer
37 this happens in a final tone-mapping or post-processing pass. wgpu hands
38 the signal to the compositor unchanged; getting this wrong produces a
39 wrong image with no error.
40
41wgpu does **not** tonemap or gamut-map for you. It gives you the surface
42and, through [`DisplayHdrInfo`], the display's advisory capabilities;
43*choosing* and *applying* a tone curve is your application's job.
44
45### The practical path
46
471. **Query capabilities.** Call [`Surface::get_capabilities`]. To use HDR or
48 wide-gamut output, read [`SurfaceCapabilities::format_capabilities`] (each
49 format and the [`SurfaceColorSpaces`] it supports), **not**
50 [`SurfaceCapabilities::formats`]: the latter lists only formats usable
51 with [`Auto`](SurfaceColorSpace::Auto), which never selects HDR.
52 [`SurfaceCapabilities::color_spaces`] is a convenience lookup for one
53 format.
542. **Optionally query the display.** Call [`Surface::display_hdr_info`] for the
55 current [`DisplayHdrInfo`] (peak and SDR-white nits, EDR headroom,
56 primaries, and a coarse dynamic-range/gamut bucket). Use it to pick a
57 tone-map target ([`DisplayHdrInfo::tone_map_headroom`]); *whether* HDR is
58 worthwhile is the capability question from step 1, not this live value.
59 Every field is advisory and optional; `None` means "cannot tell here",
60 never "SDR".
613. **Choose a format and color space.** Intersect what you want with what
62 step 1 advertises, in your own preference order (for example HDR10, then
63 linear extended sRGB, then encoded extended sRGB, then SDR
64 [`Srgb`](SurfaceColorSpace::Srgb)). Keep an SDR fallback for when nothing
65 HDR is advertised, such as when OS HDR is off.
664. **Configure the surface.** Set [`SurfaceConfiguration::color_space`] and
67 `format`. [`Auto`](SurfaceColorSpace::Auto) (the default) reproduces
68 wgpu's historical behavior and never picks HDR; any other value must be in
69 that format's advertised set or configuration fails validation.
705. **Encode what you write to the surface texture.** For an `*Srgb` format,
71 output linear and the hardware encodes for you. Otherwise the values your
72 shader writes to the surface texture must already carry the encoding the
73 chosen color space expects (sRGB, extended sRGB, PQ, or HLG) **and** any
74 gamut conversion (for example outputting in the BT.2020 gamut for HDR10);
75 in a typical renderer you do this in a final tone-mapping or
76 post-processing pass. See the table below.
776. **Present** as usual. If OS HDR is toggled mid-run, you'll see it on the
78 next [`Surface::display_hdr_info`] poll; re-query [`Surface::get_capabilities`]
79 and re-run the steps above.
80
81The standalone [HDR surface example] implements every step, including the
82encoding transfer function for each color space.
83
84### What to output from your fragment shader
85
86What the values your shader writes to the surface texture must contain for
87each color space, and whether wgpu applies the transfer function for you:
88
89| Color space | Typical format | You write | wgpu encodes? |
90| ----------- | -------------- | --------- | ------------- |
91| `Srgb`, `*Srgb` format | `{Rgba,Bgra}8UnormSrgb` | linear | **yes** (hardware sRGB OETF on store) |
92| `Srgb`, non-srgb format | `{Rgba,Bgra}8Unorm` | sRGB-encoded | no; apply the sRGB OETF yourself or use `*Srgb` instead |
93| `ExtendedSrgbLinear` (scRGB) | `Rgba16Float` | linear, `1.0` = SDR white | no, but no encoding is necessary |
94| `ExtendedSrgb` | `Rgba16Float` | extended sRGB-encoded | no; apply the extended sRGB OETF yourself |
95| `DisplayP3` | `Bgra8Unorm` | sRGB-encoded, P3 primaries | no; apply the sRGB OETF (after gamut-mapping to P3) |
96| `ExtendedDisplayP3` | `Rgba16Float` | extended sRGB-encoded, P3 primaries | no; apply the extended sRGB OETF (after gamut-mapping to P3) |
97| `Bt2100Pq` (HDR10) | `Rgb10a2Unorm` | PQ-encoded, BT.2020 primaries | no; apply the PQ OETF (after gamut-mapping to BT.2020) |
98| `Bt2100Hlg` | `Rgb10a2Unorm` | HLG-encoded, BT.2020 primaries | no; apply the HLG OETF (after gamut-mapping to BT.2020) |
99
100In short, wgpu applies the transfer function for you only when you render to
101an `*Srgb` format. In every other case the values your shader writes to the
102surface texture must already carry both the transfer function and any gamut
103conversion. The [HDR surface example] implements every encoder in WGSL.
104
105### Glossary
106
107* **Chromaticity** --- a color's hue and saturation independent of its
108 brightness, given as an `(x, y)` coordinate on the CIE 1931 diagram.
109* **Primaries / gamut** --- the chromaticities of the red, green, and blue a
110 color space addresses, and so the range of colors it can express. [BT.709]
111 is the sRGB gamut, [Display P3] is wider, and [BT.2020] is wider still.
112* **White point** --- the chromaticity of `R = G = B` (what "white" looks
113 like). Every color space here uses [D65], standard daylight.
114* **Transfer function (OETF / EOTF)** --- how stored values map to light. The
115 *OETF* is the encoding transfer function your application applies; the
116 *EOTF* is the inverse decoding transfer function the display applies.
117* **SDR / HDR** --- standard dynamic range clips at `1.0` (reference white);
118 high dynamic range lets values above `1.0` drive brighter-than-white
119 output.
120* **Nits and reference white** --- a *nit* (cd/m²) is a unit of brightness;
121 *reference white* (also called *paper white*, especially on Windows) is the
122 brightness of SDR plain white, set below the panel's peak so highlights have
123 room above it.
124* **Headroom (EDR)** --- how much brighter than current SDR white the display
125 can go right now, as a multiplier (`1.0` means none). Dynamic; see
126 [`DisplayHdrInfo::tone_map_headroom`].
127* **PQ / HLG** --- the two HDR transfer functions: [PQ] (SMPTE ST 2084,
128 HDR10) encodes absolute luminance, [HLG] (BT.2100) encodes relative
129 luminance.
130* **scRGB / extended-range sRGB** --- sRGB extended past 0.0..=1.0 for
131 HDR: [scRGB] is *linear*
132 ([`ExtendedSrgbLinear`](SurfaceColorSpace::ExtendedSrgbLinear)), while
133 [`ExtendedSrgb`](SurfaceColorSpace::ExtendedSrgb) is the same range but
134 sRGB-*encoded* (gamma), the web's HDR path.
135
136[HDR surface example]: https://github.com/gfx-rs/wgpu/tree/v29/examples/standalone/03_hdr_surface
137[BT.709]: https://www.itu.int/rec/R-REC-BT.709
138[BT.2020]: https://www.itu.int/rec/R-REC-BT.2020
139[Display P3]: https://en.wikipedia.org/wiki/DCI-P3#Display_P3
140[D65]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
141[PQ]: https://en.wikipedia.org/wiki/Perceptual_quantizer
142[HLG]: https://www.itu.int/rec/R-REC-BT.2100
143[scRGB]: https://en.wikipedia.org/wiki/ScRGB
144*/
145
146use crate::{
147 DisplayHdrInfo, Surface, SurfaceCapabilities, SurfaceColorSpace, SurfaceColorSpaces,
148 SurfaceConfiguration,
149};