[idd] serialize framebuffer copies on one queue

Replace the rotating D3D12 copy queues with two framebuffer-bound
recording slots on one physical COPY queue. Keep allocator, command
list, query range, callback state, and fence target independent per
slot.

Submit source waits, execution, and signaling on the shared timeline.
Track framebuffer ownership through completion and serialize LGMP
publication around the last successfully published frame.
This commit is contained in:
Geoffrey McRae
2026-08-03 18:08:36 +10:00
parent 843207c628
commit ff4ca7d786
12 changed files with 813 additions and 423 deletions

View File

@@ -316,23 +316,31 @@ bool CSwapChainProcessor::SwapChainThreadCore()
}
void CSwapChainProcessor::CompletionFunction(
CD3D12CommandQueue * queue, bool result, void * param1, void * param2)
CD3D12CommandSlot * slot, bool result, void * param1, void * param2)
{
auto sc = (CSwapChainProcessor *)param1;
auto fbRes = (CFrameBufferResource *)param2;
if (!result)
{
// A submitted frame may already be in LGMP, or publication may race this
// callback. Make the message releasable even though its contents failed.
sc->m_devContext->FailFrameBuffer(fbRes->GetFrameIndex());
return;
}
const uint64_t cpuCopyStart = fbRes->GetCopyStart();
uint64_t gpuCopyStart = 0;
uint64_t gpuCopyEnd = 0;
if (result && sc->m_dx12Device->IsIndirectCopy())
if (sc->m_dx12Device->IsIndirectCopy())
sc->m_devContext->WriteFrameBuffer(
fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false);
// Queue waits execute before the start timestamp. The end timestamp follows
// the last CopyTextureRegion, separating GPU work from readiness dispatch.
const bool gpuTimingValid = result &&
queue->GetGPUTimes(gpuCopyStart, gpuCopyEnd);
const bool gpuTimingValid =
slot->GetGPUTimes(gpuCopyStart, gpuCopyEnd);
// Publish readiness before sampling the endpoint. Timing has its own valid
// flag and is published immediately afterwards.
@@ -353,6 +361,7 @@ void CSwapChainProcessor::CompletionFunction(
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime);
sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex());
}
@@ -652,7 +661,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
* use a fence. Because we share this texture with DirectX12 it is able to
* read from it before the desktop duplication API has finished updating it.
*/
srcRes->Signal();
if (!srcRes->Signal())
{
SetFullPendingDamage();
return false;
}
RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {0};
bool noImageUpdate = false;
@@ -819,38 +832,34 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
unsigned frameDirtyRectCount = nbDirtyRects;
m_postProcessor.AdjustFrameDamage(frameDirtyRects, &frameDirtyRectCount);
auto copyQueue = m_dx12Device->GetCopyQueue();
if (!copyQueue)
{
DEBUG_ERROR("Failed to get a CopyQueue");
return false;
}
ComPtr<ID3D12Resource> copySrcResource = srcRes->GetRes();
CD3D12CommandQueue * computeQueue = nullptr;
CD3D12CommandSlot * computeSlot = nullptr;
if (m_postProcessor.HasActiveEffects())
{
computeQueue = m_dx12Device->GetComputeQueue();
if (!computeQueue)
computeSlot = m_dx12Device->GetComputeSlot();
if (!computeSlot)
{
DEBUG_ERROR("Failed to get a ComputeQueue");
DEBUG_ERROR("Failed to get a compute CommandSlot");
return false;
}
if (!srcRes->Sync(*computeSlot))
{
computeSlot->Cancel();
SetFullPendingDamage();
return false;
}
srcRes->Sync(*computeQueue);
copySrcResource = m_postProcessor.Run(
computeQueue->GetGfxList(), copySrcResource,
computeSlot->GetGfxList(), copySrcResource,
currentDirtyRects, &nbDirtyRects);
if (!computeQueue->Execute())
if (!computeSlot->Execute())
{
SetFullPendingDamage();
return false;
}
copyQueue->WaitFor(*computeQueue);
}
else
srcRes->Sync(*copyQueue);
ClipDirtyRects(currentDirtyRects, &nbDirtyRects, dstFormat.desc);
@@ -861,8 +870,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> 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.
// Queue or framebuffer ownership can change after the early availability
// check. Treat this as a dropped frame rather than an error.
if (!buffer.mem)
return true;
@@ -871,15 +880,37 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (!fbRes)
{
m_devContext->AbortFrameBuffer(buffer.frameIndex);
DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool");
SetFullPendingDamage();
return false;
}
CD3D12CommandSlot * copySlot =
m_dx12Device->GetCopySlot(buffer.frameIndex);
if (!copySlot)
{
m_devContext->AbortFrameBuffer(buffer.frameIndex);
DEBUG_ERROR("Failed to get a copy CommandSlot");
SetFullPendingDamage();
return false;
}
const bool syncResult = computeSlot ?
copySlot->WaitFor(*computeSlot) : srcRes->Sync(*copySlot);
if (!syncResult)
{
copySlot->Cancel();
m_devContext->AbortFrameBuffer(buffer.frameIndex);
DEBUG_ERROR("Failed to queue copy synchronization");
SetFullPendingDamage();
return false;
}
const uint64_t copyStart = Nanotime();
fbRes->SetTiming(captureTime, postProcessStart, copyStart);
copyQueue->SetCompletionCallback(&CompletionFunction, this, fbRes);
copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes);
D3D12_TEXTURE_COPY_LOCATION srcLoc = {};
srcLoc.pResource = copySrcResource.Get();
@@ -924,24 +955,26 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
copyDirtyRects, nbCopyDirtyRects, dstFormat.desc);
}
// The source/compute waits were inserted directly on the queue above. The
// command-list timestamp therefore marks the first actual copy operation.
copyQueue->BeginTiming();
// Source/compute waits are submitted immediately before this command list.
// The timestamp therefore marks the first actual copy operation.
copySlot->BeginTiming();
if (fullCopy)
{
copyQueue->GetGfxList()->CopyTextureRegion(
copySlot->GetGfxList()->CopyTextureRegion(
&dstLoc, 0, 0, 0, &srcLoc, NULL);
}
else
{
for (const RECT * rect = copyDirtyRects;
rect < copyDirtyRects + nbCopyDirtyRects; ++rect)
CopyDirtyRect(copyQueue->GetGfxList(), &dstLoc, &srcLoc, *rect);
CopyDirtyRect(copySlot->GetGfxList(), &dstLoc, &srcLoc, *rect);
}
copyQueue->EndTiming();
copySlot->EndTiming();
if (!copyQueue->Execute())
if (!copySlot->Execute())
{
if (!copySlot->HasSubmittedWork())
m_devContext->AbortFrameBuffer(buffer.frameIndex);
SetFullPendingDamage();
return false;
}