[idd] accumulate damage from dropped frames

This commit is contained in:
Geoffrey McRae
2026-07-19 01:00:22 +10:00
parent 84e0a1b956
commit 86824a0010
2 changed files with 98 additions and 12 deletions

View File

@@ -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<IDXGIResource> 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<ID3D11Texture2D> 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<IDXGIResource> acquiredBuffer
if (!srcRes)
{
DEBUG_ERROR("Failed to get a CInteropResource from the pool");
SetFullPendingDamage();
return false;
}
@@ -410,6 +457,14 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> 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<IDXGIResource> acquiredBuffer
return false;
if (postProcessFormatChanged)
{
m_nbDirtyRects = 0;
SetFullPendingDamage();
}
const D12FrameFormat& dstFormat = m_postProcessor.GetOutputFormat();
@@ -509,12 +567,10 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> 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<IDXGIResource> 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<IDXGIResource> acquiredBuffer
if (!fbRes)
{
DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool");
SetFullPendingDamage();
return false;
}
@@ -608,13 +669,25 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> 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)

View File

@@ -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<IDXGIResource> acquiredBuffer, unsigned dirtyRectCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel);