[idd] add support to return HDR metadata to clients

This commit is contained in:
Geoffrey McRae
2026-07-15 18:35:22 +10:00
committed by Geoffrey McRae
parent 11b3ac00a8
commit 0c653c1fb2
8 changed files with 233 additions and 16 deletions

View File

@@ -162,6 +162,15 @@ typedef struct KVMFRFrame
uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage) uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage)
FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS];
KVMFRFrameFlags flags; // bit field combination of FRAME_FLAG_* 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; KVMFRFrame;

View File

@@ -52,7 +52,8 @@ static inline IDDCX_WIRE_BITS_PER_COMPONENT GetWireBitsPerComponent(bool hdr)
IDDCX_WIRE_BITS_PER_COMPONENT bits = {}; IDDCX_WIRE_BITS_PER_COMPONENT bits = {};
bits.Rgb = IDDCX_BITS_PER_COMPONENT_8; bits.Rgb = IDDCX_BITS_PER_COMPONENT_8;
if (hdr) 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.YCbCr444 = IDDCX_BITS_PER_COMPONENT_NONE;
bits.YCbCr422 = IDDCX_BITS_PER_COMPONENT_NONE; bits.YCbCr422 = IDDCX_BITS_PER_COMPONENT_NONE;
bits.YCbCr420 = IDDCX_BITS_PER_COMPONENT_NONE; bits.YCbCr420 = IDDCX_BITS_PER_COMPONENT_NONE;
@@ -829,6 +830,35 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
++m_formatVer; ++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) if (++m_frameIndex == LGMP_Q_FRAME_LEN)
m_frameIndex = 0; m_frameIndex = 0;
@@ -850,9 +880,6 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
(dstFormat.hdr ? FRAME_FLAG_HDR : 0) | (dstFormat.hdr ? FRAME_FLAG_HDR : 0) |
(dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); (dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0);
if (dstFormat.format == FRAME_TYPE_RGBA16F)
flags |= FRAME_FLAG_HDR;
if (maxRows < dstFormat.desc.Height) if (maxRows < dstFormat.desc.Height)
flags |= FRAME_FLAG_TRUNCATED; flags |= FRAME_FLAG_TRUNCATED;
@@ -870,6 +897,18 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
fi->flags = flags; fi->flags = flags;
fi->rotation = FRAME_ROT_0; fi->rotation = FRAME_ROT_0;
fi->type = dstFormat.format; 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; fi->damageRectsCount = 0;
if (nbDirtyRects <= ARRAYSIZE(fi->damageRects)) 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 void CIndirectDeviceContext::ResendCursor() const
{ {
PLGMPMemory mem = m_pointerShape; PLGMPMemory mem = m_pointerShape;

View File

@@ -90,6 +90,27 @@ private:
UINT m_iddCxVersion = 0; UINT m_iddCxVersion = 0;
bool m_canProcessFP16 = false; 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(); void QueryIddCxCapabilities();
bool CanUseIddCx110DDIs() const { return m_canProcessFP16; } bool CanUseIddCx110DDIs() const { return m_canProcessFP16; }
@@ -156,6 +177,22 @@ public:
void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data); 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; } CIVSHMEM &GetIVSHMEM() { return m_ivshmem; }
}; };

View File

@@ -41,6 +41,19 @@ struct D12FrameFormat
FrameType format = FRAME_TYPE_INVALID; FrameType format = FRAME_TYPE_INVALID;
bool hdr = false; bool hdr = false;
bool hdrPQ = 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 class CPostProcessEffect

View File

@@ -366,8 +366,51 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
srcFormat.width = (unsigned)srcDesc.Width; srcFormat.width = (unsigned)srcDesc.Width;
srcFormat.height = srcDesc.Height; srcFormat.height = srcDesc.Height;
srcFormat.format = GetFrameType(srcDesc.Format); 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; bool postProcessFormatChanged = false;
if (!m_postProcessor.Configure(srcFormat, &postProcessFormatChanged)) if (!m_postProcessor.Configure(srcFormat, &postProcessFormatChanged))

View File

@@ -165,8 +165,11 @@ NTSTATUS LGIddAdapterCommitModes2(IDDCX_ADAPTER adapter, const IDARG_IN_COMMITMO
NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor, NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor,
const IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA* inArgs) const IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA* inArgs)
{ {
UNREFERENCED_PARAMETER(monitor); auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor);
UNREFERENCED_PARAMETER(inArgs); auto* ctx = wrapper->context->GetDeviceContext();
ctx->SetHDRActive(inArgs->pHdrMetaData);
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }

View File

@@ -36,14 +36,24 @@ bool CHDR16to10Effect::Init(const ComPtr<ID3D12Device3>& device)
const char * shader = const char * shader =
"cbuffer Constants : register(b0)\n" "cbuffer Constants : register(b0)\n"
"{\n" "{\n"
" float SDRWhiteLevel;\n" " float ReferenceWhiteNits;\n"
"};\n" "};\n"
"Texture2D<float4> src : register(t0);\n" "Texture2D<float4> src : register(t0);\n"
"RWTexture2D<float4> dst : register(u0);\n" "RWTexture2D<float4> 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" "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n"
"void main(uint3 dt : SV_DispatchThreadID)\n" "void main(uint3 dt : SV_DispatchThreadID)\n"
"{\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"; "}\n";
if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader)) 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_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads;
m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads;
dst.desc = desc; dst.desc = desc;
dst.format = FRAME_TYPE_RGBA10; dst.format = FRAME_TYPE_RGBA10;
dst.hdr = true; dst.hdr = true;
dst.hdrPQ = false; 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; return PostProcessStatus::SUCCESS;
} }

View File

@@ -18,8 +18,8 @@ class CHDR16to10Effect : public CComputeEffect
private: private:
struct Consts struct Consts
{ {
float SDRWhiteLevel; float ReferenceWhiteNits; // scRGB reference white in nits (typically 80)
} m_consts = { 1.0f }; } m_consts = { 80.0f };
ComPtr<ID3D12Resource> m_constBuffer; ComPtr<ID3D12Resource> m_constBuffer;
public: public: