diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index f9cd2c3b..7bb9aa63 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -357,19 +357,65 @@ static FrameType GetFrameType(DXGI_FORMAT format) } } +void CSwapChainProcessor::SetFullPendingDamage() +{ + m_hasPendingDamage = true; + m_nbPendingDirtyRects = 0; +} + +void CSwapChainProcessor::AccumulateFrameDamage( + const RECT * dirtyRects, unsigned nbDirtyRects) +{ + if (nbDirtyRects > LG_MAX_DIRTY_RECTS) + nbDirtyRects = 0; + + if (!m_hasPendingDamage) + { + m_hasPendingDamage = true; + m_nbPendingDirtyRects = nbDirtyRects; + if (nbDirtyRects) + memcpy(m_pendingDirtyRects, dirtyRects, + nbDirtyRects * sizeof(*m_pendingDirtyRects)); + return; + } + + // Zero dirty rectangles represents full-frame damage. Once any skipped + // frame requires a full update, no later rectangles can narrow it again. + if (m_nbPendingDirtyRects == 0 || nbDirtyRects == 0) + { + m_nbPendingDirtyRects = 0; + return; + } + + if (m_nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS) + { + m_nbPendingDirtyRects = 0; + return; + } + + memcpy(m_pendingDirtyRects + m_nbPendingDirtyRects, dirtyRects, + nbDirtyRects * sizeof(*m_pendingDirtyRects)); + m_nbPendingDirtyRects += nbDirtyRects; +} + 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. + // 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 + // the next published frame to invalidate the entire image. if (!m_devContext->FrameBufferAvailable()) + { + SetFullPendingDamage(); return true; + } 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; } @@ -377,6 +423,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!srcRes) { DEBUG_ERROR("Failed to get a CInteropResource from the pool"); + SetFullPendingDamage(); return false; } @@ -410,6 +457,14 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer } D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); + 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. + if (!m_devContext->FrameBufferAvailable()) + return true; + D12FrameFormat srcFormat = {}; srcFormat.desc = srcDesc; srcFormat.width = (unsigned)srcDesc.Width; @@ -492,7 +547,10 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return false; if (postProcessFormatChanged) + { m_nbDirtyRects = 0; + SetFullPendingDamage(); + } const D12FrameFormat& dstFormat = m_postProcessor.GetOutputFormat(); @@ -509,12 +567,10 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; RECT frameDirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = srcRes->GetDirtyRectCount(); - if (nbDirtyRects > ARRAYSIZE(currentDirtyRects)) - nbDirtyRects = 0; - else + unsigned nbDirtyRects = m_nbPendingDirtyRects; + if (nbDirtyRects) { - memcpy(currentDirtyRects, srcRes->GetDirtyRects(), nbDirtyRects * sizeof(*currentDirtyRects)); + memcpy(currentDirtyRects, m_pendingDirtyRects, nbDirtyRects * sizeof(*currentDirtyRects)); memcpy(frameDirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*frameDirtyRects)); } unsigned frameDirtyRectCount = nbDirtyRects; @@ -543,7 +599,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer computeQueue->GetGfxList(), copySrcResource, currentDirtyRects, &nbDirtyRects); - computeQueue->Execute(); + if (!computeQueue->Execute()) + { + SetFullPendingDamage(); + return false; + } copyQueue->WaitFor(*computeQueue); } else @@ -569,6 +629,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (!fbRes) { DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); + SetFullPendingDamage(); return false; } @@ -608,13 +669,25 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer CopyDirtyRect(copyQueue->GetGfxList(), &dstLoc, &srcLoc, *rect); } - memcpy(m_dirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*m_dirtyRects)); - m_nbDirtyRects = nbDirtyRects; - if (!copyQueue->Execute()) + { + SetFullPendingDamage(); return false; + } - return m_devContext->PublishFrameBuffer(buffer.frameIndex); + if (!m_devContext->PublishFrameBuffer(buffer.frameIndex)) + { + SetFullPendingDamage(); + return false; + } + + memcpy(m_dirtyRects, currentDirtyRects, + nbDirtyRects * sizeof(*m_dirtyRects)); + m_nbDirtyRects = nbDirtyRects; + m_hasPendingDamage = false; + m_nbPendingDirtyRects = 0; + + return true; } DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg) diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 16343296..92d06fac 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -61,9 +61,20 @@ private: BYTE* m_shapeBuffer; DWORD m_lastShapeId = 0; + // Output-space damage from the previous published frame. The shared-memory + // frame buffers alternate, so this must be copied along with the current + // damage to bring the older target buffer up to date. RECT m_dirtyRects[LG_MAX_DIRTY_RECTS] = {}; unsigned m_nbDirtyRects = 0; + // Source-space damage accumulated since the last published frame. Frames + // can be dropped while the LGMP queue is full, but their damage must be + // included in the next frame sent to the client. A count of zero represents + // full-frame damage when m_hasPendingDamage is set. + RECT m_pendingDirtyRects[LG_MAX_DIRTY_RECTS] = {}; + unsigned m_nbPendingDirtyRects = 0; + bool m_hasPendingDamage = true; + static DWORD CALLBACK _SwapChainThread(LPVOID arg); void SwapChainThread(); bool SwapChainThreadCore(); @@ -74,6 +85,8 @@ private: static void CompletionFunction( CD3D12CommandQueue * queue, bool result, void * param1, void * param2); + void AccumulateFrameDamage(const RECT * dirtyRects, unsigned nbDirtyRects); + void SetFullPendingDamage(); bool SwapChainNewFrame(ComPtr acquiredBuffer, unsigned dirtyRectCount, DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel);