Repository navigation
A fullscreen HDR layer is presented only after its GPU work completes (DM_PRESENT_AFTER_GPU) - #2
Conversation
… (DM_PRESENT_AFTER_GPU): with the menu bar hidden, rectangles of older frames flickered on a MacBook Pro On a MacBook Pro M4 Pro (macOS 27.2, built-in screen), Ocarina of Time with HDR output in fullscreen showed rectangles of older frames whenever nothing covered the layer (menu bar hidden, no frame-rate counter); its opening movie looked like frames out of order. A standalone app with the same present (PQ layer with HDR10 metadata, CAMetalDisplayLink, Metal 4 waitForDrawable / commit / signalDrawable / present, drawable at the screen's pixel size, about 12 ms of GPU work a frame) reproduced it with no game. A classic presentDrawable, no HDR10 metadata, an SDR layer, a view over the layer, or a present made after GPU completion each removed it; on-screen times stayed 16.7 ms apart in every mode. present_now now presents from the display command buffer's feedback handler when DM_PRESENT_AFTER_GPU is on (whichever of signalDrawable and completion comes second presents). Default on the Mac for an extended-range layer; iOS untested, so off there. Workaround for what looks like a system bug: reference entry in rendering.md, symptom index regenerated, Ocarina of Time's note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for tracking this down, and for the standalone repro. It reproduces here (MacBook Pro M4 Pro, built-in XDR, macOS 27.2, Ocarina of Time in fullscreen HDR), and your present removes the rectangles. We merged this PR, and added one commit on top of yours in the HDR branch (37244de; GitHub didn't let us push to your branch). Your commit is kept as it is. Ours changes two things in the code and rewrites the reference entry with what we measured:
Why skip
The present pass itself was never late: in every frame we logged it ended at least 3 ms before its refresh (median 32 ms). The rectangles come from handing the drawable over before the pass has run. A drawable that Why fullscreen only. In a window the system composites the layer, so the rectangles don't show. Presenting after the GPU there made frames reach the screen about 5 ms later (49.9 vs 54.7 ms from the present call). The present now switches on and off with fullscreen. The remaining cost. At a fixed 60 Hz no rectangle-free order we tried did better. In the heavy stretch they were late by 14 ms, against 5.6 ms in Apple's order: a frame handed over after its pass misses the refresh when the pass ends close to it. Also checked:
If you see something different on your side, a follow-up is welcome. The iPhone's frames that jump back with HDR on may be the same issue, if you get to try it there. If you'd like to file your standalone app with Feedback Assistant, the entry will link the report; otherwise we can file it. |
…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>
…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>
…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>
…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>
What and why
Follow-up to my comment on seedds#1176. With HDR output in fullscreen and nothing over the layer (menu bar hidden, no frame-rate counter), rectangles of older frames flicker on a MacBook Pro M4 Pro (macOS 27.2). A standalone Metal 4 app with this present reproduces it with no game. The system shows a signalled drawable's older contents on a screen that shows the layer directly. The cause is open and looks like a macOS bug.
The change makes
present_nowcallpresentfrom the display command buffer's commit feedback handler, once bothsignalDrawableand GPU completion have happened. It's controlled byDM_PRESENT_AFTER_GPU: on by default for the Mac's HDR layer, and off on iOS, where it is untested.Reference entry: "A fullscreen HDR layer shows rectangles of older frames" in
docs/reference/rendering.md. The symptom index is regenerated and OoT's note updated. The iPhone's "picture jumps back a frame" open item may be the same bug;DM_PRESENT_AFTER_GPU=1would tell.Targets
feat/switch2-hdr-output, the branch seedds#1176 runs from. The comment linkedfeat/hdr-output-switch2, an older branch with the float layer; happy to retarget.Checked
make -C dm/gfx/tests GAME=0400486024DEC000 check_t_present_hdr check_t_present_alloc check_t_present_rescue check_t_present_snapall pass, with the default and withDM_PRESENT_AFTER_GPU=1.test_docs.pypasses.test_env_switchesfails only onDM_NVN_BC_ALIAS, from main'sa631295b, and fails the same way without this change.regress.pynot run: the change only affects the present when the HDR layer is on.Docs