From 0c653c1fb298462710b0103e7c5738a57619f25b Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 15 Jul 2026 18:35:22 +1000 Subject: [PATCH] [idd] add support to return HDR metadata to clients --- common/include/common/KVMFR.h | 9 +++ idd/LGIdd/CIndirectDeviceContext.cpp | 102 ++++++++++++++++++++++++-- idd/LGIdd/CIndirectDeviceContext.h | 37 ++++++++++ idd/LGIdd/CPostProcessor.h | 13 ++++ idd/LGIdd/CSwapChainProcessor.cpp | 47 +++++++++++- idd/LGIdd/Device.cpp | 7 +- idd/LGIdd/effect/CHDR16to10Effect.cpp | 30 ++++++-- idd/LGIdd/effect/CHDR16to10Effect.h | 4 +- 8 files changed, 233 insertions(+), 16 deletions(-) diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 0b9ef6ab..01c12f29 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -162,6 +162,15 @@ typedef struct KVMFRFrame uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage) FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; KVMFRFrameFlags flags; // bit field combination of FRAME_FLAG_* + + // HDR static metadata (valid when FRAME_FLAG_HDR is set) + // Display color primaries in 0.00002 units (SMPTE ST 2086 format) + uint16_t hdrDisplayPrimary[3][2]; // Rx,Ry, Gx,Gy, Bx,By + uint16_t hdrWhitePoint[2]; // Wx, Wy + uint32_t hdrMaxDisplayLuminance; // Max mastering display luminance (0.0001 cd/m²) + uint32_t hdrMinDisplayLuminance; // Min mastering display luminance (0.0001 cd/m²) + uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) + uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) } KVMFRFrame; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 33752c66..191f483d 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -52,7 +52,8 @@ static inline IDDCX_WIRE_BITS_PER_COMPONENT GetWireBitsPerComponent(bool hdr) IDDCX_WIRE_BITS_PER_COMPONENT bits = {}; bits.Rgb = IDDCX_BITS_PER_COMPONENT_8; if (hdr) - bits.Rgb = (IDDCX_BITS_PER_COMPONENT)(bits.Rgb | IDDCX_BITS_PER_COMPONENT_10); + bits.Rgb = (IDDCX_BITS_PER_COMPONENT)(bits.Rgb | + IDDCX_BITS_PER_COMPONENT_10 | IDDCX_BITS_PER_COMPONENT_16); bits.YCbCr444 = IDDCX_BITS_PER_COMPONENT_NONE; bits.YCbCr422 = IDDCX_BITS_PER_COMPONENT_NONE; bits.YCbCr420 = IDDCX_BITS_PER_COMPONENT_NONE; @@ -188,7 +189,7 @@ void CIndirectDeviceContext::InitAdapter() dxgiAdapter->Release(); if ((adapterDesc.VendorId == 0x1414 && adapterDesc.DeviceId == 0x008c) || // Microsoft Basic Render Driver - (adapterDesc.VendorId == 0x1b36 && adapterDesc.DeviceId == 0x000d) || // QXL + (adapterDesc.VendorId == 0x1b36 && adapterDesc.DeviceId == 0x000d) || // QXL (adapterDesc.VendorId == 0x1234 && adapterDesc.DeviceId == 0x1111)) // QEMU Standard VGA continue; @@ -829,6 +830,35 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame ++m_formatVer; } + // Detect HDR metadata changes that require a format version bump + // so the client knows to re-apply the HDR image description. + if (srcFormat.hdr) + { + if (!m_lastHDRActive || + memcmp(m_lastHDRDisplayPrimary, srcFormat.displayPrimary, sizeof(m_lastHDRDisplayPrimary)) != 0 || + memcmp(m_lastHDRWhitePoint , srcFormat.whitePoint , sizeof(m_lastHDRWhitePoint )) != 0 || + m_lastHDRMaxDisplayLuminance != srcFormat.maxDisplayLuminance || + m_lastHDRMinDisplayLuminance != srcFormat.minDisplayLuminance || + m_lastHDRMaxContentLightLevel != srcFormat.maxContentLightLevel || + m_lastHDRMaxFrameAverageLightLevel != srcFormat.maxFrameAverageLightLevel) + { + ++m_formatVer; + } + } + else if (m_lastHDRActive) + { + // HDR was turned off + ++m_formatVer; + } + + m_lastHDRActive = srcFormat.hdr; + memcpy(m_lastHDRDisplayPrimary, srcFormat.displayPrimary, sizeof(m_lastHDRDisplayPrimary)); + memcpy(m_lastHDRWhitePoint , srcFormat.whitePoint , sizeof(m_lastHDRWhitePoint )); + m_lastHDRMaxDisplayLuminance = srcFormat.maxDisplayLuminance; + m_lastHDRMinDisplayLuminance = srcFormat.minDisplayLuminance; + m_lastHDRMaxContentLightLevel = srcFormat.maxContentLightLevel; + m_lastHDRMaxFrameAverageLightLevel = srcFormat.maxFrameAverageLightLevel; + if (++m_frameIndex == LGMP_Q_FRAME_LEN) m_frameIndex = 0; @@ -850,9 +880,6 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame (dstFormat.hdr ? FRAME_FLAG_HDR : 0) | (dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); - if (dstFormat.format == FRAME_TYPE_RGBA16F) - flags |= FRAME_FLAG_HDR; - if (maxRows < dstFormat.desc.Height) flags |= FRAME_FLAG_TRUNCATED; @@ -870,6 +897,18 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame fi->flags = flags; fi->rotation = FRAME_ROT_0; fi->type = dstFormat.format; + + // Populate HDR metadata if the frame is HDR + if (flags & FRAME_FLAG_HDR) + { + memcpy(fi->hdrDisplayPrimary, srcFormat.displayPrimary, sizeof(fi->hdrDisplayPrimary)); + memcpy(fi->hdrWhitePoint , srcFormat.whitePoint , sizeof(fi->hdrWhitePoint)); + fi->hdrMaxDisplayLuminance = srcFormat.maxDisplayLuminance; + fi->hdrMinDisplayLuminance = srcFormat.minDisplayLuminance; + fi->hdrMaxContentLightLevel = srcFormat.maxContentLightLevel; + fi->hdrMaxFrameAverageLightLevel = srcFormat.maxFrameAverageLightLevel; + } + fi->damageRectsCount = 0; if (nbDirtyRects <= ARRAYSIZE(fi->damageRects)) { @@ -984,6 +1023,59 @@ void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, co } } +void CIndirectDeviceContext::SetHDRActive(const struct IDDCX_HDR_METADATA * hdrMeta) +{ + AcquireSRWLockExclusive(&m_hdrLock); + + if (!hdrMeta) + { + m_hdrActive = false; + ReleaseSRWLockExclusive(&m_hdrLock); + return; + } + + auto& hdr10 = hdrMeta->Hdr10; + + m_hdrActive = true; + + m_hdrDisplayPrimary[0][0] = hdr10.RedPrimary [0]; + m_hdrDisplayPrimary[0][1] = hdr10.RedPrimary [1]; + m_hdrDisplayPrimary[1][0] = hdr10.GreenPrimary[0]; + m_hdrDisplayPrimary[1][1] = hdr10.GreenPrimary[1]; + m_hdrDisplayPrimary[2][0] = hdr10.BluePrimary [0]; + m_hdrDisplayPrimary[2][1] = hdr10.BluePrimary [1]; + m_hdrWhitePoint [0] = hdr10.WhitePoint [0]; + m_hdrWhitePoint [1] = hdr10.WhitePoint [1]; + + m_hdrMaxDisplayLuminance = hdr10.MaxMasteringLuminance; + m_hdrMinDisplayLuminance = hdr10.MinMasteringLuminance; + m_hdrMaxContentLightLevel = hdr10.MaxContentLightLevel; + m_hdrMaxFrameAverageLightLevel = hdr10.MaxFrameAverageLightLevel; + + ReleaseSRWLockExclusive(&m_hdrLock); +} + +bool CIndirectDeviceContext::GetHDRMetadata(D12FrameFormat & format) const +{ + AcquireSRWLockShared(&m_hdrLock); + + if (!m_hdrActive) + { + ReleaseSRWLockShared(&m_hdrLock); + return false; + } + + memcpy(format.displayPrimary, m_hdrDisplayPrimary, sizeof(format.displayPrimary)); + memcpy(format.whitePoint , m_hdrWhitePoint , sizeof(format.whitePoint )); + format.maxDisplayLuminance = m_hdrMaxDisplayLuminance; + format.minDisplayLuminance = m_hdrMinDisplayLuminance; + format.maxContentLightLevel = m_hdrMaxContentLightLevel; + format.maxFrameAverageLightLevel = m_hdrMaxFrameAverageLightLevel; + + ReleaseSRWLockShared(&m_hdrLock); + return true; +} + void CIndirectDeviceContext::ResendCursor() const { PLGMPMemory mem = m_pointerShape; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 2a8bf7ec..4a1f7b38 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -90,6 +90,27 @@ private: UINT m_iddCxVersion = 0; bool m_canProcessFP16 = false; + // HDR state from EvtIddCxMonitorSetDefaultHdrMetadata (IddCx 1.10+) + // Protected by m_hdrLock - accessed from both the IDD callback thread + // (SetHDRActive) and the swap-chain thread (PrepareFrameBuffer, GetHDRMetadata). + mutable SRWLOCK m_hdrLock = SRWLOCK_INIT; + bool m_hdrActive = false; + uint16_t m_hdrDisplayPrimary[3][2] = {}; + uint16_t m_hdrWhitePoint[2] = {}; + uint32_t m_hdrMaxDisplayLuminance = 0; + uint32_t m_hdrMinDisplayLuminance = 0; + uint32_t m_hdrMaxContentLightLevel = 0; + uint32_t m_hdrMaxFrameAverageLightLevel = 0; + + // Previous HDR metadata used to detect changes for formatVer bumps + uint16_t m_lastHDRDisplayPrimary[3][2] = {}; + uint16_t m_lastHDRWhitePoint[2] = {}; + uint32_t m_lastHDRMaxDisplayLuminance = 0; + uint32_t m_lastHDRMinDisplayLuminance = 0; + uint32_t m_lastHDRMaxContentLightLevel = 0; + uint32_t m_lastHDRMaxFrameAverageLightLevel = 0; + bool m_lastHDRActive = false; + void QueryIddCxCapabilities(); bool CanUseIddCx110DDIs() const { return m_canProcessFP16; } @@ -156,6 +177,22 @@ public: void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data); + // Tracks HDR state from EvtIddCxMonitorSetDefaultHdrMetadata + void SetHDRActive(const struct IDDCX_HDR_METADATA * hdrMeta); + + // Returns true if the display is currently in HDR mode + bool IsHDRActive() const + { + AcquireSRWLockShared(&m_hdrLock); + bool result = m_hdrActive; + ReleaseSRWLockShared(&m_hdrLock); + return result; + } + + // Copies current HDR metadata into the provided D12FrameFormat. + // Returns true if HDR is active and metadata was copied. + bool GetHDRMetadata(D12FrameFormat & format) const; + CIVSHMEM &GetIVSHMEM() { return m_ivshmem; } }; diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h index dcb54289..912f741f 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/CPostProcessor.h @@ -41,6 +41,19 @@ struct D12FrameFormat FrameType format = FRAME_TYPE_INVALID; bool hdr = false; bool hdrPQ = false; + + // HDR static metadata (SMPTE ST 2086) + // Display color primaries in 0.00002 units (xy coordinates) + uint16_t displayPrimary[3][2]; + // White point in 0.00002 units + uint16_t whitePoint[2]; + // Max display luminance in 0.0001 cd/m² units + uint32_t maxDisplayLuminance; + // Min display luminance in 0.0001 cd/m² units + uint32_t minDisplayLuminance; + // MaxCLL and MaxFALL in cd/m² + uint32_t maxContentLightLevel; + uint32_t maxFrameAverageLightLevel; }; class CPostProcessEffect diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 0d7f01c2..843913d2 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -366,8 +366,51 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcFormat.width = (unsigned)srcDesc.Width; srcFormat.height = srcDesc.Height; srcFormat.format = GetFrameType(srcDesc.Format); - srcFormat.hdr = srcDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT; - srcFormat.hdrPQ = false; + + // Determine HDR status from both format and color space. + // HDR metadata loading is handled inside each branch so the logic + // is self-contained: first identify the content type, then load the + // matching metadata. + if (srcDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT) + { + // FP16 is HDR content (scRGB / linear, not PQ-curve). + // FP16 always carries HDR color data regardless of OS HDR mode, + // but metadata (primaries, luminances) may be unavailable. + srcFormat.hdr = true; + srcFormat.hdrPQ = false; + if (!m_devContext->GetHDRMetadata(srcFormat)) + { + // No HDR metadata from the OS; provide reasonable defaults + // so downstream consumers have valid primaries and luminances. + // BT.709/sRGB primaries (in 0.00002 units): + srcFormat.displayPrimary[0][0] = 13250; // Rx + srcFormat.displayPrimary[0][1] = 34500; // Ry + srcFormat.displayPrimary[1][0] = 7500; // Gx + srcFormat.displayPrimary[1][1] = 30000; // Gy + srcFormat.displayPrimary[2][0] = 34000; // Bx + srcFormat.displayPrimary[2][1] = 16000; // By + // D65 white point (in 0.00002 units): + srcFormat.whitePoint[0] = 15635; + srcFormat.whitePoint[1] = 16450; + // 80 cd/m² display, 0.005 cd/m² black (in 0.0001 cd/m² units): + srcFormat.maxDisplayLuminance = 800000; + srcFormat.minDisplayLuminance = 50; + // Content light levels unknown: + srcFormat.maxContentLightLevel = 0; + srcFormat.maxFrameAverageLightLevel = 0; + } + } + else if (m_devContext->CanProcessFP16() && m_devContext->IsHDRActive()) + { + // Non-FP16 format (e.g., RGBA10) with OS HDR mode active. + // Windows applies the PQ (ST.2084) transfer function for non-FP16 HDR. + srcFormat.hdr = true; + srcFormat.hdrPQ = true; + + // Load HDR metadata; if none is available the frame is not HDR. + if (!m_devContext->GetHDRMetadata(srcFormat)) + srcFormat.hdr = false; + } bool postProcessFormatChanged = false; if (!m_postProcessor.Configure(srcFormat, &postProcessFormatChanged)) diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index f20753de..fe57799d 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -165,8 +165,11 @@ NTSTATUS LGIddAdapterCommitModes2(IDDCX_ADAPTER adapter, const IDARG_IN_COMMITMO NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor, const IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA* inArgs) { - UNREFERENCED_PARAMETER(monitor); - UNREFERENCED_PARAMETER(inArgs); + auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); + auto* ctx = wrapper->context->GetDeviceContext(); + + ctx->SetHDRActive(inArgs->pHdrMetaData); + return STATUS_SUCCESS; } diff --git a/idd/LGIdd/effect/CHDR16to10Effect.cpp b/idd/LGIdd/effect/CHDR16to10Effect.cpp index d8f8b348..d85b5395 100644 --- a/idd/LGIdd/effect/CHDR16to10Effect.cpp +++ b/idd/LGIdd/effect/CHDR16to10Effect.cpp @@ -36,14 +36,24 @@ bool CHDR16to10Effect::Init(const ComPtr& device) const char * shader = "cbuffer Constants : register(b0)\n" "{\n" - " float SDRWhiteLevel;\n" + " float ReferenceWhiteNits;\n" "};\n" "Texture2D src : register(t0);\n" "RWTexture2D dst : register(u0);\n" + "static const float PQ_m1 = 0.1593017578125;\n" + "static const float PQ_m2 = 78.84375;\n" + "static const float PQ_c1 = 0.8359375;\n" + "static const float PQ_c2 = 18.8515625;\n" + "static const float PQ_c3 = 18.6875;\n" "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n" "void main(uint3 dt : SV_DispatchThreadID)\n" "{\n" - " dst[dt.xy] = float4(src[dt.xy].rgb * SDRWhiteLevel, src[dt.xy].a);\n" + " float3 linear = src[dt.xy].rgb * ReferenceWhiteNits;\n" + " // scRGB to PQ (ST.2084)\n" + " float3 Y = linear / 10000.0;\n" + " float3 Ym1 = pow(max(Y, 0.0), PQ_m1);\n" + " float3 pq = pow((PQ_c1 + PQ_c2 * Ym1) / (1.0 + PQ_c3 * Ym1), PQ_m2);\n" + " dst[dt.xy] = float4(pq, src[dt.xy].a);\n" "}\n"; if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader)) @@ -99,10 +109,20 @@ PostProcessStatus CHDR16to10Effect::SetFormat( m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; - dst.desc = desc; + dst.desc = desc; dst.format = FRAME_TYPE_RGBA10; - dst.hdr = true; - dst.hdrPQ = false; + dst.hdr = true; + dst.hdrPQ = true; + + // Explicitly propagate HDR static metadata so it survives post-processing. + // Do not rely on the caller's copy semantics in CPostProcessor::Configure(). + memcpy(dst.displayPrimary, src.displayPrimary, sizeof(dst.displayPrimary)); + memcpy(dst.whitePoint , src.whitePoint , sizeof(dst.whitePoint )); + dst.maxDisplayLuminance = src.maxDisplayLuminance; + dst.minDisplayLuminance = src.minDisplayLuminance; + dst.maxContentLightLevel = src.maxContentLightLevel; + dst.maxFrameAverageLightLevel = src.maxFrameAverageLightLevel; + return PostProcessStatus::SUCCESS; } diff --git a/idd/LGIdd/effect/CHDR16to10Effect.h b/idd/LGIdd/effect/CHDR16to10Effect.h index 45001522..cf701f73 100644 --- a/idd/LGIdd/effect/CHDR16to10Effect.h +++ b/idd/LGIdd/effect/CHDR16to10Effect.h @@ -18,8 +18,8 @@ class CHDR16to10Effect : public CComputeEffect private: struct Consts { - float SDRWhiteLevel; - } m_consts = { 1.0f }; + float ReferenceWhiteNits; // scRGB reference white in nits (typically 80) + } m_consts = { 80.0f }; ComPtr m_constBuffer; public: