[idd] capture: retire published pending damage

Track damage received after each retained candidate snapshot. When a
candidate is submitted successfully, replace the global pending set
with its trailing damage instead of requiring the source generation
to remain unchanged.

This lets published full-frame damage retire while preserving newer
partial or full updates.
This commit is contained in:
Geoffrey McRae
2026-08-05 20:57:32 +10:00
parent ff5b43173a
commit f073099950
2 changed files with 86 additions and 53 deletions

View File

@@ -798,12 +798,48 @@ static FrameType GetFrameType(DXGI_FORMAT format)
} }
} }
static void AccumulatePendingDamage(
RECT pendingDirtyRects[], unsigned * nbPendingDirtyRects,
bool * hasPendingDamage, const RECT dirtyRects[], unsigned nbDirtyRects)
{
if (nbDirtyRects > LG_MAX_DIRTY_RECTS)
nbDirtyRects = 0;
if (!*hasPendingDamage)
{
*hasPendingDamage = true;
*nbPendingDirtyRects = nbDirtyRects;
if (nbDirtyRects)
memcpy(pendingDirtyRects, dirtyRects,
nbDirtyRects * sizeof(*pendingDirtyRects));
return;
}
// Zero dirty rectangles represents full-frame damage. Once an accumulated
// set is full, no later rectangles can narrow that same set again.
if (*nbPendingDirtyRects == 0 || nbDirtyRects == 0 ||
*nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS)
{
*nbPendingDirtyRects = 0;
return;
}
memcpy(pendingDirtyRects + *nbPendingDirtyRects, dirtyRects,
nbDirtyRects * sizeof(*pendingDirtyRects));
*nbPendingDirtyRects += nbDirtyRects;
}
void CSwapChainProcessor::SetFullPendingDamage() void CSwapChainProcessor::SetFullPendingDamage()
{ {
AcquireSRWLockExclusive(&m_damageLock); AcquireSRWLockExclusive(&m_damageLock);
m_hasPendingDamage = true; m_hasPendingDamage = true;
m_nbPendingDirtyRects = 0; m_nbPendingDirtyRects = 0;
++m_damageGeneration; for (CandidateDamageTail& tail : m_candidateDamageTail)
if (tail.active)
{
tail.hasDamage = true;
tail.nbDirtyRects = 0;
}
ReleaseSRWLockExclusive(&m_damageLock); ReleaseSRWLockExclusive(&m_damageLock);
} }
@@ -811,40 +847,14 @@ void CSwapChainProcessor::AccumulateFrameDamage(
const RECT * dirtyRects, unsigned nbDirtyRects) const RECT * dirtyRects, unsigned nbDirtyRects)
{ {
AcquireSRWLockExclusive(&m_damageLock); AcquireSRWLockExclusive(&m_damageLock);
++m_damageGeneration; AccumulatePendingDamage(
if (nbDirtyRects > LG_MAX_DIRTY_RECTS) m_pendingDirtyRects, &m_nbPendingDirtyRects, &m_hasPendingDamage,
nbDirtyRects = 0; dirtyRects, nbDirtyRects);
for (CandidateDamageTail& tail : m_candidateDamageTail)
if (!m_hasPendingDamage) if (tail.active)
{ AccumulatePendingDamage(
m_hasPendingDamage = true; tail.dirtyRects, &tail.nbDirtyRects, &tail.hasDamage,
m_nbPendingDirtyRects = nbDirtyRects; dirtyRects, nbDirtyRects);
if (nbDirtyRects)
memcpy(m_pendingDirtyRects, dirtyRects,
nbDirtyRects * sizeof(*m_pendingDirtyRects));
ReleaseSRWLockExclusive(&m_damageLock);
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;
ReleaseSRWLockExclusive(&m_damageLock);
return;
}
if (m_nbPendingDirtyRects + nbDirtyRects > LG_MAX_DIRTY_RECTS)
{
m_nbPendingDirtyRects = 0;
ReleaseSRWLockExclusive(&m_damageLock);
return;
}
memcpy(m_pendingDirtyRects + m_nbPendingDirtyRects, dirtyRects,
nbDirtyRects * sizeof(*m_pendingDirtyRects));
m_nbPendingDirtyRects += nbDirtyRects;
ReleaseSRWLockExclusive(&m_damageLock); ReleaseSRWLockExclusive(&m_damageLock);
} }
@@ -994,6 +1004,11 @@ void CSwapChainProcessor::ResetCandidates()
for (FrameCandidate& candidate : m_candidates) for (FrameCandidate& candidate : m_candidates)
candidate = {}; candidate = {};
ReleaseSRWLockExclusive(&m_candidateLock); ReleaseSRWLockExclusive(&m_candidateLock);
AcquireSRWLockExclusive(&m_damageLock);
for (CandidateDamageTail& tail : m_candidateDamageTail)
tail = {};
ReleaseSRWLockExclusive(&m_damageLock);
SignalCandidateState(); SignalCandidateState();
} }
@@ -1059,6 +1074,7 @@ bool CSwapChainProcessor::PublishNewestCandidate(
FrameCandidate& candidate = m_candidates[candidateIndex]; FrameCandidate& candidate = m_candidates[candidateIndex];
CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; CPostProcessor& postProcessor = m_postProcessors[candidateIndex];
const uint64_t candidateSequence = candidate.sequence;
auto buffer = m_devContext->PrepareFrameBuffer( auto buffer = m_devContext->PrepareFrameBuffer(
candidate.pitch, candidate.pitch,
@@ -1194,10 +1210,16 @@ bool CSwapChainProcessor::PublishNewestCandidate(
memcpy(m_dirtyRects, candidate.dirtyRects, memcpy(m_dirtyRects, candidate.dirtyRects,
candidate.nbDirtyRects * sizeof(*m_dirtyRects)); candidate.nbDirtyRects * sizeof(*m_dirtyRects));
m_nbDirtyRects = candidate.nbDirtyRects; m_nbDirtyRects = candidate.nbDirtyRects;
if (candidate.damageGeneration == m_damageGeneration) CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex];
if (tail.active && tail.ownerSequence == candidateSequence)
{ {
m_hasPendingDamage = false; m_hasPendingDamage = tail.hasDamage;
m_nbPendingDirtyRects = 0; m_nbPendingDirtyRects = tail.nbDirtyRects;
if (tail.hasDamage && tail.nbDirtyRects)
memcpy(m_pendingDirtyRects, tail.dirtyRects,
tail.nbDirtyRects * sizeof(*m_pendingDirtyRects));
tail.ownerSequence = 0;
tail.active = false;
} }
ReleaseSRWLockExclusive(&m_damageLock); ReleaseSRWLockExclusive(&m_damageLock);
@@ -1545,6 +1567,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
} }
const unsigned candidateIndex = const unsigned candidateIndex =
static_cast<unsigned>(selectedCandidate); static_cast<unsigned>(selectedCandidate);
FrameCandidate& candidate = m_candidates[candidateIndex];
CSRWExclusiveLock pipelineLock(&m_pipelineLock); CSRWExclusiveLock pipelineLock(&m_pipelineLock);
CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; CPostProcessor& postProcessor = m_postProcessors[candidateIndex];
@@ -1552,17 +1575,20 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {};
unsigned nbDirtyRects = 0; unsigned nbDirtyRects = 0;
uint64_t damageGeneration = 0; AcquireSRWLockExclusive(&m_damageLock);
AcquireSRWLockShared(&m_damageLock);
if (m_hasPendingDamage) if (m_hasPendingDamage)
{ {
nbDirtyRects = m_nbPendingDirtyRects; nbDirtyRects = m_nbPendingDirtyRects;
if (nbDirtyRects) if (nbDirtyRects)
memcpy(currentDirtyRects, m_pendingDirtyRects, memcpy(currentDirtyRects, m_pendingDirtyRects,
nbDirtyRects * sizeof(*currentDirtyRects)); nbDirtyRects * sizeof(*currentDirtyRects));
damageGeneration = m_damageGeneration;
} }
ReleaseSRWLockShared(&m_damageLock); CandidateDamageTail& tail = m_candidateDamageTail[candidateIndex];
tail.ownerSequence = candidate.sequence;
tail.nbDirtyRects = 0;
tail.hasDamage = false;
tail.active = true;
ReleaseSRWLockExclusive(&m_damageLock);
CD3D12CommandSlot * copySlot = CD3D12CommandSlot * copySlot =
m_dx12Device->GetCopySlot(candidateIndex); m_dx12Device->GetCopySlot(candidateIndex);
@@ -1651,13 +1677,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return false; return false;
} }
FrameCandidate& candidate = m_candidates[candidateIndex];
candidate.srcFormat = srcFormat; candidate.srcFormat = srcFormat;
candidate.dstFormat = dstFormat; candidate.dstFormat = dstFormat;
candidate.nbDirtyRects = nbDirtyRects; candidate.nbDirtyRects = nbDirtyRects;
candidate.pitch = postProcessor.GetOutputPitch(); candidate.pitch = postProcessor.GetOutputPitch();
candidate.frameSize = postProcessor.GetOutputSize(); candidate.frameSize = postProcessor.GetOutputSize();
candidate.damageGeneration = damageGeneration;
candidate.captureTime = captureTime; candidate.captureTime = captureTime;
candidate.postProcessStart = postProcessStart; candidate.postProcessStart = postProcessStart;
candidate.prepareCopyStart = CFrameScheduler::Nanotime(); candidate.prepareCopyStart = CFrameScheduler::Nanotime();

View File

@@ -75,7 +75,6 @@ private:
unsigned pitch = 0; unsigned pitch = 0;
size_t frameSize = 0; size_t frameSize = 0;
uint64_t sequence = 0; uint64_t sequence = 0;
uint64_t damageGeneration = 0;
uint64_t captureTime = 0; uint64_t captureTime = 0;
uint64_t postProcessStart = 0; uint64_t postProcessStart = 0;
uint64_t prepareCopyStart = 0; uint64_t prepareCopyStart = 0;
@@ -87,12 +86,22 @@ private:
bool prepareTimingValid = false; bool prepareTimingValid = false;
}; };
struct CandidateDamageTail
{
uint64_t ownerSequence = 0;
RECT dirtyRects[LG_MAX_DIRTY_RECTS] = {};
unsigned nbDirtyRects = 0;
bool hasDamage = false;
bool active = false;
};
// An active tail records only damage received after its candidate snapshot.
FrameCandidate m_candidates[LGMP_Q_FRAME_LEN]; FrameCandidate m_candidates[LGMP_Q_FRAME_LEN];
CandidateDamageTail m_candidateDamageTail[LGMP_Q_FRAME_LEN];
SRWLOCK m_candidateLock = SRWLOCK_INIT; SRWLOCK m_candidateLock = SRWLOCK_INIT;
SRWLOCK m_damageLock = SRWLOCK_INIT; SRWLOCK m_damageLock = SRWLOCK_INIT;
SRWLOCK m_pipelineLock = SRWLOCK_INIT; SRWLOCK m_pipelineLock = SRWLOCK_INIT;
uint64_t m_candidateSequence = 0; uint64_t m_candidateSequence = 0;
uint64_t m_damageGeneration = 0;
Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_thread[3]; Wrappers::HandleT<Wrappers::HandleTraits::HANDLENullTraits> m_thread[3];
Wrappers::Event m_terminateEvent; Wrappers::Event m_terminateEvent;