From f65ecdaf5d56194a659996a1a668787fbd18bba5 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 6 Aug 2026 14:52:37 +1000 Subject: [PATCH] [idd] capture: recover final desktop updates Request one static-desktop re-encode and inspect repeated presentation numbers for its empty dirty-rectangle marker. Prepare every marker in a free candidate without superseding retained damage, so the last desktop update survives a late copy failure. --- idd/LGIdd/CIndirectDeviceContext.cpp | 3 +- idd/LGIdd/CSwapChainProcessor.cpp | 117 ++++++++++++++++++--------- idd/LGIdd/CSwapChainProcessor.h | 5 +- 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index aa0dd74a..54e16151 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -412,7 +412,8 @@ void CIndirectDeviceContext::InitAdapter() caps.Flags |= IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16; #endif - caps.MaxMonitorsSupported = 1; + caps.MaxMonitorsSupported = 1; + caps.StaticDesktopReencodeFrameCount = 1; caps.EndPointDiagnostics.Size = sizeof(caps.EndPointDiagnostics); caps.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index d3d56523..58f24a1d 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -90,6 +90,8 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex // once set or only one thread would ever observe termination. m_terminateEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr)); m_candidateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); + m_candidateAvailableEvent.Attach( + CreateEvent(nullptr, FALSE, FALSE, nullptr)); m_publishTimer.Attach(CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS)); if (!m_publishTimer.Get()) @@ -102,7 +104,8 @@ CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContex bool CSwapChainProcessor::Start() { if (!m_terminateEvent.Get() || !m_candidateEvent.Get() || - !m_publishTimer.Get() || !m_cursorDataEvent.Get() || !m_shapeBuffer) + !m_candidateAvailableEvent.Get() || !m_publishTimer.Get() || + !m_cursorDataEvent.Get() || !m_shapeBuffer) { DEBUG_ERROR("Failed to initialize swap chain worker resources"); return false; @@ -516,7 +519,8 @@ void CSwapChainProcessor::SwapChainThreadCore() // restart loop while waiting for a valid configuration g_pipe.SetGPUStatus(m_dx11Device->IsSoftware()); - UINT lastFrameNumber = 0; + UINT lastFrameNumber = 0; + bool hasLastFrameNumber = false; for (;;) { if (WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0) @@ -591,13 +595,16 @@ void CSwapChainProcessor::SwapChainThreadCore() } else if (SUCCEEDED(hr)) { - if (frameNumber != lastFrameNumber) + const bool duplicateFrame = + hasLastFrameNumber && frameNumber == lastFrameNumber; + if (!duplicateFrame) { - lastFrameNumber = frameNumber; - if (!SwapChainNewFrame(surface, dirtyRectCount, moveRegionCount, - colorSpace, sdrWhiteLevel, captureStart)) - DEBUG_WARN("Failed to submit frame"); + lastFrameNumber = frameNumber; + hasLastFrameNumber = true; } + if (!SwapChainNewFrame(surface, dirtyRectCount, moveRegionCount, + colorSpace, sdrWhiteLevel, captureStart, duplicateFrame)) + DEBUG_WARN("Failed to submit frame"); // Every acquired frame must be finished before the next acquire, even if // its presentation number was a duplicate and no work was submitted. @@ -944,7 +951,8 @@ void CSwapChainProcessor::AccumulateFrameDamage( ReleaseSRWLockExclusive(&m_damageLock); } -int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample) +int CSwapChainProcessor::AcquireCandidate( + bool exclusiveSample, bool allowSupersede) { int selected = -1; uint64_t oldest = UINT64_MAX; @@ -978,7 +986,7 @@ int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample) // Preserve one completed fallback unless another candidate is already // publishing. In that case its peer must remain available for new source // frames instead of being frozen for the duration of the transport copy. - if (!exclusiveSample && selected < 0 && + if (allowSupersede && !exclusiveSample && selected < 0 && readyCount > (publishing ? 0U : 1U)) for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) if (m_candidates[i].state == CANDIDATE_READY && @@ -1099,6 +1107,7 @@ void CSwapChainProcessor::ResetCandidates() void CSwapChainProcessor::SignalCandidateState() { SetEvent(m_candidateEvent.Get()); + SetEvent(m_candidateAvailableEvent.Get()); } bool CSwapChainProcessor::PublishNewestCandidate( @@ -1389,35 +1398,21 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, unsigned moveRegionCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, - uint64_t captureStart) + uint64_t captureStart, bool duplicateFrame) { const uint64_t postProcessStart = CFrameScheduler::Nanotime(); const uint64_t captureTime = postProcessStart - captureStart; - ComPtr texture; - HRESULT hr = acquiredBuffer.As(&texture); - if (FAILED(hr)) - { - DEBUG_ERROR_HR(hr, "Failed to obtain the ID3D11Texture2D from the acquiredBuffer"); - SetFullPendingDamage(); - return false; - } - - CInteropResource * srcRes = m_resPool.Get(texture); - if (!srcRes) - { - DEBUG_ERROR("Failed to get a CInteropResource from the pool"); - SetFullPendingDamage(); - return false; - } - - RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {0}; - bool noImageUpdate = false; + RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {0}; + unsigned resolvedDirtyRectCount = 0; + bool fullDamage = false; + bool noImageUpdate = false; + HRESULT hr; 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(); + fullDamage = true; } else { @@ -1430,7 +1425,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (FAILED(hr)) { DEBUG_ERROR_HR(hr, "IddCxSwapChainGetDirtyRects Failed"); - srcRes->SetFullDamage(); + fullDamage = true; } else if (dirtyOut.DirtyRectOutCount == 1 && dirtyRects[0].left == 0 && dirtyRects[0].top == 0 && @@ -1439,12 +1434,39 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer // 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); + resolvedDirtyRectCount = dirtyOut.DirtyRectOutCount; } + // Reencode frames reuse the preceding presentation number. Inspect their + // empty dirty rectangle above, but suppress every ordinary duplicate. + if (duplicateFrame && !noImageUpdate) + return true; + + ComPtr texture; + hr = acquiredBuffer.As(&texture); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, + "Failed to obtain the ID3D11Texture2D from the acquiredBuffer"); + SetFullPendingDamage(); + return false; + } + + CInteropResource * srcRes = m_resPool.Get(texture); + if (!srcRes) + { + DEBUG_ERROR("Failed to get a CInteropResource from the pool"); + SetFullPendingDamage(); + return false; + } + + if (fullDamage) + srcRes->SetFullDamage(); + else + srcRes->SetDirtyRects(dirtyRects, resolvedDirtyRectCount); + D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); if (!noImageUpdate) { @@ -1619,16 +1641,30 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) m_devContext->ForceFrame(); - if (noImageUpdate) + // Always prepare the requested static-desktop re-encode. An older + // publication can still fail after this frame is acquired, so deciding + // solely from the current pending-damage state can lose the final update. + int selectedCandidate = AcquireCandidate(timingToken != 0, !noImageUpdate); + while (selectedCandidate < 0 && noImageUpdate) { - AcquireSRWLockShared(&m_damageLock); - const bool hasPendingDamage = m_hasPendingDamage; - ReleaseSRWLockShared(&m_damageLock); - if (!hasPendingDamage) + HANDLE waitHandles[] = + { + m_terminateEvent.Get(), + m_candidateAvailableEvent.Get(), + }; + const DWORD waitResult = WaitForMultipleObjects( + ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); + if (waitResult == WAIT_OBJECT_0) return true; - } + if (waitResult != WAIT_OBJECT_0 + 1) + { + DEBUG_ERROR_HR(HRESULT_FROM_WIN32(GetLastError()), + "Failed while waiting for a frame candidate"); + return false; + } - const int selectedCandidate = AcquireCandidate(timingToken != 0); + selectedCandidate = AcquireCandidate(timingToken != 0, false); + } if (selectedCandidate < 0) { m_devContext->FrameSuperseded(); @@ -1636,6 +1672,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer } const unsigned candidateIndex = static_cast(selectedCandidate); + FrameCandidate& candidate = m_candidates[candidateIndex]; CSRWExclusiveLock pipelineLock(&m_pipelineLock); diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 4ddec21c..311f9ad8 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -107,6 +107,7 @@ private: Wrappers::HandleT m_thread[3]; Wrappers::Event m_terminateEvent; Wrappers::Event m_candidateEvent; + Wrappers::Event m_candidateAvailableEvent; Wrappers::HandleT m_publishTimer; Wrappers::Event m_cursorDataEvent; @@ -147,7 +148,7 @@ private: const CFrameScheduler::Schedule& schedule, bool periodic, uint64_t publishStart); bool HasReadyCandidate(); - int AcquireCandidate(bool exclusiveSample); + int AcquireCandidate(bool exclusiveSample, bool allowSupersede); void ReleaseCandidate(unsigned candidateIndex); bool EnsureCandidateResource( unsigned candidateIndex, size_t frameSize); @@ -171,7 +172,7 @@ private: bool SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, unsigned moveRegionCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, - uint64_t captureStart); + uint64_t captureStart, bool duplicateFrame); public: CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration,