From 234f8623dcdb7f26158be3130f99fafc0006bbbe Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 18 Jul 2026 03:53:24 +1000 Subject: [PATCH] [idd] avoid release frame timeouts during replug Drop frames when the LGMP queue is full instead of blocking while holding an IddCx surface. Always finish every acquired frame, including duplicate frame numbers, so replug teardown cannot trigger the release-frame watchdog. --- idd/LGIdd/CIndirectDeviceContext.cpp | 12 ++++++----- idd/LGIdd/CIndirectDeviceContext.h | 1 + idd/LGIdd/CSwapChainProcessor.cpp | 31 +++++++++++++++++----------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 9a103793..08be3ace 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -923,13 +923,19 @@ void CIndirectDeviceContext::LGMPTimer() ResendCursor(); } +bool CIndirectDeviceContext::FrameBufferAvailable() const +{ + return m_lgmp && m_frameQueue && + lgmpHostQueuePending(m_frameQueue) < LGMP_Q_FRAME_LEN; +} + CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrameBuffer( unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects) { PreparedFrameBuffer result = {}; - if (!m_lgmp || !m_frameQueue) + if (!FrameBufferAvailable()) return result; if (m_width != dstFormat.desc.Width || @@ -988,10 +994,6 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame KVMFRFrame * fi = m_frame[m_frameIndex]; - // wait until there is room in the queue - while (lgmpHostQueuePending(m_frameQueue) == LGMP_Q_FRAME_LEN) - Sleep(0); - if (dstFormat.format == FRAME_TYPE_INVALID) { DEBUG_ERROR("Unsupported frame format, skipping frame"); diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 69f5ab51..c348ed33 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -210,6 +210,7 @@ public: uint8_t* mem; }; + bool FrameBufferAvailable() const; PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects); bool PublishFrameBuffer(unsigned frameIndex); void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 3090d4dc..068d0f30 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -226,18 +226,18 @@ bool CSwapChainProcessor::SwapChainThreadCore() lastFrameNumber = frameNumber; if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace, sdrWhiteLevel)) DEBUG_WARN("Failed to submit frame"); + } - // report that all GPU processing for this frame has been queued - hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain); - if (FAILED(hr)) - { - // A lost path is normal (mode change/topology rebuild); Windows - // reassigns a fresh swap chain. Just exit and let it. - if (hr != STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) - DEBUG_ERROR_HR(hr, "IddCxSwapChainFinishedProcessingFrame Failed"); - break; - } - + // Every acquired frame must be finished before the next acquire, even if + // its presentation number was a duplicate and no work was submitted. + hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain); + if (FAILED(hr)) + { + // A lost path is normal (mode change/topology rebuild); Windows + // reassigns a fresh swap chain. Just exit and let it. + if (hr != STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) + DEBUG_ERROR_HR(hr, "IddCxSwapChainFinishedProcessingFrame Failed"); + break; } } else @@ -339,6 +339,11 @@ static FrameType GetFrameType(DXGI_FORMAT format) bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel) { + // Never hold an IddCx frame waiting for a slow or disconnected client. In + // particular, monitor departure cannot complete until the frame is released. + if (!m_devContext->FrameBufferAvailable()) + return true; + ComPtr texture; HRESULT hr = acquiredBuffer.As(&texture); if (FAILED(hr)) @@ -532,8 +537,10 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer frameDirtyRects, frameDirtyRectCount); + // The LGMP timer can fill the queue with a subscriber resend after the early + // availability check. Treat this as a dropped frame rather than an error. if (!buffer.mem) - return false; + return true; CFrameBufferResource * fbRes = m_fbPool.Get(buffer, (size_t)layout.Footprint.RowPitch * dstFormat.desc.Height);