Repository navigation
HDR on the Mac: an RGB10A2 BT.2100 PQ layer (the RGBA16Float layer showed black blocks on an M5) - #1
Merged
maxdavidson merged 2 commits intoNov 5, 2026
Conversation
…window as it is; the RGBA16Float extended-range layer showed black blocks over flat areas on an M5 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxdavidson
reviewed
Nov 5, 2026
maxdavidson
left a comment
Owner
There was a problem hiding this comment.
Thanks, this looks good. Passing the HDR10 window to a PQ layer as it is also halves the layer's bandwidth and skips our decode.
Checked here (M4 Pro, built-in XDR, macOS 26.7.1):
t_present_hdrpasses, 22 of 22.- Donkey Kong Bananza ran with each layer: no
[RED]lines, the game made its HDR window, and present-to-screen p50 was the same, 49.5–49.8 ms.
For a follow-up, after merge:
- With RGB10A2, Core Animation ignores the metadata's
opticalOutputScale.CAEDRMetadata.hsays normalized formats are taken as 10,000. The picture is still right, since mode 3 puts SDR white at 203 nits, but the docs that call 1.0 the paper white need updating. - Some comments and env rows still describe the float layer.
- The Metal HUD is much brighter on the PQ layer. It writes SDR values unconverted, and 1.0 in PQ is 10,000 nits.
- Open: the root cause, which could be settled by reading back the float layer while the blocks show, and the iPhone, which still uses the float layer.
— Claude Opus 5.5, via Claude Code
2 tasks done
maxdavidson
pushed a commit
that referenced
this pull request
Nov 6, 2026
…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>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 6, 2026
… float source encoded for the PQ layer Follow-up to the PQ layer (#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>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 7, 2026
… that would put the table in page zero, since no path of a working game carries it to the load (issue seedds#1532) main+0x59dc90 interleaves four dispatch loops that share registers: x12 is the table base of one (adrp + add at +0x59dba4) and the constant 1 of another (mov w12,#1 at +0x59dbdc, in a block that leaves by b to its own dispatch), kept apart only by the value of w8. Since e5620b1 the base's writer walk sees a write of wN, met that mov first and gave up, and the NSP script stopped at `jump table 59dc90` on a build the profile names. patch_nso.py null_base: before the walk has found the base's add, a mov of a constant (or of wzr / xzr) that puts the whole table inside page zero is passed; Horizon never maps that page, so the load would fault through it on the console too. Any other writer still ends the chain, and a constant between the adrp and the add is the base's own input. Checked: test_jump_copies.py NullPageBase (4 of its cases fail on the rewriter before), lint_python 0 new, test_docs. The site's ten targets are instructions of the function, three of them shared with its sibling tables in x15 and x17, and Cuphead's main.sites.tsv is line for line the one of the rewriter before e5620b1. Rewritten before and after: 34 other modules of 12 games (Okami HD, Octopath Traveler 0 and II, Three Houses, Sonic Superstars, Minecraft Dungeons II, Splatoon Raiders, Ocarina of Time, DK Bananza, Pokopia, Mario Tennis Fever, Cuphead's sdk and movie module) are byte-identical. Cuphead's NSP build completes, and the game plays from a fresh save to the Inkwell Isle map at 1.3.7 with no red line (the route does not reach this function). No regress.py run: nothing changes in any module that rewrote before.
maxdavidson
pushed a commit
that referenced
this pull request
Nov 7, 2026
…d jump_adr_offsets took the nearest cmp of the index, whatever followed it (issue seedds#977, PR seedds#1515) guarded_count takes a compare only when, between it and the table load, the first branch on an unsigned condition comes with the compare's flags still in place: b.hi out of the dispatch reads N + 1 entries, b.hs / b.cs N (read as N + 1 before), b.ls / b.lo into it the same counts when the instruction before its target ends the path that failed. A compare with no such branch only sets the flags and the walk goes on to the check before it; with none the site is refused. Before, `cmp w18,seedds#4; b.hi exit; cmp w18,#1; ldrsw ...` read 2 entries of a table of 5 with no error. Checked (by the author): 165 sites of 27 modules of 6 games, all cmp; b.hi, rewritten byte-identically; GuardedCheck, 8 cases, 7 failing before. Checked here, as seedds#977 asks before a stricter rule goes in: 35 modules of 12 games on the T7 (Cuphead, Three Houses, Minecraft Dungeons II, Octopath Traveler 0 and II, Okami HD, Sonic Superstars, Ocarina of Time, DK Bananza, Mario Tennis Fever, Pokopia, Splatoon Raiders) rewrite byte-identically before and after, and so do Super Smash Bros. Ultimate 13.0.5, Mario Kart 8 Deluxe and Super Mario Odyssey's main, sdk and subsdk0 (against origin/main, with the page-zero rule of the commit before); test_jump_copies 75 OK, lint_python 0 new, test_docs. No regress.py run: no module that rewrote before changes. Modules with b.hs or a branch into the dispatch are still tested only synthetically. Co-authored-by: Atomsk-debug <135618857+Atomsk-debug@users.noreply.github.com>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 7, 2026
…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>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 7, 2026
… float source encoded for the PQ layer Follow-up to the PQ layer (#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>
Know-It-All-Brother
pushed a commit
to Know-It-All-Brother/nxnative
that referenced
this pull request
Nov 9, 2026
… 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>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 10, 2026
…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>
maxdavidson
pushed a commit
that referenced
this pull request
Nov 10, 2026
… float source encoded for the PQ layer Follow-up to the PQ layer (#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>
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
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
…eedds#1352) Civilization VII 1.5.0.1 crashed on the Mac with CRASH sig=10 addr=0 pc=main+0x4e8a18 on its UI's second screen change: four hand-written blocks in its main save x1-x17 in a 0x98-byte frame around svc #0x29, which leaves sp 8 bytes off. Horizon allows that, Apple's kernel faults on it, as with sub sp, sp, xN (site_sp_align). odd_sp_frames rounds both immediates of such a pair up to 16 in place, only when the matching add follows within 64 instructions and nothing between them branches, writes or copies sp, uses writeback or reaches past the frame. On top of the PR: a frame of 0xff1-0xfff rounds to 0x1000, which the unshifted 12-bit field cannot hold; written as before it became sub sp, sp, #0, lsl seedds#12, a frame of nothing whose stores land on the caller's stack. sp_adjust writes it as #1, lsl seedds#12, and test_sp_frame.py covers it. The search now finds candidates by a one-byte regex like exclusive_order_sites instead of walking every word: 2.71 s -> 0.08 s on PRAGMATA's 134 MiB main. Checked: test_sp_frame.py (8), the 16 test files that use patch_nso.py, test_docs.py, lint_python.py --against origin/main (nothing new). The search finds no odd sub/add sp, #imm word in 42 modules (main, rtld, sdk, subsdk*) of 10 local games, so their rewrites are unchanged; the PR author measured TotK and MGS4 byte-identical and Civilization VII 8 words changed, 14 minutes (49,800 frames) without a crash. regress.py not run: no local game has such a frame. Supersedes PR seedds#1666 (every odd immediate rounded unpaired, which would move incoming stack arguments). Co-authored-by: Xiaofeng Han <15362031+xiaofeng-han@users.noreply.github.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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A follow-up to seedds#1176, against its branch. On a MacBook Air M5 (macOS 27.0.1, built-in screen, potential EDR headroom 2), the RGBA16Float extended linear BT.2020 layer showed black blocks with coloured squares in them over flat areas: Mario Kart World's water on the course map. They flashed for a frame now and then, and for a moment every time the window came back to the front (⌘-Tab). With
DM_HDR=0there were none.The game's frames were clean:
DM_NVN_MAGSCANfound no non-finite pixel in any pass in about 1,400 frames of HDR play;DM_CTLshotwhile the blocks showed were clean;DM_PRESENT_SNAP=2) did not remove them;present_pq_to_linear'spowagainst a zero base (the present library is built with fast math) or clipping the layer's colours to Display P3.With the game's HDR10 window passed as it is to an RGB10A2 layer in BT.2100 PQ (
kCGColorSpaceITUR_2100_PQ), the blocks are gone and the colours look the same. This makes that layer the Mac's default:present_mode: into an RGB10A2 drawable, the HDR10 window goes in as it is (mode 0); any other source is gamma-coded BT.709 andfsencodes it as HDR10 with its white at the paper white (new mode 3,present_linear_to_pq), for the game's SDR window before it switches.DM_HDR_LAYER=floatkeeps the RGBA16Float layer. The iPhone is unchanged.Why the extended-range layer shows the blocks is open (Core Animation, or the system's handling of that format on this Mac).
Checked
t_present_hdr(22 checks, 3 new: the PQ window's codes written as they are, SDR white at the paper white and black at code 0, SDR mid grey);test_docs,test_env_switches,test_dmsh_abi;make -C dm/hostfor Mario Kart World.0400C3F00006E000, v0), MacBook Air M5 16 GB, macOS 27.0.1,DM_HDR=1, windowed: with the float layer, the course map and free roam showed the blocks (recorded on video), now and then and after every ⌘-Tab; with the PQ layer, several minutes on the course map with repeated ⌘-Tab showed none, and the boot screens before the HDR window have their normal colours.Docs
docs/reference/rendering.mdentry, andpython3 dm/tools/docs_symptoms.pywas run🤖 Generated with Claude Code