[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.
This commit is contained in:
Geoffrey McRae
2026-08-05 20:48:50 +10:00
parent 13a34b383d
commit ff5b43173a
2 changed files with 36 additions and 13 deletions

View File

@@ -552,6 +552,7 @@ void CSwapChainProcessor::CandidateCompletionFunction(
uint64_t gpuEnd = 0; uint64_t gpuEnd = 0;
const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd); const bool timingValid = result && slot->GetGPUTimes(gpuStart, gpuEnd);
bool forceFrame = false;
AcquireSRWLockExclusive(&sc->m_candidateLock); AcquireSRWLockExclusive(&sc->m_candidateLock);
if (candidate->state == CANDIDATE_PREPARING) if (candidate->state == CANDIDATE_PREPARING)
{ {
@@ -559,7 +560,9 @@ void CSwapChainProcessor::CandidateCompletionFunction(
candidate->prepareGPUStart = gpuStart; candidate->prepareGPUStart = gpuStart;
candidate->prepareGPUEnd = gpuEnd; candidate->prepareGPUEnd = gpuEnd;
candidate->prepareTimingValid = timingValid; 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); ReleaseSRWLockExclusive(&sc->m_candidateLock);
@@ -568,6 +571,8 @@ void CSwapChainProcessor::CandidateCompletionFunction(
sc->SetFullPendingDamage(); sc->SetFullPendingDamage();
sc->m_devContext->ForceFrame(); sc->m_devContext->ForceFrame();
} }
else if (forceFrame)
sc->m_devContext->ForceFrame();
sc->SignalCandidateState(); sc->SignalCandidateState();
} }
@@ -606,10 +611,16 @@ void CSwapChainProcessor::CompletionFunction(
const uint64_t publishStart = fbRes->GetCopyStart(); const uint64_t publishStart = fbRes->GetCopyStart();
uint64_t gpuCopyStart = 0; uint64_t gpuCopyStart = 0;
uint64_t gpuCopyEnd = 0; uint64_t gpuCopyEnd = 0;
uint64_t indirectCopyTime = 0;
if (sc->m_dx12Device->IsIndirectCopy()) 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( sc->m_devContext->WriteFrameBuffer(
fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false);
indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart;
}
// Queue waits execute before the start timestamp. The end timestamp follows // Queue waits execute before the start timestamp. The end timestamp follows
// the last copy command, separating GPU work from readiness dispatch. // the last copy command, separating GPU work from readiness dispatch.
@@ -634,7 +645,7 @@ void CSwapChainProcessor::CompletionFunction(
uint64_t publishCopyTime = readyEnd - publishStart; uint64_t publishCopyTime = readyEnd - publishStart;
if (gpuTimingValid && gpuCopyStart >= publishStart && if (gpuTimingValid && gpuCopyStart >= publishStart &&
gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= readyEnd) gpuCopyEnd >= gpuCopyStart && gpuCopyEnd <= readyEnd)
publishCopyTime = gpuCopyEnd - gpuCopyStart; publishCopyTime = gpuCopyEnd - gpuCopyStart + indirectCopyTime;
const uint64_t copyTime = prepareCopyTime + publishCopyTime; const uint64_t copyTime = prepareCopyTime + publishCopyTime;
const uint64_t elapsed = readyEnd - postProcessStart; const uint64_t elapsed = readyEnd - postProcessStart;
@@ -837,7 +848,7 @@ void CSwapChainProcessor::AccumulateFrameDamage(
ReleaseSRWLockExclusive(&m_damageLock); ReleaseSRWLockExclusive(&m_damageLock);
} }
int CSwapChainProcessor::AcquireCandidate() int CSwapChainProcessor::AcquireCandidate(bool exclusiveSample)
{ {
HANDLE waitHandles[] = HANDLE waitHandles[] =
{ {
@@ -850,21 +861,28 @@ int CSwapChainProcessor::AcquireCandidate()
int selected = -1; int selected = -1;
uint64_t oldest = UINT64_MAX; uint64_t oldest = UINT64_MAX;
bool superseded = false; bool superseded = false;
bool idle = true;
AcquireSRWLockExclusive(&m_candidateLock); AcquireSRWLockExclusive(&m_candidateLock);
for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) 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<int>(i); selected = static_cast<int>(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; unsigned readyCount = 0;
for (const FrameCandidate& candidate : m_candidates) for (const FrameCandidate& candidate : m_candidates)
if (candidate.state == CANDIDATE_READY) if (candidate.state == CANDIDATE_READY)
++readyCount; ++readyCount;
if (selected < 0 && readyCount > 1) if (!exclusiveSample && selected < 0 && readyCount > 1)
for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i) for (unsigned i = 0; i < ARRAYSIZE(m_candidates); ++i)
if (m_candidates[i].state == CANDIDATE_READY && if (m_candidates[i].state == CANDIDATE_READY &&
m_candidates[i].sequence < oldest) m_candidates[i].sequence < oldest)
@@ -1428,6 +1446,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
bool needsReconfigure = false; bool needsReconfigure = false;
bool postProcessFormatChanged = false; bool postProcessFormatChanged = false;
bool requiresFullDamage = false; bool requiresFullDamage = false;
unsigned timingEffectIndex = 0;
uint64_t timingToken = 0;
{ {
CSRWExclusiveLock pipelineLock(&m_pipelineLock); CSRWExclusiveLock pipelineLock(&m_pipelineLock);
m_postProcessors[0].Update(srcFormat); m_postProcessors[0].Update(srcFormat);
@@ -1500,6 +1520,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
requiresFullDamage = m_postProcessors[0].RequiresFullDamage(); requiresFullDamage = m_postProcessors[0].RequiresFullDamage();
if (requiresFullDamage) if (requiresFullDamage)
SetFullPendingDamage(); SetFullPendingDamage();
m_postProcessors[0].GetTimingToken(
&timingEffectIndex, &timingToken);
} }
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
@@ -1514,7 +1537,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return true; return true;
} }
const int selectedCandidate = AcquireCandidate(); const int selectedCandidate = AcquireCandidate(timingToken != 0);
if (selectedCandidate < 0) if (selectedCandidate < 0)
{ {
m_devContext->FrameSuperseded(); m_devContext->FrameSuperseded();
@@ -1645,8 +1668,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (nbDirtyRects) if (nbDirtyRects)
memcpy(candidate.dirtyRects, currentDirtyRects, memcpy(candidate.dirtyRects, currentDirtyRects,
nbDirtyRects * sizeof(*candidate.dirtyRects)); nbDirtyRects * sizeof(*candidate.dirtyRects));
postProcessor.GetTimingToken( candidate.timingEffectIndex = timingEffectIndex;
&candidate.timingEffectIndex, &candidate.timingToken); candidate.timingToken = timingToken;
copySlot->SetCompletionCallback( copySlot->SetCompletionCallback(
&CandidateCompletionFunction, this, &candidate); &CandidateCompletionFunction, this, &candidate);

View File

@@ -137,7 +137,7 @@ private:
const CFrameScheduler::Schedule& schedule, bool periodic, const CFrameScheduler::Schedule& schedule, bool periodic,
uint64_t publishStart); uint64_t publishStart);
bool HasReadyCandidate(); bool HasReadyCandidate();
int AcquireCandidate(); int AcquireCandidate(bool exclusiveSample);
void ReleaseCandidate(unsigned candidateIndex); void ReleaseCandidate(unsigned candidateIndex);
bool EnsureCandidateResource(unsigned candidateIndex, bool EnsureCandidateResource(unsigned candidateIndex,
ID3D12Resource * source); ID3D12Resource * source);