From ff5b43173ae1d95aec13172c31293ea9219100d8 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 5 Aug 2026 20:48:50 +1000 Subject: [PATCH] [idd] rgb24: isolate benchmark samples from cadence Run effect timing samples only when the retained candidate pipeline is idle and force each completed sample to publish immediately. This prevents work from superseded candidates from being charged to a surviving sample. Include the indirect CPU copy to IVSHMEM in the measured copy stage while preserving the wall-clock fallback when GPU timestamps are unavailable. --- idd/LGIdd/CSwapChainProcessor.cpp | 47 +++++++++++++++++++++++-------- idd/LGIdd/CSwapChainProcessor.h | 2 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index a394228c..d885a59d 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -552,6 +552,7 @@ void CSwapChainProcessor::CandidateCompletionFunction( uint64_t gpuEnd = 0; const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd); + bool forceFrame = false; AcquireSRWLockExclusive(&sc->m_candidateLock); if (candidate->state == CANDIDATE_PREPARING) { @@ -559,7 +560,9 @@ void CSwapChainProcessor::CandidateCompletionFunction( candidate->prepareGPUStart = gpuStart; candidate->prepareGPUEnd = gpuEnd; candidate->prepareTimingValid = timingValid; - candidate->state = result ? CANDIDATE_READY : CANDIDATE_FREE; + candidate->state = + result ? CANDIDATE_READY : CANDIDATE_FREE; + forceFrame = result && candidate->timingToken != 0; } ReleaseSRWLockExclusive(&sc->m_candidateLock); @@ -568,6 +571,8 @@ void CSwapChainProcessor::CandidateCompletionFunction( sc->SetFullPendingDamage(); sc->m_devContext->ForceFrame(); } + else if (forceFrame) + sc->m_devContext->ForceFrame(); sc->SignalCandidateState(); } @@ -604,12 +609,18 @@ void CSwapChainProcessor::CompletionFunction( ReleaseSRWLockShared(&sc->m_candidateLock); const uint64_t publishStart = fbRes->GetCopyStart(); - uint64_t gpuCopyStart = 0; - uint64_t gpuCopyEnd = 0; - + uint64_t gpuCopyStart = 0; + uint64_t gpuCopyEnd = 0; + uint64_t indirectCopyTime = 0; if (sc->m_dx12Device->IsIndirectCopy()) + { + // GPU timestamps end at the readback copy. Include the following CPU copy + // to IVSHMEM in effect benchmark samples without charging it to Ready. + const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); sc->m_devContext->WriteFrameBuffer( fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); + indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; + } // Queue waits execute before the start timestamp. The end timestamp follows // the last copy command, separating GPU work from readiness dispatch. @@ -634,7 +645,7 @@ void CSwapChainProcessor::CompletionFunction( uint64_t publishCopyTime = readyEnd - publishStart; if (gpuTimingValid && gpuCopyStart >= publishStart && gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= readyEnd) - publishCopyTime = gpuCopyEnd - gpuCopyStart; + publishCopyTime = gpuCopyEnd - gpuCopyStart + indirectCopyTime; const uint64_t copyTime = prepareCopyTime + publishCopyTime; const uint64_t elapsed = readyEnd - postProcessStart; @@ -837,7 +848,7 @@ void CSwapChainProcessor::AccumulateFrameDamage( ReleaseSRWLockExclusive(&m_damageLock); } -int CSwapChainProcessor::AcquireCandidate() +int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample) { HANDLE waitHandles[] = { @@ -850,21 +861,28 @@ int CSwapChainProcessor::AcquireCandidate() int selected = -1; uint64_t oldest = UINT64_MAX; bool superseded = false; + bool idle = true; AcquireSRWLockExclusive(&m_candidateLock); for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) - if (m_candidates[i].state == CANDIDATE_FREE) + if (m_candidates[i].state != CANDIDATE_FREE) + idle = false; + else if (selected < 0) { selected = static_cast(i); - break; } + // Effect timing samples must not queue behind work which can later be + // superseded, otherwise that discarded work contaminates the sample. + if (exclusiveSample && !idle) + selected = -1; + unsigned readyCount = 0; for (const FrameCandidate& candidate : m_candidates) if (candidate.state == CANDIDATE_READY) ++readyCount; - if (selected < 0 && readyCount > 1) + if (!exclusiveSample && selected < 0 && readyCount > 1) for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) if (m_candidates[i].state == CANDIDATE_READY && m_candidates[i].sequence < oldest) @@ -1428,6 +1446,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer bool needsReconfigure = false; bool postProcessFormatChanged = false; bool requiresFullDamage = false; + unsigned timingEffectIndex = 0; + uint64_t timingToken = 0; { CSRWExclusiveLock pipelineLock(&m_pipelineLock); m_postProcessors[0].Update(srcFormat); @@ -1500,6 +1520,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer requiresFullDamage = m_postProcessors[0].RequiresFullDamage(); if (requiresFullDamage) SetFullPendingDamage(); + + m_postProcessors[0].GetTimingToken( + &timingEffectIndex, &timingToken); } if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) @@ -1514,7 +1537,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return true; } - const int selectedCandidate = AcquireCandidate(); + const int selectedCandidate = AcquireCandidate(timingToken != 0); if (selectedCandidate < 0) { m_devContext->FrameSuperseded(); @@ -1645,8 +1668,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer if (nbDirtyRects) memcpy(candidate.dirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*candidate.dirtyRects)); - postProcessor.GetTimingToken( - &candidate.timingEffectIndex, &candidate.timingToken); + candidate.timingEffectIndex = timingEffectIndex; + candidate.timingToken = timingToken; copySlot->SetCompletionCallback( &CandidateCompletionFunction, this, &candidate); diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index dd4afdc6..da75c1fa 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -137,7 +137,7 @@ private: const CFrameScheduler::Schedule& schedule, bool periodic, uint64_t publishStart); bool HasReadyCandidate(); - int AcquireCandidate(); + int AcquireCandidate(bool exclusiveSample); void ReleaseCandidate(unsigned candidateIndex); bool EnsureCandidateResource(unsigned candidateIndex, ID3D12Resource * source);