Skip to content

HDR: launcher rows for paper white and peak; an Automatic peak follows the screen's current EDR headroom (Mac) - #3

Closed
Amost- wants to merge 13 commits into
maxdavidson:feat/switch2-hdr-outputfrom
Amost-:pr/hdr-calibration
Closed

Amost- wants to merge 13 commits into
maxdavidson:feat/switch2-hdr-outputfrom
Amost-:pr/hdr-calibration

Conversation

@Amost-

@Amost- Amost- commented Nov 8, 2026

Copy link
Copy Markdown

What and why

The calibration follow-up from my test comment on seedds#1176 (seedds#1176 (comment)). The paper white and the peak that the status query answers (DM_HDR_PAPER_WHITE, DM_HDR_PEAK) had no launcher setting, though they are what a console's HDR calibration sets. Ocarina of Time shows that they matter: it declares min(peak, 3.5 × paper white), so the paper white is what lifts its highlights.

  • HDR paper white (120–400 nits, default 203) and HDR peak (400–1600, default 1000): rows under HDR output, drawn while it is on, mapped to the existing switches. At the defaults the answers are unchanged. HDR output gets a one-line description and level=basic, so the two rows show with it.
  • Automatic peak (Mac): DM_HDR_PEAK=auto answers the paper white × the window's screen's current EDR headroom (maximumExtendedDynamicRangeColorComponentValue, read beside the potential headroom the front end already polls four times a second). Rounded to 10 nits, never below the paper white, at most 16 bits, logged when it changes (at most once a second). iOS would need UIScreen.currentEDRHeadroom (not done), so the row offers Automatic on the Mac only.

Docs: docs/settings.md (the two rows), docs/env_switches.tsv (both switches are now player), a bullet in the HDR entry of docs/reference/rendering.md, and Ocarina of Time's HDR note.

Checked

Front ends

Front-ends: new: DM_HDR_PEAK=auto reads the weak front_hdr_headroom() (the window's screen's current EDR headroom; front_mac.m defines it). A front end without it answers the paper white for auto.

Docs

  • The finding is in Ocarina of Time's game note and the rendering.md HDR entry (no Symptom line changed, so the symptom index stays as it is; test_docs passes)
  • No game files, keys or data extracted from a game in the change

This merges cleanly with #2 except in Ocarina of Time's README, where both add a bullet at the end of the HDR output section: keep both. Happy to rebase after #2 lands.

🤖 Generated with Claude Code

seedds and others added 13 commits November 7, 2026 23:09
With DM_HDR=1, vi::QueryDisplayHdrStatus answers HDR on for Switch 2 SDKs, and the game makes an RGB10A2 window of PQ code values in BT.2020 (Donkey Kong Bananza). The Mac and iOS layers become RGBA16Float in extended linear BT.2020. present.metal decodes PQ (1.0 at the 203-nit paper white) and 8-bit sRGB, and rolls highlights off into the screen's EDR headroom. Without DM_HDR nothing changes.

The layer HDR metadata calls are still dropped, and logged while HDR is on. Screenshots take 10-bit windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A launcher row on the Mac and iOS, off by default, shown when the game's sdk module names the Ounce spec. The findings and gaps are in Donkey Kong Bananza's note and docs/reference/rendering.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With DM_HDR=1 it queries the HDR status, then replaces its RGBA8 window with an RGB10A2 PQ one, declaring SDR 0 to 203 nits and extended 0 to 1000; the present decodes it with the right colours and crop. Measured to the title screen on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With 2, a screen without EDR gets the game's HDR picture mapped down
by Core Animation instead of the game's own SDR. On Mario Kart World
the mapped picture is more saturated than its SDR; which is better is
open. Moving the window between screens drops frames whether or not
the game rebuilds its window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bananza asks for the HDR status every frame, as Mario Kart World
does, and reaches the screen in the same time with HDR on. The Mac
front end logged "no EDR metadata" on every DM_HDR=1 start: the else
belonged to the check before the DM_HDR=2 title. DM_HDR_PAPER_WHITE
and DM_HDR_PEAK have no launcher setting; they stand for what a
console's HDR calibration sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…window as it is; the RGBA16Float extended-range layer showed black blocks over flat areas on an M5 (#1)

On a MacBook Air M5 (macOS 27.0.1) the extended linear BT.2020 layer showed black
blocks with coloured squares over Mario Kart World's course-map water, now and then
and after the window came back to the front, while the game's frames were clean.
The layer now takes the PQ window as it is; an SDR source is encoded as HDR10 at
the paper white (present_mode 3). DM_HDR_LAYER=float keeps the old layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… float source encoded for the PQ layer

Follow-up to the PQ layer (maxdavidson#1). The present code is shared, so the iPhone's
front end only makes the same layer: RGB10A2 in BT.2100 PQ, with DM_HDR_LAYER=float keeping the
RGBA16Float one on either platform (hle_vi.c dm_hdr_layer_float).

- present_mode: a float source (linear BT.709) into the PQ layer is encoded as HDR10 at the paper
  white (mode 4), not decoded as sRGB first; t_present_hdr checks it (24 checks).
- Docs and comments: the metadata's opticalOutputScale counts for the float layer only (Core
  Animation takes 10,000 for a normalized format), the Metal HUD shows at the screen's peak on the
  PQ layer, and the float layer's description moved to DM_HDR_LAYER. Donkey Kong Bananza's note has
  the two layers' times on an M4 Pro.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…runs

The game asks for its HDR status every frame and switches between its SDR and HDR windows when the
answer changes (as when the window moves to another screen), so HDR output can change while it runs.
hle_vi.c keeps the mode (0 off, 1 by the screen, 2 on any screen) as a value the front end sets
(dm_hdr_set_mode), read from DM_HDR at start. On iOS, dm_setting_live takes DM_HDR for Switch 2
games, so the gear menu offers the HDR output row: turning it on sets the PQ layer first, then the
answer; turning it off answers SDR first, then gives the layer back its BGRA8 format. The frames
around the change are drawn for the drawable they reach (present_mode). The declared range is kept
while the layer is SDR, for the metadata once it is HDR.

t_present_hdr: 27 checks (off, on again and mode 2 while the game runs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut the 8x1 sources in a 2x1 bar of the 16:9 display, and 15 of 27 checks failed

The present now lays the picture out in the vi layer's shape, the display's 16:9 when no layer is sized (nvn_mtl.m present_shape). The test calls mtl_present_layer(N, 1), as t_present_crop and t_present_alloc do, so each source texel lands on its own target texel again. 27 of 27 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…za; the iPhone shows HDR on the PQ layer

Ocarina of Time (0400486024DEC000, Mac app, chain cfcc3a82c78b): it boots an RGBA8 window (0x25), asks vi::QueryDisplayHdrStatus every frame, and told HDR (DM_HDR=2) makes an RGB10A2 window (0x3d) with SDR 0..203 and extended 0..710 nits; no new RED line. On an LG monitor with HDR off in macOS, DM_HDR=1 answers SDR and it keeps the RGBA8 window.

Donkey Kong Bananza on an iPhone 17 Pro (iOS 27.2) shows HDR on the PQ layer. Open: with HDR on only, the picture jumps back a frame now and then while the frame rate drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the screen's current EDR headroom (Mac)

HDR paper white (120 to 400 nits, default 203) and HDR peak (400 to 1600, default 1000) are rows under HDR output, drawn while it is on, and set the existing DM_HDR_PAPER_WHITE and DM_HDR_PEAK: what a console's HDR calibration sets. At the defaults the answers are unchanged. HDR output gets a description and level=basic, so the two rows show with it.

DM_HDR_PEAK=auto (the row's Automatic, Mac only) answers the paper white times the window's screen's current EDR headroom (maximumExtendedDynamicRangeColorComponentValue, read beside the potential headroom four times a second), rounded to 10 nits, never below the paper white and at most 16 bits, logged when it changes, at most once a second. iOS would need UIScreen.currentEDRHeadroom; without it auto is the paper white.

Ocarina of Time (0400486024DEC000, v131072) on a MacBook Pro M2 Pro's XDR panel (potential headroom 16, macOS 27.2) declares min(peak, 3.5 x paper white): DM_HDR_PEAK=400 gives 0..400, 1600 gives 0..710, DM_HDR_PAPER_WHITE=300 gives 0..1000. With Automatic and the window in front, the headroom followed the brightness slider both ways (16 at low brightness, 3.2 at full) and the game declared its range again during play (0..710 -> 0..650 -> 0..710).

Front-ends: new: DM_HDR_PEAK=auto reads the weak front_hdr_headroom() (the window's screen's current EDR headroom, front_mac.m); a front end without it answers the paper white for auto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maxdavidson
maxdavidson force-pushed the feat/switch2-hdr-output branch 2 times, most recently from 8c265de to cf2ca59 Compare November 10, 2026 18:43
maxdavidson pushed a commit that referenced this pull request Nov 10, 2026
…en that can show EDR, the game's HDR10 window goes as it is into an RGB10A2 BT.2100 PQ layer, and the peak follows the screen's current headroom with no HDR10 metadata on the layer, on the Mac and iOS

- hle_vi.c: with DM_HDR=1 (the HDR output row, on by default) vi::QueryDisplayHdrStatus answers HDR on, format 1, while the window's screen can show EDR (DM_HDR=2: on any screen), and the game asks every frame, so its window follows the screen; the iOS in-game menu turns it on and off while the game runs. The paper white (DM_HDR_PAPER_WHITE, the HDR paper white row) is 100 nits by default, the screen's own white: macOS shows a PQ layer at EDR = nits / 100, measured with an extended-range screen capture, so the game's normal picture matches SDR and the headroom is left to its highlights (by eye, Mario Kart World at 100 had SDR's base brightness with brighter highlights; at 203 the whole picture was brighter).
- The peak (DM_HDR_PEAK, the HDR peak row) is automatic by default: 100 nits times the screen's current headroom (front_hdr_headroom: NSScreen's maximumExtendedDynamicRangeColorComponentValue on the Mac, UIScreen's currentEDRHeadroom on iOS), in steps of about 6 % taken once they have held for a second, at least twice the paper white, so the game tone-maps into what the screen shows. Donkey Kong Bananza and Ocarina of Time declare within the peak they are told, and all three games' pixels stay within it.
- No HDR10 metadata on the layer (DM_HDR_META=1 adds it): with metadata above 100 x headroom Core Animation compresses the whole picture, mid-tones included (measured), and Mario Kart World declares about 1.8 times the peak it is told; and in fullscreen a layer with metadata of any range showed rectangles of older frames under GPU load, in the host and in a standalone app with no game, judged by eye without knowing the mode (dm/gfx/tools/hdr_rects.mm), none without. A fixed peak above the screen's is rolled off in the present above three quarters of the screen's held peak (present.metal mode 4), keyed on the smaller of the declared range and the peak told.
- present.metal and nvn_mtl.m present_mode: the HDR10 window as it is into the PQ layer, an SDR or float source encoded at the paper white while the status answers HDR and at 100 nits, the screen's SDR white, while it answers SDR (at 203 an SDR screen showed the SDR picture twice as bright and clipped), an HDR frame brought to SDR white with its hue for screenshots and SDR drawables; RGB10A2 windows are PQ only once the status has answered HDR. A PQ layer rather than an extended-range float one, which showed black blocks over flat areas on a MacBook Air M5 while the game's frames were clean.
- With metadata in fullscreen (DM_PRESENT_AFTER_GPU), the drawable is handed to the system once its present pass has completed, without signalDrawable, which with the pass completed made frames a refresh late; it costs frames a refresh late when the pass ends close to the refresh, so it is off without metadata and in a window.
- dm/gfx/tools/edr_probe.m measures how macOS maps a PQ layer into EDR; dm/gfx/tools/hdr_rects.mm reproduces the rectangles with no game.
- t_present_hdr: the status answer, the automatic peak and its hold, the PQ decode and encode, the roll-off's curve, SDR and float sources, screenshots; test_settings: the rows and their per-platform defaults.
- Notes: Donkey Kong Bananza, Mario Kart World and Ocarina of Time's HDR windows and what each declares; three reference entries in docs/reference/rendering.md.

The PQ layer is beecrepaldi-afk's (#1), the present after the GPU and the standalone app that found the rectangles b1tdr3amer24's (#2), the paper white and peak rows and the automatic peak's front end hook Amost's (#3).

Front-ends: new: HDR output for Switch 2 games (DM_HDR, DM_HDR_PAPER_WHITE, DM_HDR_PEAK rows): the front end makes the HDR layer RGB10A2 in kCGColorSpaceITUR_2100_PQ with wantsExtendedDynamicRangeContent (front_mac.m, front_ios.m)
Front-ends: new: the HDR output row defaults to on; the host's own default, DM_HDR unset, stays SDR, so a front end that does not pass the row's value starts the game in SDR
Front-ends: new: front_hdr_screen() (the window's screen can show EDR) and front_hdr_headroom() (its current EDR headroom) are weak hooks in hle_vi.c; without front_hdr_headroom the automatic peak answers 1000 and nothing is rolled off
Front-ends: new: a front end calls dm_hdr_layer_meta_set() with whether it gave the layer HDR10 metadata, and receives the game's declared range through front_hdr_content_range() for that metadata

Co-authored-by: beecrepaldi-afk <beecrepaldi@gmail.com>
Co-authored-by: b1tdr3amer24 <240231594+b1tdr3amer24@users.noreply.github.com>
Co-authored-by: Amost <6445689+Amost-@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maxdavidson

Copy link
Copy Markdown
Owner

Thanks for the launcher rows and the Automatic peak, and for the measurements on the M2 Pro. Your work is in seedds#1176, in its single commit, with you as a co-author. That pull request is now one squashed commit on current main, so this one can't be merged here, and I'm closing it.

What changed on top of it, from measurements on a MacBook Pro M4 Pro's XDR screen and an LG C3 TV (macOS 27.2):

  1. Automatic is 100 nits times the headroom, not the paper white times it. An extended-range screen capture shows macOS displaying a PQ layer at EDR = nits / 100, so 100 x headroom is the highest level the screen shows unchanged; the paper white times it was always above that, and Core Animation compressed the whole picture. The answer moves in steps of about 6 % and only once a step has held for a second, because the headroom ramps from about 1.2 when the layer appears.
  2. Automatic is the default peak, and 100 nits the default paper white, on the Mac and iOS (iOS now reads UIScreen.currentEDRHeadroom). Switching while the game ran, Mario Kart World at 100 had SDR's base brightness with brighter highlights; at 203 the whole picture was brighter. 100 is now the row's first option; 203 and the others stay.
  3. No HDR10 metadata on the layer by default. In fullscreen a layer with metadata of any range showed rectangles of older frames under GPU load, also in a standalone app; without it, none. A fixed peak above the screen's is rolled off in the present shader instead.

Your headroom readings (16 at low brightness, 3.2 at full) are in the reference entry, credited to this PR. If the M2 Pro shows something different with the new defaults, a follow-up is welcome.

maxdavidson pushed a commit that referenced this pull request Nov 11, 2026
…en that can show EDR, the game's HDR10 window goes as it is into an RGB10A2 BT.2100 PQ layer, and the peak follows the screen's current headroom with no HDR10 metadata on the layer, on the Mac and iOS

- hle_vi.c: with DM_HDR=1 (the HDR output row, on by default) vi::QueryDisplayHdrStatus answers HDR on, format 1, while the window's screen can show EDR (DM_HDR=2: on any screen), and the game asks every frame, so its window follows the screen; the iOS in-game menu turns it on and off while the game runs. The paper white (DM_HDR_PAPER_WHITE, the HDR paper white row) is 100 nits by default, the screen's own white: macOS shows a PQ layer at EDR = nits / 100, measured with an extended-range screen capture, so the game's normal picture matches SDR and the headroom is left to its highlights (by eye, Mario Kart World at 100 had SDR's base brightness with brighter highlights; at 203 the whole picture was brighter).
- The peak (DM_HDR_PEAK, the HDR peak row) is automatic by default: 100 nits times the screen's current headroom (front_hdr_headroom: NSScreen's maximumExtendedDynamicRangeColorComponentValue on the Mac, UIScreen's currentEDRHeadroom on iOS), in steps of about 6 % taken once they have held for a second, at least twice the paper white, so the game tone-maps into what the screen shows. Donkey Kong Bananza and Ocarina of Time declare within the peak they are told, and all three games' pixels stay within it.
- No HDR10 metadata on the layer (DM_HDR_META=1 adds it): with metadata above 100 x headroom Core Animation compresses the whole picture, mid-tones included (measured), and Mario Kart World declares about 1.8 times the peak it is told; and in fullscreen a layer with metadata of any range showed rectangles of older frames under GPU load, in the host and in a standalone app with no game, judged by eye without knowing the mode (dm/gfx/tools/hdr_rects.mm), none without. A fixed peak above the screen's is rolled off in the present above three quarters of the screen's held peak (present.metal mode 4), keyed on the smaller of the declared range and the peak told.
- present.metal and nvn_mtl.m present_mode: the HDR10 window as it is into the PQ layer, an SDR or float source encoded at the paper white while the status answers HDR and at 100 nits, the screen's SDR white, while it answers SDR (at 203 an SDR screen showed the SDR picture twice as bright and clipped), an HDR frame brought to SDR white with its hue for screenshots and SDR drawables; RGB10A2 windows are PQ only once the status has answered HDR. A PQ layer rather than an extended-range float one, which showed black blocks over flat areas on a MacBook Air M5 while the game's frames were clean.
- With metadata in fullscreen (DM_PRESENT_AFTER_GPU), the drawable is handed to the system once its present pass has completed, without signalDrawable, which with the pass completed made frames a refresh late; it costs frames a refresh late when the pass ends close to the refresh, so it is off without metadata and in a window.
- dm/gfx/tools/edr_probe.m measures how macOS maps a PQ layer into EDR; dm/gfx/tools/hdr_rects.mm reproduces the rectangles with no game.
- t_present_hdr: the status answer, the automatic peak and its hold, the PQ decode and encode, the roll-off's curve, SDR and float sources, screenshots; test_settings: the rows and their per-platform defaults.
- Notes: Donkey Kong Bananza, Mario Kart World and Ocarina of Time's HDR windows and what each declares; three reference entries in docs/reference/rendering.md.

The PQ layer is beecrepaldi-afk's (#1), the present after the GPU and the standalone app that found the rectangles b1tdr3amer24's (#2), the paper white and peak rows and the automatic peak's front end hook Amost's (#3).

Front-ends: new: HDR output for Switch 2 games (DM_HDR, DM_HDR_PAPER_WHITE, DM_HDR_PEAK rows): the front end makes the HDR layer RGB10A2 in kCGColorSpaceITUR_2100_PQ with wantsExtendedDynamicRangeContent (front_mac.m, front_ios.m)
Front-ends: new: the HDR output row defaults to on; the host's own default, DM_HDR unset, stays SDR, so a front end that does not pass the row's value starts the game in SDR
Front-ends: new: front_hdr_screen() (the window's screen can show EDR) and front_hdr_headroom() (its current EDR headroom) are weak hooks in hle_vi.c; without front_hdr_headroom the automatic peak answers 1000 and nothing is rolled off
Front-ends: new: a front end calls dm_hdr_layer_meta_set() with whether it gave the layer HDR10 metadata, and receives the game's declared range through front_hdr_content_range() for that metadata

Co-authored-by: beecrepaldi-afk <beecrepaldi@gmail.com>
Co-authored-by: b1tdr3amer24 <240231594+b1tdr3amer24@users.noreply.github.com>
Co-authored-by: Amost <6445689+Amost-@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxdavidson pushed a commit that referenced this pull request Nov 12, 2026
…en that can show EDR, the game's HDR10 window goes as it is into an RGB10A2 BT.2100 PQ layer, and the peak follows the screen's current headroom with no HDR10 metadata on the layer, on the Mac and iOS

- hle_vi.c: with DM_HDR=1 (the HDR output row, on by default) vi::QueryDisplayHdrStatus answers HDR on, format 1, while the window's screen can show EDR (DM_HDR=2: on any screen), and the game asks every frame, so its window follows the screen; the iOS in-game menu turns it on and off while the game runs. The paper white (DM_HDR_PAPER_WHITE, the HDR paper white row) is 100 nits by default, the screen's own white: macOS shows a PQ layer at EDR = nits / 100, measured with an extended-range screen capture, so the game's normal picture matches SDR and the headroom is left to its highlights (by eye, Mario Kart World at 100 had SDR's base brightness with brighter highlights; at 203 the whole picture was brighter).
- The peak (DM_HDR_PEAK, the HDR peak row) is automatic by default: 100 nits times the screen's current headroom (front_hdr_headroom: NSScreen's maximumExtendedDynamicRangeColorComponentValue on the Mac, UIScreen's currentEDRHeadroom on iOS), in steps of about 6 % taken once they have held for a second, at least twice the paper white, so the game tone-maps into what the screen shows. Donkey Kong Bananza and Ocarina of Time declare within the peak they are told, and all three games' pixels stay within it.
- No HDR10 metadata on the layer (DM_HDR_META=1 adds it): with metadata above 100 x headroom Core Animation compresses the whole picture, mid-tones included (measured), and Mario Kart World declares about 1.8 times the peak it is told; and in fullscreen a layer with metadata of any range showed rectangles of older frames under GPU load, in the host and in a standalone app with no game, judged by eye without knowing the mode (dm/gfx/tools/hdr_rects.mm), none without. A fixed peak above the screen's is rolled off in the present above three quarters of the screen's held peak (present.metal mode 4), keyed on the smaller of the declared range and the peak told.
- present.metal and nvn_mtl.m present_mode: the HDR10 window as it is into the PQ layer, an SDR or float source encoded at the paper white while the status answers HDR and at 100 nits, the screen's SDR white, while it answers SDR (at 203 an SDR screen showed the SDR picture twice as bright and clipped), an HDR frame brought to SDR white with its hue for screenshots and SDR drawables; RGB10A2 windows are PQ only once the status has answered HDR. A PQ layer rather than an extended-range float one, which showed black blocks over flat areas on a MacBook Air M5 while the game's frames were clean.
- With metadata in fullscreen (DM_PRESENT_AFTER_GPU), the drawable is handed to the system once its present pass has completed, without signalDrawable, which with the pass completed made frames a refresh late; it costs frames a refresh late when the pass ends close to the refresh, so it is off without metadata and in a window.
- dm/gfx/tools/edr_probe.m measures how macOS maps a PQ layer into EDR; dm/gfx/tools/hdr_rects.mm reproduces the rectangles with no game.
- t_present_hdr: the status answer, the automatic peak and its hold, the PQ decode and encode, the roll-off's curve, SDR and float sources, screenshots; test_settings: the rows and their per-platform defaults.
- Notes: Donkey Kong Bananza, Mario Kart World and Ocarina of Time's HDR windows and what each declares; three reference entries in docs/reference/rendering.md.

The PQ layer is beecrepaldi-afk's (#1), the present after the GPU and the standalone app that found the rectangles b1tdr3amer24's (#2), the paper white and peak rows and the automatic peak's front end hook Amost's (#3).

Front-ends: new: HDR output for Switch 2 games (DM_HDR, DM_HDR_PAPER_WHITE, DM_HDR_PEAK rows): the front end makes the HDR layer RGB10A2 in kCGColorSpaceITUR_2100_PQ with wantsExtendedDynamicRangeContent (front_mac.m, front_ios.m)
Front-ends: new: the HDR output row defaults to on; the host's own default, DM_HDR unset, stays SDR, so a front end that does not pass the row's value starts the game in SDR
Front-ends: new: front_hdr_screen() (the window's screen can show EDR) and front_hdr_headroom() (its current EDR headroom) are weak hooks in hle_vi.c; without front_hdr_headroom the automatic peak answers 1000 and nothing is rolled off
Front-ends: new: a front end calls dm_hdr_layer_meta_set() with whether it gave the layer HDR10 metadata, and receives the game's declared range through front_hdr_content_range() for that metadata

Co-authored-by: beecrepaldi-afk <beecrepaldi@gmail.com>
Co-authored-by: b1tdr3amer24 <240231594+b1tdr3amer24@users.noreply.github.com>
Co-authored-by: Amost <6445689+Amost-@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxdavidson pushed a commit that referenced this pull request Nov 13, 2026
…en that can show EDR, the game's HDR10 window goes as it is into an RGB10A2 BT.2100 PQ layer, and the peak follows the screen's current headroom with no HDR10 metadata on the layer, on the Mac and iOS

- hle_vi.c: with DM_HDR=1 (the HDR output row, on by default) vi::QueryDisplayHdrStatus answers HDR on, format 1, while the window's screen can show EDR (DM_HDR=2: on any screen), and the game asks every frame, so its window follows the screen; the iOS in-game menu turns it on and off while the game runs. The paper white (DM_HDR_PAPER_WHITE, the HDR paper white row) is 100 nits by default, the screen's own white: macOS shows a PQ layer at EDR = nits / 100, measured with an extended-range screen capture, so the game's normal picture matches SDR and the headroom is left to its highlights (by eye, Mario Kart World at 100 had SDR's base brightness with brighter highlights; at 203 the whole picture was brighter).
- The peak (DM_HDR_PEAK, the HDR peak row) is automatic by default: 100 nits times the screen's current headroom (front_hdr_headroom: NSScreen's maximumExtendedDynamicRangeColorComponentValue on the Mac, UIScreen's currentEDRHeadroom on iOS), in steps of about 6 % taken once they have held for a second, at least twice the paper white, so the game tone-maps into what the screen shows. Donkey Kong Bananza and Ocarina of Time declare within the peak they are told, and all three games' pixels stay within it.
- No HDR10 metadata on the layer (DM_HDR_META=1 adds it): with metadata above 100 x headroom Core Animation compresses the whole picture, mid-tones included (measured), and Mario Kart World declares about 1.8 times the peak it is told; and in fullscreen a layer with metadata of any range showed rectangles of older frames under GPU load, in the host and in a standalone app with no game, judged by eye without knowing the mode (dm/gfx/tools/hdr_rects.mm), none without. A fixed peak above the screen's is rolled off in the present above three quarters of the screen's held peak (present.metal mode 4), keyed on the smaller of the declared range and the peak told.
- present.metal and nvn_mtl.m present_mode: the HDR10 window as it is into the PQ layer, an SDR or float source encoded at the paper white while the status answers HDR and at 100 nits, the screen's SDR white, while it answers SDR (at 203 an SDR screen showed the SDR picture twice as bright and clipped), an HDR frame brought to SDR white with its hue for screenshots and SDR drawables; RGB10A2 windows are PQ only once the status has answered HDR. A PQ layer rather than an extended-range float one, which showed black blocks over flat areas on a MacBook Air M5 while the game's frames were clean.
- With metadata in fullscreen (DM_PRESENT_AFTER_GPU), the drawable is handed to the system once its present pass has completed, without signalDrawable, which with the pass completed made frames a refresh late; it costs frames a refresh late when the pass ends close to the refresh, so it is off without metadata and in a window.
- dm/gfx/tools/edr_probe.m measures how macOS maps a PQ layer into EDR; dm/gfx/tools/hdr_rects.mm reproduces the rectangles with no game.
- t_present_hdr: the status answer, the automatic peak and its hold, the PQ decode and encode, the roll-off's curve, SDR and float sources, screenshots; test_settings: the rows and their per-platform defaults.
- Notes: Donkey Kong Bananza, Mario Kart World and Ocarina of Time's HDR windows and what each declares; three reference entries in docs/reference/rendering.md.

The PQ layer is beecrepaldi-afk's (#1), the present after the GPU and the standalone app that found the rectangles b1tdr3amer24's (#2), the paper white and peak rows and the automatic peak's front end hook Amost's (#3).

Front-ends: new: HDR output for Switch 2 games (DM_HDR, DM_HDR_PAPER_WHITE, DM_HDR_PEAK rows): the front end makes the HDR layer RGB10A2 in kCGColorSpaceITUR_2100_PQ with wantsExtendedDynamicRangeContent (front_mac.m, front_ios.m)
Front-ends: new: the HDR output row defaults to on; the host's own default, DM_HDR unset, stays SDR, so a front end that does not pass the row's value starts the game in SDR
Front-ends: new: front_hdr_screen() (the window's screen can show EDR) and front_hdr_headroom() (its current EDR headroom) are weak hooks in hle_vi.c; without front_hdr_headroom the automatic peak answers 1000 and nothing is rolled off
Front-ends: new: a front end calls dm_hdr_layer_meta_set() with whether it gave the layer HDR10 metadata, and receives the game's declared range through front_hdr_content_range() for that metadata

Co-authored-by: beecrepaldi-afk <beecrepaldi@gmail.com>
Co-authored-by: b1tdr3amer24 <240231594+b1tdr3amer24@users.noreply.github.com>
Co-authored-by: Amost <6445689+Amost-@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants