From 9704eb041129d5acd8a9a8a4c6fdf933096de52e Mon Sep 17 00:00:00 2001 From: Artur Korop Date: Mon, 6 Jul 2026 23:33:44 -0400 Subject: [PATCH] Fix HDR mode for virtual display --- Streaming/VideoRenderer.cpp | 87 ++++++++++++++++++++++++++++--------- Streaming/VideoRenderer.h | 3 ++ 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/Streaming/VideoRenderer.cpp b/Streaming/VideoRenderer.cpp index 1f6c1e8d..6a6e801a 100644 --- a/Streaming/VideoRenderer.cpp +++ b/Streaming/VideoRenderer.cpp @@ -159,30 +159,53 @@ bool VideoRenderer::Render(AVFrame *frame) { ID3D11ShaderResourceView* nullSrvs[2] = {}; ctx->PSSetShaderResources(0, 2, nullSrvs); - if (frame->color_trc != m_LastColorTrc) { - DXGI_COLOR_SPACE_TYPE colorspace = {}; - - if (frame->color_trc == AVCOL_TRC_SMPTE2084) { - // Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering - colorspace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; - } else { - // Restore default sRGB colorspace - colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; - } + applySwapChainColorSpace(frame); - UINT colorSpaceSupport = 0; - if (colorspace && SUCCEEDED(m_deviceResources->GetSwapChain()->CheckColorSpaceSupport(colorspace, &colorSpaceSupport)) && (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { - DX::ThrowIfFailed(m_deviceResources->GetSwapChain()->SetColorSpace1(colorspace)); - Utils::Logf("Colorspace changed to %s\n", - colorspace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 - ? "DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020" - : "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709"); - } + return true; +} - m_LastColorTrc = frame->color_trc; +bool VideoRenderer::frameUsesHdrColorSpace(const AVFrame* frame) const { + if (frame->color_trc == AVCOL_TRC_SMPTE2084) { + return true; } - return true; + if (!configuration->enableHDR && !client->IsHDR()) { + return false; + } + + // HDR stream or display: HEVC Main10 often omits TRC or reports BT.709 despite PQ content + return frame->color_trc == AVCOL_TRC_UNSPECIFIED || frame->color_trc == AVCOL_TRC_BT709; +} + +void VideoRenderer::applySwapChainColorSpace(const AVFrame* frame) { + bool useHdr = frameUsesHdrColorSpace(frame); + if (useHdr == m_SwapChainHdrColorSpace && frame->color_trc == m_LastColorTrc) { + return; + } + + auto* swapChain = m_deviceResources->GetSwapChain(); + if (!swapChain) { + return; + } + + DXGI_COLOR_SPACE_TYPE colorspace = useHdr + ? DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 + : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + + HRESULT hr = swapChain->SetColorSpace1(colorspace); + if (SUCCEEDED(hr)) { + Utils::Logf("Colorspace changed to %s (color_trc=%d, hdrStream=%d, displayHdr=%d)\n", + useHdr ? "DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020" + : "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709", + frame->color_trc, configuration->enableHDR, client->IsHDR()); + m_SwapChainHdrColorSpace = useHdr; + m_LastColorTrc = frame->color_trc; + } else { + Utils::Logf("SetColorSpace1(%s) failed: 0x%08X (color_trc=%d)\n", + useHdr ? "DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020" + : "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709", + hr, frame->color_trc); + } } void VideoRenderer::CreateDeviceDependentResources() @@ -681,6 +704,30 @@ void VideoRenderer::SetHDR(bool enabled) // toggle the display to the correct state client->SetDisplayHDR(false, SS_HDR_METADATA{}); } + + // Keep HDMI and swap chain color spaces in sync; force re-apply on next frame if this fails + m_LastColorTrc = AVCOL_TRC_UNSPECIFIED; + m_SwapChainHdrColorSpace = !enabled; + + auto* swapChain = m_deviceResources->GetSwapChain(); + if (!swapChain) { + return; + } + + DXGI_COLOR_SPACE_TYPE colorspace = enabled + ? DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 + : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + + HRESULT hr = swapChain->SetColorSpace1(colorspace); + if (SUCCEEDED(hr)) { + m_SwapChainHdrColorSpace = enabled; + Utils::Logf("SetHDR(%s): swap chain colorspace set to %s\n", + enabled ? "true" : "false", + enabled ? "DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020" + : "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709"); + } else { + Utils::Logf("SetHDR(%s): SetColorSpace1 failed: 0x%08X\n", enabled ? "true" : "false", hr); + } } void VideoRenderer::Stop() { diff --git a/Streaming/VideoRenderer.h b/Streaming/VideoRenderer.h index 48176378..20979c48 100644 --- a/Streaming/VideoRenderer.h +++ b/Streaming/VideoRenderer.h @@ -55,6 +55,8 @@ namespace moonlight_xbox_dx void getFramePremultipliedCscConstants(const AVFrame* frame, std::array &cscMatrix, std::array &offsets); void getFrameChromaCositingOffsets(const AVFrame* frame, std::array &chromaOffsets); bool hasFrameFormatChanged(const AVFrame* frame); + bool frameUsesHdrColorSpace(const AVFrame* frame) const; + void applySwapChainColorSpace(const AVFrame* frame); // Cached pointer to device resources. std::shared_ptr m_deviceResources; @@ -97,6 +99,7 @@ namespace moonlight_xbox_dx AVColorTransferCharacteristic m_LastColorTrc = AVCOL_TRC_UNSPECIFIED; AVColorSpace m_LastColorSpace = AVCOL_SPC_UNSPECIFIED; AVChromaLocation m_LastChromaLocation = AVCHROMA_LOC_UNSPECIFIED; + bool m_SwapChainHdrColorSpace = false; }; }