From 2006fb640f1e679f5b516497d244ec8b25796bb6 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 17 Jul 2026 17:49:49 +1000 Subject: [PATCH] [idd/client/obs] hdr: wire up the SdrWhiteLevel from the capture --- client/include/interface/renderer.h | 1 + client/renderers/EGL/cursor.c | 31 +++++++++++------- client/renderers/EGL/cursor.h | 3 +- client/renderers/EGL/egl.c | 3 +- client/renderers/EGL/shader/cursor_mono.frag | 3 +- client/renderers/EGL/shader/cursor_rgb.frag | 3 +- client/src/main.c | 31 ++++++++++-------- common/include/common/KVMFR.h | 10 +++++- host/src/app.c | 1 + idd/LGIdd/CIndirectDeviceContext.cpp | 5 ++- idd/LGIdd/CIndirectDeviceContext.h | 1 + idd/LGIdd/CPostProcessor.cpp | 34 +++++++++++--------- idd/LGIdd/CPostProcessor.h | 2 ++ idd/LGIdd/CSwapChainProcessor.cpp | 21 ++++++------ idd/LGIdd/CSwapChainProcessor.h | 2 +- idd/LGIdd/effect/CHDR16to10Effect.cpp | 1 + obs/lg.c | 25 ++++++++------ 17 files changed, 111 insertions(+), 66 deletions(-) diff --git a/client/include/interface/renderer.h b/client/include/interface/renderer.h index a1ad6a32..a1b1dc4e 100644 --- a/client/include/interface/renderer.h +++ b/client/include/interface/renderer.h @@ -92,6 +92,7 @@ typedef struct LG_RendererFormat uint32_t hdrMinDisplayLuminance; uint32_t hdrMaxContentLightLevel; uint32_t hdrMaxFrameAverageLightLevel; + uint32_t sdrWhiteLevel; } LG_RendererFormat; diff --git a/client/renderers/EGL/cursor.c b/client/renderers/EGL/cursor.c index c8d7a777..079fad57 100644 --- a/client/renderers/EGL/cursor.c +++ b/client/renderers/EGL/cursor.c @@ -46,6 +46,7 @@ struct CursorTex GLuint uRotate; GLuint uCBMode; GLint uMapSDRtoPQ; + GLint uSDRWhiteLevel; }; struct CursorPos @@ -79,6 +80,7 @@ struct EGL_Cursor _Atomic(struct CursorSize) size; _Atomic(float) scale; _Atomic(bool) mapSDRtoPQ; + _Atomic(float) sdrWhiteLevel; struct CursorTex norm; struct CursorTex mono; @@ -108,11 +110,12 @@ static bool cursorTexInit(struct CursorTex * t, return false; } - t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" ); - t->uScale = egl_shaderGetUniform(t->shader, "scale" ); - t->uRotate = egl_shaderGetUniform(t->shader, "rotate" ); - t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" ); - t->uMapSDRtoPQ = egl_shaderGetUniform(t->shader, "mapSDRtoPQ"); + t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" ); + t->uScale = egl_shaderGetUniform(t->shader, "scale" ); + t->uRotate = egl_shaderGetUniform(t->shader, "rotate" ); + t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" ); + t->uMapSDRtoPQ = egl_shaderGetUniform(t->shader, "mapSDRtoPQ" ); + t->uSDRWhiteLevel = egl_shaderGetUniform(t->shader, "sdrWhiteLevel"); return true; } @@ -121,11 +124,12 @@ static inline void setCursorTexUniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono, float x, float y, float w, float h, float scale) { - glUniform4f(t->uMousePos , x, y, w, mono ? h / 2 : h); - glUniform1f(t->uScale , scale); - glUniform1i(t->uRotate , cursor->rotate); - glUniform1i(t->uCBMode , cursor->cbMode); - glUniform1i(t->uMapSDRtoPQ, !mono && atomic_load(&cursor->mapSDRtoPQ)); + glUniform4f(t->uMousePos , x, y, w, mono ? h / 2 : h); + glUniform1f(t->uScale , scale); + glUniform1i(t->uRotate , cursor->rotate); + glUniform1i(t->uCBMode , cursor->cbMode); + glUniform1i(t->uMapSDRtoPQ , !mono && atomic_load(&cursor->mapSDRtoPQ)); + glUniform1f(t->uSDRWhiteLevel, atomic_load(&cursor->sdrWhiteLevel)); } static void cursorTexFree(struct CursorTex * t) @@ -174,6 +178,8 @@ bool egl_cursorInit(EGL_Cursor ** cursor) atomic_init(&(*cursor)->size , size ); atomic_init(&(*cursor)->scale , 1.0f ); atomic_init(&(*cursor)->mapSDRtoPQ, false); + atomic_init(&(*cursor)->sdrWhiteLevel, + (float)KVMFR_SDR_WHITE_LEVEL_DEFAULT); return true; } @@ -435,7 +441,10 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor, return state; } -void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ) +void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ, + float sdrWhiteLevel) { + atomic_store(&cursor->sdrWhiteLevel, sdrWhiteLevel > 0.0f ? + sdrWhiteLevel : (float)KVMFR_SDR_WHITE_LEVEL_DEFAULT); atomic_store(&cursor->mapSDRtoPQ, hdrActive && hdrPQ); } diff --git a/client/renderers/EGL/cursor.h b/client/renderers/EGL/cursor.h index 74d62f92..0fd85b41 100644 --- a/client/renderers/EGL/cursor.h +++ b/client/renderers/EGL/cursor.h @@ -53,4 +53,5 @@ void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, struct CursorState egl_cursorRender(EGL_Cursor * cursor, LG_RendererRotate rotate, int width, int height); -void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ); +void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ, + float sdrWhiteLevel); diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 47fc756a..0b8cd2b8 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -631,7 +631,8 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo egl_desktopSetNativeHDR(this->desktop, useNativeHDR); // Tell the cursor shader whether to map SDR cursor colors into PQ - egl_cursorSetHDRState(this->cursor, useNativeHDR, format.hdrPQ); + egl_cursorSetHDRState(this->cursor, useNativeHDR, format.hdrPQ, + format.sdrWhiteLevel); egl_update_scale_type(this); egl_damageSetup(this->damage, format.frameWidth, format.frameHeight); diff --git a/client/renderers/EGL/shader/cursor_mono.frag b/client/renderers/EGL/shader/cursor_mono.frag index bb2d83f3..695b09f6 100644 --- a/client/renderers/EGL/shader/cursor_mono.frag +++ b/client/renderers/EGL/shader/cursor_mono.frag @@ -10,6 +10,7 @@ out vec4 color; uniform sampler2D sampler1; uniform float scale; uniform bool mapSDRtoPQ; +uniform float sdrWhiteLevel; void main() { @@ -32,7 +33,7 @@ void main() if (mapSDRtoPQ) { vec3 linear = bt709to2020(srgb2lin(tmp.rgb)); - tmp.rgb = lin2pq(linear * (203.0 / 10000.0)); + tmp.rgb = lin2pq(linear * (sdrWhiteLevel / 10000.0)); } color = tmp; diff --git a/client/renderers/EGL/shader/cursor_rgb.frag b/client/renderers/EGL/shader/cursor_rgb.frag index f6754bf0..9be30404 100644 --- a/client/renderers/EGL/shader/cursor_rgb.frag +++ b/client/renderers/EGL/shader/cursor_rgb.frag @@ -12,6 +12,7 @@ uniform sampler2D sampler1; uniform float scale; uniform int cbMode; uniform bool mapSDRtoPQ; +uniform float sdrWhiteLevel; void main() { @@ -38,7 +39,7 @@ void main() // then premultiply again so the blend operation remains valid. vec3 srgb = clamp(color.rgb / color.a, 0.0, 1.0); vec3 linear = bt709to2020(srgb2lin(srgb)); - color.rgb = lin2pq(linear * (203.0 / 10000.0)) * color.a; + color.rgb = lin2pq(linear * (sdrWhiteLevel / 10000.0)) * color.a; } else color.rgb = vec3(0.0); diff --git a/client/src/main.c b/client/src/main.c index bedec53e..2aa2438a 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -629,17 +629,19 @@ int main_frameThread(void * unused) if (!g_state.formatValid || frame->formatVer != formatVer) { // setup the renderer format with the frame format details - lgrFormat.type = frame->type; - lgrFormat.screenWidth = frame->screenWidth; - lgrFormat.screenHeight = frame->screenHeight; - lgrFormat.dataWidth = frame->dataWidth; - lgrFormat.dataHeight = frame->dataHeight; - lgrFormat.frameWidth = frame->frameWidth; - lgrFormat.frameHeight = frame->frameHeight; - lgrFormat.stride = frame->stride; - lgrFormat.pitch = frame->pitch; - lgrFormat.hdr = frame->flags & FRAME_FLAG_HDR; - lgrFormat.hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ; + lgrFormat.type = frame->type; + lgrFormat.screenWidth = frame->screenWidth; + lgrFormat.screenHeight = frame->screenHeight; + lgrFormat.dataWidth = frame->dataWidth; + lgrFormat.dataHeight = frame->dataHeight; + lgrFormat.frameWidth = frame->frameWidth; + lgrFormat.frameHeight = frame->frameHeight; + lgrFormat.stride = frame->stride; + lgrFormat.pitch = frame->pitch; + lgrFormat.hdr = frame->flags & FRAME_FLAG_HDR; + lgrFormat.hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ; + lgrFormat.sdrWhiteLevel = frame->sdrWhiteLevel ? + frame->sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT; if (lgrFormat.hdr) { @@ -728,14 +730,15 @@ int main_frameThread(void * unused) g_state.formatValid = true; formatVer = frame->formatVer; - DEBUG_INFO("Format: %s %ux%u (%ux%u) stride:%u pitch:%u rotation:%d hdr:%d pq:%d", + DEBUG_INFO("Format: %s %ux%u (%ux%u) stride:%u pitch:%u rotation:%d hdr:%d pq:%d sdrWhite:%u nits", FrameTypeStr[frame->type], frame->frameWidth, frame->frameHeight, frame->dataWidth , frame->dataHeight , frame->stride, frame->pitch, frame->rotation, frame->flags & FRAME_FLAG_HDR ? 1 : 0, - frame->flags & FRAME_FLAG_HDR_PQ ? 1 : 0); + frame->flags & FRAME_FLAG_HDR_PQ ? 1 : 0, + lgrFormat.sdrWhiteLevel); LG_LOCK(g_state.lgrLock); if (!RENDERER(onFrameFormat, lgrFormat)) @@ -1533,7 +1536,7 @@ restart: if (waitCount == 30) { DEBUG_BREAK(); - if (!g_params.disableWaitingMessage) + if (!g_params.disableWaitingMessage) { msgs[msgsCount++] = app_msgBox( "Host Application Not Running", diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 29190f7a..1929c7bc 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -28,7 +28,11 @@ #include "types.h" #define KVMFR_MAGIC "KVMFR---" -#define KVMFR_VERSION 20 +#define KVMFR_VERSION 21 + +// Fallback used by producers that cannot report the source display's SDR +// white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. +#define KVMFR_SDR_WHITE_LEVEL_DEFAULT 203u #define KVMFR_MAX_DAMAGE_RECTS 64 @@ -175,6 +179,10 @@ typedef struct KVMFRFrame uint32_t hdrMinDisplayLuminance; // Min mastering display luminance (0.0001 cd/m²) uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) + + // White level in nits for SDR content composited into an HDR frame, such as + // the hardware cursor. Valid for all frames; SDR modes normally report 80. + uint32_t sdrWhiteLevel; } KVMFRFrame; diff --git a/host/src/app.c b/host/src/app.c index 7663bb41..a7034bdc 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -347,6 +347,7 @@ static bool sendFrame(CaptureResult result, bool * restart) fi->pitch = frame.pitch; // fi->offset is initialized at startup fi->flags = flags; + fi->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; fi->damageRectsCount = frame.damageRectsCount; memcpy(fi->damageRects, frame.damageRects, frame.damageRectsCount * sizeof(FrameDamageRect)); diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 82087238..3cb5f8ab 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -957,7 +957,8 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame m_lastHDRMaxDisplayLuminance != dstFormat.maxDisplayLuminance || m_lastHDRMinDisplayLuminance != dstFormat.minDisplayLuminance || m_lastHDRMaxContentLightLevel != dstFormat.maxContentLightLevel || - m_lastHDRMaxFrameAverageLightLevel != dstFormat.maxFrameAverageLightLevel) + m_lastHDRMaxFrameAverageLightLevel != dstFormat.maxFrameAverageLightLevel || + m_lastSDRWhiteLevel != dstFormat.sdrWhiteLevel) { ++m_formatVer; } @@ -975,6 +976,7 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame m_lastHDRMinDisplayLuminance = dstFormat.minDisplayLuminance; m_lastHDRMaxContentLightLevel = dstFormat.maxContentLightLevel; m_lastHDRMaxFrameAverageLightLevel = dstFormat.maxFrameAverageLightLevel; + m_lastSDRWhiteLevel = dstFormat.sdrWhiteLevel; if (++m_frameIndex == LGMP_Q_FRAME_LEN) m_frameIndex = 0; @@ -1012,6 +1014,7 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame fi->pitch = pitch; // fi->offset is initialized at startup fi->flags = flags; + fi->sdrWhiteLevel = dstFormat.sdrWhiteLevel; fi->rotation = FRAME_ROT_0; fi->type = dstFormat.format; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index a07b974c..602fb1ae 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -129,6 +129,7 @@ private: uint32_t m_lastHDRMinDisplayLuminance = 0; uint32_t m_lastHDRMaxContentLightLevel = 0; uint32_t m_lastHDRMaxFrameAverageLightLevel = 0; + uint32_t m_lastSDRWhiteLevel = 0; bool m_lastHDRActive = false; void QueryIddCxCapabilities(); diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/CPostProcessor.cpp index 44f8a61e..1c2c68ba 100644 --- a/idd/LGIdd/CPostProcessor.cpp +++ b/idd/LGIdd/CPostProcessor.cpp @@ -66,14 +66,15 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha if (formatChanged) *formatChanged = false; - if (srcFormat.desc.Width == m_srcFormat.desc.Width && - srcFormat.desc.Height == m_srcFormat.desc.Height && - srcFormat.desc.Format == m_srcFormat.desc.Format && - srcFormat.format == m_srcFormat.format && - srcFormat.width == m_srcFormat.width && - srcFormat.height == m_srcFormat.height && - srcFormat.hdr == m_srcFormat.hdr && - srcFormat.hdrPQ == m_srcFormat.hdrPQ) + if (srcFormat.desc.Width == m_srcFormat.desc.Width && + srcFormat.desc.Height == m_srcFormat.desc.Height && + srcFormat.desc.Format == m_srcFormat.desc.Format && + srcFormat.format == m_srcFormat.format && + srcFormat.width == m_srcFormat.width && + srcFormat.height == m_srcFormat.height && + srcFormat.hdr == m_srcFormat.hdr && + srcFormat.hdrPQ == m_srcFormat.hdrPQ && + srcFormat.sdrWhiteLevel == m_srcFormat.sdrWhiteLevel) return true; D12FrameFormat oldDst = m_dstFormat; @@ -105,14 +106,15 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha m_dstFormat = cur; if (formatChanged) - *formatChanged = oldDst.desc.Width != m_dstFormat.desc.Width || - oldDst.desc.Height != m_dstFormat.desc.Height || - oldDst.desc.Format != m_dstFormat.desc.Format || - oldDst.format != m_dstFormat.format || - oldDst.width != m_dstFormat.width || - oldDst.height != m_dstFormat.height || - oldDst.hdr != m_dstFormat.hdr || - oldDst.hdrPQ != m_dstFormat.hdrPQ; + *formatChanged = oldDst.desc.Width != m_dstFormat.desc.Width || + oldDst.desc.Height != m_dstFormat.desc.Height || + oldDst.desc.Format != m_dstFormat.desc.Format || + oldDst.format != m_dstFormat.format || + oldDst.width != m_dstFormat.width || + oldDst.height != m_dstFormat.height || + oldDst.hdr != m_dstFormat.hdr || + oldDst.hdrPQ != m_dstFormat.hdrPQ || + oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel; return true; } diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h index 912f741f..e1999abf 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/CPostProcessor.h @@ -21,6 +21,7 @@ struct CD3D12Device; extern "C" { + #include "common/KVMFR.h" #include "common/types.h" } @@ -41,6 +42,7 @@ struct D12FrameFormat FrameType format = FRAME_TYPE_INVALID; bool hdr = false; bool hdrPQ = false; + uint32_t sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; // HDR static metadata (SMPTE ST 2086) // Display color primaries in 0.00002 units (xy coordinates) diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 53d5b307..e69e9817 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -165,6 +165,7 @@ bool CSwapChainProcessor::SwapChainThreadCore() // Only the buffer2 acquisition path (IddCx 1.10+) reports it; on the legacy // path HDR is not available, so default to SDR. DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + UINT sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; #ifdef HAS_IDDCX_110 if (m_devContext->CanProcessFP16()) @@ -179,10 +180,11 @@ bool CSwapChainProcessor::SwapChainThreadCore() hr = IddCxSwapChainReleaseAndAcquireBuffer2(m_hSwapChain, &acquireIn, &buffer); if (SUCCEEDED(hr)) { - frameNumber = buffer.MetaData.PresentationFrameNumber; + frameNumber = buffer.MetaData.PresentationFrameNumber; dirtyRectCount = buffer.MetaData.DirtyRectCount; - surface = buffer.MetaData.pSurface; - colorSpace = buffer.MetaData.SurfaceColorSpace; + surface = buffer.MetaData.pSurface; + colorSpace = buffer.MetaData.SurfaceColorSpace; + sdrWhiteLevel = buffer.MetaData.SdrWhiteLevel; } } else @@ -222,7 +224,7 @@ bool CSwapChainProcessor::SwapChainThreadCore() if (frameNumber != lastFrameNumber) { lastFrameNumber = frameNumber; - SwapChainNewFrame(surface, dirtyRectCount, colorSpace); + SwapChainNewFrame(surface, dirtyRectCount, colorSpace, sdrWhiteLevel); // report that all GPU processing for this frame has been queued hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain); @@ -334,7 +336,7 @@ static FrameType GetFrameType(DXGI_FORMAT format) } bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, - DXGI_COLOR_SPACE_TYPE colorSpace) + DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel) { ComPtr texture; HRESULT hr = acquiredBuffer.As(&texture); @@ -382,10 +384,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); D12FrameFormat srcFormat = {}; - srcFormat.desc = srcDesc; - srcFormat.width = (unsigned)srcDesc.Width; - srcFormat.height = srcDesc.Height; - srcFormat.format = GetFrameType(srcDesc.Format); + srcFormat.desc = srcDesc; + srcFormat.width = (unsigned)srcDesc.Width; + srcFormat.height = srcDesc.Height; + srcFormat.format = GetFrameType(srcDesc.Format); + srcFormat.sdrWhiteLevel = sdrWhiteLevel; switch (colorSpace) { diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index a71598e9..4420d52b 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -71,7 +71,7 @@ private: static void CompletionFunction( CD3D12CommandQueue * queue, bool result, void * param1, void * param2); bool SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, - DXGI_COLOR_SPACE_TYPE colorSpace); + DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel); public: CSwapChainProcessor(IDDCX_MONITOR monitor, CIndirectDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, diff --git a/idd/LGIdd/effect/CHDR16to10Effect.cpp b/idd/LGIdd/effect/CHDR16to10Effect.cpp index 9acc38cb..5a202dd1 100644 --- a/idd/LGIdd/effect/CHDR16to10Effect.cpp +++ b/idd/LGIdd/effect/CHDR16to10Effect.cpp @@ -140,6 +140,7 @@ PostProcessStatus CHDR16to10Effect::SetFormat( dst.minDisplayLuminance = src.minDisplayLuminance; dst.maxContentLightLevel = src.maxContentLightLevel; dst.maxFrameAverageLightLevel = src.maxFrameAverageLightLevel; + dst.sdrWhiteLevel = src.sdrWhiteLevel; return PostProcessStatus::SUCCESS; } diff --git a/obs/lg.c b/obs/lg.c index 4a43bddf..7f55378e 100644 --- a/obs/lg.c +++ b/obs/lg.c @@ -104,6 +104,7 @@ typedef struct bool hideMouse; bool hdr; bool hdrPQ; + float sdrWhiteLevel; // source SDR white level in nits struct vec3 colorMatrix[3]; // source primaries -> BT.709 (linear) float hdrScale; // scRGB reference white scaling #if LIBOBS_API_MAJOR_VER >= 27 @@ -822,10 +823,12 @@ static void lgFormatInit(LGPlugin * this, const KVMFRFrame * frame, this->texture = NULL; } - this->dataWidth = frame->dataWidth; - this->unpack = false; - this->hdr = frame->flags & FRAME_FLAG_HDR; - this->hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ; + this->dataWidth = frame->dataWidth; + this->unpack = false; + this->hdr = frame->flags & FRAME_FLAG_HDR; + this->hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ; + this->sdrWhiteLevel = frame->sdrWhiteLevel ? + (float)frame->sdrWhiteLevel : (float)KVMFR_SDR_WHITE_LEVEL_DEFAULT; if (this->hdr && this->hdrPQ) lgComputeColorMatrix(this, frame); @@ -1223,14 +1226,14 @@ static void lgVideoRender(void * data, gs_effect_t * effect) gs_set_scissor_rect(&r); /* Color cursor pixels are premultiplied sRGB. When rendering into OBS's - * linear scRGB space, decode and scale them to its configured SDR white. */ + * linear scRGB space, decode them and reproduce the source display's SDR + * white level reported by the IDD. */ bool mapCursorToScRGB = false; float cursorScale = 1.0f; #if LIBOBS_API_MAJOR_VER >= 28 mapCursorToScRGB = this->colorSpace == GS_CS_709_SCRGB; if (mapCursorToScRGB) - cursorScale = - obs_get_video_sdr_white_level() / LG_SCRGB_REFERENCE_WHITE; + cursorScale = this->sdrWhiteLevel / LG_SCRGB_REFERENCE_WHITE; #endif effect = mapCursorToScRGB && !this->cursorMono ? @@ -1261,9 +1264,13 @@ static void lgVideoRender(void * data, gs_effect_t * effect) if (this->cursor.type == CURSOR_TYPE_MASKED_COLOR && this->cursorXorTex) { - effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); - image = gs_effect_get_param_by_name(effect, "image"); + effect = mapCursorToScRGB ? + this->cursorEffect : obs_get_base_effect(OBS_EFFECT_DEFAULT); + image = mapCursorToScRGB ? + this->cursorImage : gs_effect_get_param_by_name(effect, "image"); gs_effect_set_texture(image, this->cursorXorTex); + if (mapCursorToScRGB) + gs_effect_set_float(this->cursorMultiplier, cursorScale); gs_blend_function_separate( GS_BLEND_INVDSTCOLOR, GS_BLEND_INVSRCALPHA, GS_BLEND_ZERO , GS_BLEND_ONE);