From c8e4b97c3cdc7e7ba9cbd65151ff8f3730cc3eda Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 3 Aug 2026 14:43:30 +1000 Subject: [PATCH] [idd] skip copies for static frames Treat IddCx's single empty dirty rectangle as no image update instead of promoting it to full damage. Preserve pending damage and format changes, and fall back to full damage when legacy move regions are present. --- idd/LGIdd/CInteropResource.cpp | 3 +- idd/LGIdd/CSwapChainProcessor.cpp | 74 ++++++++++++++++++++++++------- idd/LGIdd/CSwapChainProcessor.h | 6 ++- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/idd/LGIdd/CInteropResource.cpp b/idd/LGIdd/CInteropResource.cpp index c0da165f..699e7199 100644 --- a/idd/LGIdd/CInteropResource.cpp +++ b/idd/LGIdd/CInteropResource.cpp @@ -154,6 +154,7 @@ void CInteropResource::SetDirtyRects(const RECT * dirtyRects, unsigned nbDirtyRe return; } - memcpy(m_dirtyRects, dirtyRects, nbDirtyRects * sizeof(*m_dirtyRects)); + if (nbDirtyRects) + memcpy(m_dirtyRects, dirtyRects, nbDirtyRects * sizeof(*m_dirtyRects)); m_nbDirtyRects = nbDirtyRects; } diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index b6cf2029..d50201ec 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -44,6 +44,23 @@ static uint64_t Nanotime() ticks % frequency * 1000000000ULL / frequency; } +static bool FrameMetadataChanged(const D12FrameFormat& previous, + const D12FrameFormat& current) +{ + return + previous.hdrMetadata != current.hdrMetadata || + previous.sdrWhiteLevel != current.sdrWhiteLevel || + (current.hdrMetadata && + (memcmp(previous.displayPrimary, current.displayPrimary, + sizeof(current.displayPrimary)) != 0 || + memcmp(previous.whitePoint, current.whitePoint, + sizeof(current.whitePoint)) != 0 || + previous.maxDisplayLuminance != current.maxDisplayLuminance || + previous.minDisplayLuminance != current.minDisplayLuminance || + previous.maxContentLightLevel != current.maxContentLightLevel || + previous.maxFrameAverageLightLevel != current.maxFrameAverageLightLevel)); +} + CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration, IDDCX_MONITOR monitor, CIndirectDeviceContext* devContext, IDDCX_SWAPCHAIN hSwapChain, @@ -199,8 +216,9 @@ bool CSwapChainProcessor::SwapChainThreadCore() if (WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0) break; - UINT frameNumber = 0; - UINT dirtyRectCount = 0; + UINT frameNumber = 0; + UINT dirtyRectCount = 0; + UINT moveRegionCount = 0; ComPtr surface; // The surface colour space is the source of truth for the content format. @@ -240,9 +258,10 @@ bool CSwapChainProcessor::SwapChainThreadCore() hr = IddCxSwapChainReleaseAndAcquireBuffer(m_hSwapChain, &buffer); if (SUCCEEDED(hr)) { - frameNumber = buffer.MetaData.PresentationFrameNumber; - dirtyRectCount = buffer.MetaData.DirtyRectCount; - surface = buffer.MetaData.pSurface; + frameNumber = buffer.MetaData.PresentationFrameNumber; + dirtyRectCount = buffer.MetaData.DirtyRectCount; + moveRegionCount = buffer.MetaData.MoveRegionCount; + surface = buffer.MetaData.pSurface; } } @@ -269,8 +288,8 @@ bool CSwapChainProcessor::SwapChainThreadCore() if (frameNumber != lastFrameNumber) { lastFrameNumber = frameNumber; - if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace, - sdrWhiteLevel, Nanotime() - captureStart)) + if (!SwapChainNewFrame(surface, dirtyRectCount, moveRegionCount, + colorSpace, sdrWhiteLevel, Nanotime() - captureStart)) DEBUG_WARN("Failed to submit frame"); } @@ -494,8 +513,9 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const } bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, - unsigned dirtyRectCount, DXGI_COLOR_SPACE_TYPE colorSpace, - UINT sdrWhiteLevel, uint64_t captureTime) + unsigned dirtyRectCount, unsigned moveRegionCount, + DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, + uint64_t captureTime) { // Preserve the fast drop path: never hold an IddCx frame while waiting for // a slow or disconnected client. We have not read its rectangles, so force @@ -533,8 +553,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcRes->Signal(); RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {0}; - if (dirtyRectCount > ARRAYSIZE(dirtyRects)) + bool noImageUpdate = false; + if (moveRegionCount || dirtyRectCount > ARRAYSIZE(dirtyRects)) { + // Move regions are not represented by the dirty rectangle list. Copy the + // full surface so the alternating destinations remain coherent. srcRes->SetFullDamage(); } else @@ -550,13 +573,23 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer DEBUG_ERROR_HR(hr, "IddCxSwapChainGetDirtyRects Failed"); srcRes->SetFullDamage(); } + else if (dirtyOut.DirtyRectOutCount == 1 && + dirtyRects[0].left == 0 && dirtyRects[0].top == 0 && + dirtyRects[0].right == 0 && dirtyRects[0].bottom == 0) + { + // One empty rectangle is IddCx's static-desktop re-encode marker. It + // does not describe an image update and must not become full damage. + noImageUpdate = true; + srcRes->SetDirtyRects(nullptr, 0); + } else srcRes->SetDirtyRects(dirtyRects, dirtyOut.DirtyRectOutCount); } D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); - AccumulateFrameDamage( - srcRes->GetDirtyRects(), srcRes->GetDirtyRectCount()); + if (!noImageUpdate) + AccumulateFrameDamage( + srcRes->GetDirtyRects(), srcRes->GetDirtyRectCount()); // Never hold an IddCx frame waiting for a slow or disconnected client. Read // and retain its damage first so the next published frame remains complete. @@ -564,11 +597,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return true; D12FrameFormat srcFormat = {}; - srcFormat.desc = srcDesc; - srcFormat.width = (unsigned)srcDesc.Width; - srcFormat.height = srcDesc.Height; - srcFormat.format = GetFrameType(srcDesc.Format); - srcFormat.sdrWhiteLevel = sdrWhiteLevel; + srcFormat.desc = srcDesc; + srcFormat.width = (unsigned)srcDesc.Width; + srcFormat.height = srcDesc.Height; + srcFormat.format = GetFrameType(srcDesc.Format); + srcFormat.sdrWhiteLevel = sdrWhiteLevel; srcFormat.colorTransform = m_devContext->GetColorTransform(); switch (colorSpace) @@ -643,6 +676,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer break; } + const bool frameMetadataChanged = noImageUpdate && + FrameMetadataChanged(m_postProcessor.GetOutputFormat(), srcFormat); bool postProcessFormatChanged = false; if (!m_postProcessor.Configure(srcFormat, &postProcessFormatChanged)) return false; @@ -652,6 +687,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer m_nbDirtyRects = 0; SetFullPendingDamage(); } + else if (frameMetadataChanged) + SetFullPendingDamage(); + + if (noImageUpdate && !m_hasPendingDamage) + return true; const D12FrameFormat& dstFormat = m_postProcessor.GetOutputFormat(); diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index d5d8b0d5..bf76f6cc 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -101,8 +101,10 @@ private: void UpdateHDRMetadata(const IDDCX_METADATA2& metadata); #endif bool GetContentHDRMetadata(D12FrameFormat& format) const; - bool SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, - DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, uint64_t captureTime); + bool SwapChainNewFrame(ComPtr acquiredBuffer, + unsigned dirtyRectCount, unsigned moveRegionCount, + DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, + uint64_t captureTime); public: CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration,