From ab25601a61e40920a1fa5c3e69531fe302c44753 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 5 Aug 2026 21:45:08 +1000 Subject: [PATCH] [idd] rgb24: compare stable candidate timings Ignore allocation alignment when matching retained candidate resources. D3D12 may resolve automatic alignment requests in GetDesc. Comparing that value recreated the candidate for every benchmark sample. Measure native and packed over matching wall-clock intervals. Use preparation and publication intervals, excluding the cadence hold. This avoids mixing GPU timestamps with fallback timing. The CPU IVSHMEM copy is included only on indirect systems. --- idd/LGIdd/CSwapChainProcessor.cpp | 56 ++++++++++++++++++++----------- idd/LGIdd/CSwapChainProcessor.h | 33 +++++++++--------- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index f2a44deb..a6dbd59a 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -598,6 +598,7 @@ void CSwapChainProcessor::CompletionFunction( uint64_t prepareReady; uint64_t prepareGPUStart; uint64_t prepareGPUEnd; + uint64_t timingStart; bool prepareTimingValid; AcquireSRWLockShared(&sc->m_candidateLock); const FrameCandidate& candidate = sc->m_candidates[candidateIndex]; @@ -605,6 +606,7 @@ void CSwapChainProcessor::CompletionFunction( prepareReady = candidate.prepareReady; prepareGPUStart = candidate.prepareGPUStart; prepareGPUEnd = candidate.prepareGPUEnd; + timingStart = candidate.timingStart; prepareTimingValid = candidate.prepareTimingValid; ReleaseSRWLockShared(&sc->m_candidateLock); @@ -614,8 +616,8 @@ void CSwapChainProcessor::CompletionFunction( 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. + // GPU timestamps end at the readback copy. Track the following CPU copy + // separately for frame metrics; benchmark wall time includes it directly. const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); sc->m_devContext->WriteFrameBuffer( fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); @@ -652,9 +654,18 @@ void CSwapChainProcessor::CompletionFunction( const uint64_t measured = postProcessTime + copyTime; const uint64_t readyTime = elapsed > measured ? elapsed - measured : 0; - sc->m_postProcessors[candidateIndex].RecordTiming( - fbRes->GetTimingEffectIndex(), fbRes->GetTimingToken(), - fbRes->IsFullCopy(), postProcessTime + copyTime); + // Use matching wall-clock boundaries for both modes. The split excludes the + // cadence hold while including the indirect CPU copy only when it occurs. + const uint64_t timingToken = fbRes->GetTimingToken(); + if (timingToken && timingStart && prepareReady >= timingStart && + readyEnd >= publishStart) + { + const uint64_t totalTime = + (prepareReady - timingStart) + (readyEnd - publishStart); + sc->m_postProcessors[candidateIndex].RecordTiming( + fbRes->GetTimingEffectIndex(), timingToken, + fbRes->IsFullCopy(), totalTime); + } sc->m_devContext->RecordFrameTiming(readyEnd - publishStart); sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime); @@ -943,9 +954,10 @@ void CSwapChainProcessor::ReleaseCandidate(unsigned candidateIndex) static bool ResourceDescMatches( const D3D12_RESOURCE_DESC& left, const D3D12_RESOURCE_DESC& right) { + // Alignment is allocation metadata. GetDesc may report the resolved value + // when the creation descriptor requested automatic alignment. return left.Dimension == right.Dimension && - left.Alignment == right.Alignment && left.Width == right.Width && left.Height == right.Height && left.DepthOrArraySize == right.DepthOrArraySize && @@ -963,7 +975,7 @@ bool CSwapChainProcessor::EnsureCandidateResource( FrameCandidate& candidate = m_candidates[candidateIndex]; D3D12_RESOURCE_DESC desc = source->GetDesc(); desc.Alignment = 0; - desc.Flags = static_cast( + desc.Flags = static_cast( static_cast(desc.Flags) & ~static_cast(D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER)); @@ -1599,6 +1611,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer SetFullPendingDamage(); return false; } + // Candidate and copy-slot acquisition are common to both benchmark modes. + const uint64_t timingStart = timingToken ? + CFrameScheduler::Nanotime() : 0; ComPtr copySrcResource = srcRes->GetRes(); CD3D12CommandSlot * computeSlot = nullptr; @@ -1677,23 +1692,24 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer return false; } - candidate.srcFormat = srcFormat; - candidate.dstFormat = dstFormat; - candidate.nbDirtyRects = nbDirtyRects; - candidate.pitch = postProcessor.GetOutputPitch(); - candidate.frameSize = postProcessor.GetOutputSize(); - candidate.captureTime = captureTime; - candidate.postProcessStart = postProcessStart; - candidate.prepareCopyStart = CFrameScheduler::Nanotime(); - candidate.prepareReady = 0; - candidate.prepareGPUStart = 0; - candidate.prepareGPUEnd = 0; + candidate.srcFormat = srcFormat; + candidate.dstFormat = dstFormat; + candidate.nbDirtyRects = nbDirtyRects; + candidate.pitch = postProcessor.GetOutputPitch(); + candidate.frameSize = postProcessor.GetOutputSize(); + candidate.captureTime = captureTime; + candidate.postProcessStart = postProcessStart; + candidate.prepareCopyStart = CFrameScheduler::Nanotime(); + candidate.prepareReady = 0; + candidate.prepareGPUStart = 0; + candidate.prepareGPUEnd = 0; + candidate.timingStart = timingStart; candidate.prepareTimingValid = false; if (nbDirtyRects) memcpy(candidate.dirtyRects, currentDirtyRects, nbDirtyRects * sizeof(*candidate.dirtyRects)); - candidate.timingEffectIndex = timingEffectIndex; - candidate.timingToken = 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 a4a29cce..18b4c659 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -66,24 +66,25 @@ private: struct FrameCandidate { - CandidateState state = CANDIDATE_FREE; + CandidateState state = CANDIDATE_FREE; ComPtr resource; - D12FrameFormat srcFormat = {}; - D12FrameFormat dstFormat = {}; + D12FrameFormat srcFormat = {}; + D12FrameFormat dstFormat = {}; RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {}; - unsigned nbDirtyRects = 0; - unsigned pitch = 0; - size_t frameSize = 0; - uint64_t sequence = 0; - uint64_t captureTime = 0; - uint64_t postProcessStart = 0; - uint64_t prepareCopyStart = 0; - uint64_t prepareReady = 0; - uint64_t prepareGPUStart = 0; - uint64_t prepareGPUEnd = 0; - unsigned timingEffectIndex = 0; - uint64_t timingToken = 0; - bool prepareTimingValid = false; + unsigned nbDirtyRects = 0; + unsigned pitch = 0; + size_t frameSize = 0; + uint64_t sequence = 0; + uint64_t captureTime = 0; + uint64_t postProcessStart = 0; + uint64_t prepareCopyStart = 0; + uint64_t prepareReady = 0; + uint64_t prepareGPUStart = 0; + uint64_t prepareGPUEnd = 0; + uint64_t timingStart = 0; + unsigned timingEffectIndex = 0; + uint64_t timingToken = 0; + bool prepareTimingValid = false; }; struct CandidateDamageTail