diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 82b1edd5..26354e34 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -28,7 +28,7 @@ #include "types.h" #define KVMFR_MAGIC "KVMFR---" -#define KVMFR_VERSION 22 +#define KVMFR_VERSION 23 // Fallback used by producers that cannot report the source display's SDR // white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. @@ -54,7 +54,10 @@ enum CURSOR_FLAG_POSITION = 0x1, CURSOR_FLAG_VISIBLE = 0x2, CURSOR_FLAG_SHAPE = 0x4, - CURSOR_FLAG_COLOR_TRANSFORM = 0x8 + CURSOR_FLAG_COLOR_TRANSFORM = 0x8, + // CURSOR_FLAG_VISIBLE contains a new visibility state. This distinguishes + // an invisible cursor update from messages carrying unrelated cursor data. + CURSOR_FLAG_VISIBLE_VALID = 0x10 }; typedef uint32_t KVMFRCursorFlags; @@ -135,6 +138,7 @@ typedef struct KVMFRCursor uint32_t width; // width of the shape uint32_t height; // height of the shape uint32_t pitch; // row length in bytes of the shape + uint32_t sdrWhiteLevel; // cursor white level in nits for HDR composition } KVMFRCursor; diff --git a/host/src/app.c b/host/src/app.c index a7034bdc..1528abf6 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -515,8 +515,9 @@ static void sendPointer(bool newClient) KVMFRCursor *cursor = lgmpHostMemPtr(mem); cursor->x = app.pointerInfo.x; cursor->y = app.pointerInfo.y; + cursor->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; - const uint32_t flags = CURSOR_FLAG_POSITION | + const uint32_t flags = CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE_VALID | (app.pointerShapeValid ? CURSOR_FLAG_SHAPE : 0) | (app.pointerInfo.visible ? CURSOR_FLAG_VISIBLE : 0); @@ -524,7 +525,7 @@ static void sendPointer(bool newClient) return; } - uint32_t flags = 0; + uint32_t flags = CURSOR_FLAG_VISIBLE_VALID; PLGMPMemory mem; if (app.pointerInfo.shapeUpdate) { @@ -539,6 +540,7 @@ static void sendPointer(bool newClient) app.pointerIndex = 0; } KVMFRCursor *cursor = lgmpHostMemPtr(mem); + cursor->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; if (app.pointerInfo.positionUpdate || newClient) { diff --git a/idd/LGIdd/CEdid.cpp b/idd/LGIdd/CEdid.cpp index 345212d3..dcc699b7 100644 --- a/idd/LGIdd/CEdid.cpp +++ b/idd/LGIdd/CEdid.cpp @@ -309,6 +309,41 @@ static EdidStandardTiming MakeUnusedStandardTiming() return timing; } +#include // Ensure this header is included for std::min and std::max + +static WORD EdidChromaticity(double value) +{ + return (WORD)min(1023.0, max(0.0, value * 1024.0 + 0.5)); +} + +static void SetChromaticityCoordinates(BYTE coordinates[10]) +{ + // The virtual display transports HDR in the BT.2020 container. Describe + // that full container gamut and its D65 white point rather than leaving an + // invalid all-zero base-block colour volume. + const WORD rx = EdidChromaticity(0.7080); + const WORD ry = EdidChromaticity(0.2920); + const WORD gx = EdidChromaticity(0.1700); + const WORD gy = EdidChromaticity(0.7970); + const WORD bx = EdidChromaticity(0.1310); + const WORD by = EdidChromaticity(0.0460); + const WORD wx = EdidChromaticity(0.3127); + const WORD wy = EdidChromaticity(0.3290); + + coordinates[0] = (BYTE)(((rx & 3) << 6) | ((ry & 3) << 4) | + ((gx & 3) << 2) | (gy & 3)); + coordinates[1] = (BYTE)(((bx & 3) << 6) | ((by & 3) << 4) | + ((wx & 3) << 2) | (wy & 3)); + coordinates[2] = (BYTE)(rx >> 2); + coordinates[3] = (BYTE)(ry >> 2); + coordinates[4] = (BYTE)(gx >> 2); + coordinates[5] = (BYTE)(gy >> 2); + coordinates[6] = (BYTE)(bx >> 2); + coordinates[7] = (BYTE)(by >> 2); + coordinates[8] = (BYTE)(wx >> 2); + coordinates[9] = (BYTE)(wy >> 2); +} + static void InitEdidBaseBlock(EdidBaseBlock& base) { memcpy(base.header, EDID_HEADER, sizeof(base.header)); @@ -328,6 +363,7 @@ static void InitEdidBaseBlock(EdidBaseBlock& base) base.verticalSizeCm = EDID_VERTICAL_SIZE_CM; base.displayGamma = EDID_DISPLAY_GAMMA_2_2; base.supportedFeatures = EDID_FEATURES_PREFERRED_TIMING_RGB; + SetChromaticityCoordinates(base.chromaticityCoordinates); for (UINT i = 0; i < EDID_STANDARD_TIMING_COUNT; ++i) base.standardTimings[i] = MakeUnusedStandardTiming(); @@ -612,4 +648,4 @@ void CEdid::Build(const CSettings::DisplayModes& modes) SetChecksum(cta); memcpy(m_data.data() + sizeof(baseBlock), &ctaBlock, sizeof(ctaBlock)); -} \ No newline at end of file +} diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index a632403e..d52da11a 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -1222,7 +1222,8 @@ void CIndirectDeviceContext::FinalizeFrameBuffer(unsigned frameIndex) const fb->wp = m_height * m_pitch; } -void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, const BYTE * data) +void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, + const BYTE * data, UINT sdrWhiteLevel) { PLGMPMemory mem; if (info.CursorShapeInfo.CursorType == IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED) @@ -1239,9 +1240,11 @@ void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, co } KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem); + cursor->sdrWhiteLevel = sdrWhiteLevel ? + sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT; m_cursorVisible = info.IsCursorVisible; - uint32_t flags = 0; + uint32_t flags = CURSOR_FLAG_VISIBLE_VALID; if (info.IsCursorVisible) { @@ -1416,7 +1419,7 @@ void CIndirectDeviceContext::ResendCursor() cursor->y = (int16_t)m_cursorY; const uint32_t flags = - CURSOR_FLAG_POSITION | CURSOR_FLAG_SHAPE | + CURSOR_FLAG_POSITION | CURSOR_FLAG_SHAPE | CURSOR_FLAG_VISIBLE_VALID | (m_cursorVisible ? CURSOR_FLAG_VISIBLE : 0); LGMP_STATUS status; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 563d5459..27d15f16 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -225,7 +225,8 @@ public: void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; void FinalizeFrameBuffer(unsigned frameIndex) const; - void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data); + void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data, + UINT sdrWhiteLevel); // Tracks HDR state from EvtIddCxMonitorSetDefaultHdrMetadata void SetHDRActive(const struct IDDCX_HDR10_METADATA * hdrMeta); diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index dfb853c4..d648386b 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -206,6 +206,7 @@ bool CSwapChainProcessor::SwapChainThreadCore() surface = buffer.MetaData.pSurface; colorSpace = buffer.MetaData.SurfaceColorSpace; sdrWhiteLevel = buffer.MetaData.SdrWhiteLevel; + m_sdrWhiteLevel.store(sdrWhiteLevel, std::memory_order_relaxed); UpdateHDRMetadata(buffer.MetaData); } } @@ -776,6 +777,7 @@ bool CSwapChainProcessor::QueryHWCursor() in.ShapeBufferSizeInBytes = 512 * 512 * 4; IDARG_OUT_QUERY_HWCURSOR out = {}; + UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed); NTSTATUS status; #ifdef HAS_IDDCX_110 if (m_devContext->CanProcessFP16()) @@ -787,6 +789,8 @@ bool CSwapChainProcessor::QueryHWCursor() out.Y = out3.Y; out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated; out.CursorShapeInfo = out3.CursorShapeInfo; + if (out3.SdrWhiteLevel) + cursorWhiteLevel = out3.SdrWhiteLevel; } else #endif @@ -810,7 +814,7 @@ bool CSwapChainProcessor::QueryHWCursor() if (out.IsCursorShapeUpdated) m_lastShapeId = out.CursorShapeInfo.ShapeId; - m_devContext->SendCursor(out, m_shapeBuffer); + m_devContext->SendCursor(out, m_shapeBuffer, cursorWhiteLevel); return true; } diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index c82c18f5..0b79d01b 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -30,6 +30,7 @@ #include #include #include +#include #include using namespace Microsoft::WRL; @@ -60,6 +61,7 @@ private: Wrappers::Event m_cursorDataEvent; BYTE* m_shapeBuffer; DWORD m_lastShapeId = 0; + std::atomic m_sdrWhiteLevel { KVMFR_SDR_WHITE_LEVEL_DEFAULT }; // Output-space damage from the previous published frame. The shared-memory // frame buffers alternate, so this must be copied along with the current