MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Fullscreen and HDR: Windows' window rules from Wine, HDR10 from the Mac's own EDR

Entry 050835 of 939 tests pass

D3D12 games on the Mac layer can now go exclusive fullscreen and output HDR10 or scRGB, which macOS shows as extended dynamic range on an XDR screen; Wine's own DXGI conformance test now runs against the layer.

What changed

The layer now implements the two DXGI features a game touches before it draws anything: fullscreen and HDR. DXGI is the part of Direct3D that owns windows, displays and swap chains (the chain of images a game draws into and shows).

Exclusive fullscreen. SetFullscreenState restyles the game's window, stretches it over a display and may change the display mode. None of that involves Metal, and Wine's own Direct3D (wined3d) already does it, so I ported wined3d's window-state code (LGPL, like this project). To check it, I built the Direct3D 12 half of Wine's DXGI conformance test (dxgi.c) as a new test suite, one test function per process. The test expects what Windows does, which differs from wined3d in the window styles. The layer follows Windows. 16 of its 20 tests pass. The other 4 fail for reasons outside the layer: one needs Direct3D 10, one runs into a Wine assertion, one expects Windows' compositor to report other windows covering the fullscreen one, and one hits a Wine cursor race.

HDR. macOS does HDR as EDR (extended dynamic range): a Metal layer tagged with an HDR colour space may show values brighter than SDR white, up to the screen's headroom (16× on the built-in XDR panel). So each DXGI colour space is a layer set-up (see the diagram):

  • sRGB stays SDR.
  • scRGB (linear half-float) uses the extended linear sRGB colour space.
  • HDR10 (10-bit PQ, BT.2020) uses the ITU-R 2100 PQ colour space.

The game's HDR10 mastering metadata becomes Core Animation's CAEDRMetadata, so macOS tone maps as a TV would. GetDesc1 reports each display's peak as 100 nits times its headroom: 1600 nits on the XDR panel.

How each DXGI colour space and the HDR10 metadata become the layer's set-up.SetColorSpace1 (or the format's default) → colour space; colour space → back buffer's format sRGB colour space no EDR (G22 P709 (sRGB)); colour space → RGBA16Float extended linear sRGB EDR on (G10 P709 (scRGB, FP16 only)); colour space → RGB10A2 ITU-R 2100 PQ EDR on (G2084 P2020 (HDR10, RGB10A2 only)); SetHDRMetaData(HDR10) → CAEDRMetadata HDR10 from H.265 SEI bytes; CAEDRMetadata HDR10 from H.265 SEI bytes → RGB10A2 ITU-R 2100 PQ EDR on; back buffer's format sRGB colour space no EDR → macOS compositor: colour matching, tone mapping to the screen's headroom; RGBA16Float extended linear sRGB EDR on → macOS compositor: colour matching, tone mapping to the screen's headroom; RGB10A2 ITU-R 2100 PQ EDR on → macOS compositor: colour matching, tone mapping to the screen's headroom; NSScreen EDR headroom and P3 gamut → GetDesc1: HDR10, 100 nits × headroomSetColorSpace1(or the format's default)colour spaceback buffer's formatsRGB colour spaceno EDRRGBA16Floatextended linear sRGBEDR onRGB10A2ITU-R 2100 PQEDR onSetHDRMetaData(HDR10)CAEDRMetadata HDR10from H.265 SEI bytesmacOS compositor:colour matching,tone mapping to thescreen's headroomNSScreen EDR headroomand P3 gamutGetDesc1: HDR10,100 nits × headroomG22 P709(sRGB)G10 P709(scRGB, FP16 only)G2084 P2020(HDR10, RGB10A2 only)
How each DXGI colour space and the HDR10 metadata become the layer's set-up. (Mermaid source)
Scene hdr: the first presented frame of an HDR10 swap chain (R10G10B10A2, PQ, BT.2020) as the layer's SDR preview: 100 nits is white and brighter light clips, so the log grey ramp (0.1 to 1000 nits, a tick at each decade) turns white at the 100-nit tick; the bottom band is every hue at 80 nits
Gallery scene hdr: the first presented frame of an HDR10 swap chain (R10G10B10A2, PQ, BT.2020) as the layer's SDR preview: 100 nits is white and brighter light clips, so the log grey ramp (0.1 to 1000 nits, a tick at each decade) turns white at the 100-nit tick; the bottom band is every hue at 80 nits. All scenes in the gallery

The interesting part

One Wine test, test_swapchain_present, hung now and then just after entering fullscreen. The Wine debugger put the test thread in ShowWindow of a second window. The Mac's main thread, which does all of Wine's window work, was idle, and two AppKit animation threads were waiting.

Earlier the test minimizes the window and restores it at once. With Wine's driver trace on, the "did minimize" events arrived 2.5 s later, after the window had gone fullscreen: the minimize animation was still running. A 30-line program that makes the same window calls, with no Direct3D at all, hangs at the same line in 1 or 2 of 16 runs. So the bug is in Wine's Mac driver, not the layer, and the repro is in the repository.

Numbers

  • Full run: 835 pass, 28 fail, 76 skip of 939, up from 818 of 918. Nothing newly failing. The new tests are api/hdr_colour_space and the 20 wine-dxgi tests.
  • The HDR test preview: grey steps at 1, 10 and 99 nits in the PQ ramp come out as 26, 89 and 254 in the SDR preview. That is what sRGB encoding gives for 0.01, 0.1 and 0.99 of white.
  • The hang: 6 of 8 runs of the full test before the HDR work, 4 of 8 after, and 1 or 2 of 16 runs of the repro without Direct3D.

I have not yet looked at the HDR output on the XDR panel myself: the test runs are unattended. The numbers above come from the layer's drawable read back, not from the screen.

Tests by suite at the end of this entry: 835 of 939 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton490 pass, 15 fail, 72 skip of 577
  2. vkd3d177 pass, 9 fail, 4 skip of 190
  3. apps77 pass, 0 fail, 0 skip of 77
  4. api (+1)71 pass, 0 fail, 0 skip of 71
  5. wine-dxgi (+16)16 pass, 4 fail, 0 skip of 20
  6. samples4 pass, 0 fail, 0 skip of 4