[idd] capture: replay only completed frames

Track the newest submitted and completed frame independently. Only
retain or replay a frame after its copy callback succeeds.

Keep the prior completed frame when a newer copy fails, and compare
publication sequences so out-of-order callbacks cannot move backward.
This commit is contained in:
Geoffrey McRae
2026-08-06 14:02:39 +10:00
parent 160c5fd1fa
commit f6c72b0bc5
3 changed files with 42 additions and 18 deletions

View File

@@ -1187,7 +1187,8 @@ bool CIndirectDeviceContext::SetupLGMP(size_t alignSize)
} }
m_maxFrameSize = maxFrameSize; m_maxFrameSize = maxFrameSize;
m_publishedFrameIndex.store(-1, std::memory_order_release); m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0; m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0, memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence)); sizeof(m_frameLastPublishSequence));
@@ -1241,7 +1242,8 @@ void CIndirectDeviceContext::DeInitLGMP()
if (m_lgmp == nullptr) if (m_lgmp == nullptr)
{ {
m_frameScheduler.Reset(); m_frameScheduler.Reset();
m_publishedFrameIndex.store(-1, std::memory_order_release); m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0; m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0, memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence)); sizeof(m_frameLastPublishSequence));
@@ -1261,7 +1263,8 @@ void CIndirectDeviceContext::DeInitLGMP()
m_frameScheduler.Reset(); m_frameScheduler.Reset();
AcquireSRWLockExclusive(&m_framePublishLock); AcquireSRWLockExclusive(&m_framePublishLock);
m_publishedFrameIndex.store(-1, std::memory_order_release); m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0; m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0, memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence)); sizeof(m_frameLastPublishSequence));
@@ -1633,15 +1636,15 @@ bool CIndirectDeviceContext::HasMatchingOwnerDelivery(
int CIndirectDeviceContext::FindAvailableFrameBuffer() const int CIndirectDeviceContext::FindAvailableFrameBuffer() const
{ {
const LONG publishedFrameIndex = const LONG readyFrameIndex =
m_publishedFrameIndex.load(std::memory_order_acquire); m_readyFrameIndex.load(std::memory_order_acquire);
int available = -1; int available = -1;
uint64_t newestPublish = 0; uint64_t newestPublish = 0;
for (unsigned frameIndex = 0; for (unsigned frameIndex = 0;
frameIndex < LGMP_Q_FRAME_BUFFER_LEN; frameIndex < LGMP_Q_FRAME_BUFFER_LEN;
++frameIndex) ++frameIndex)
{ {
if (static_cast<LONG>(frameIndex) == publishedFrameIndex || if (static_cast<LONG>(frameIndex) == readyFrameIndex ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) || m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
m_frameDelivery[frameIndex].ownerQueueMask || m_frameDelivery[frameIndex].ownerQueueMask ||
m_frameDelivery[frameIndex].sharedPending || m_frameDelivery[frameIndex].sharedPending ||
@@ -1722,7 +1725,7 @@ bool CIndirectDeviceContext::GetSharedFrameTarget(uint64_t now,
AcquireSRWLockShared(&m_framePublishLock); AcquireSRWLockShared(&m_framePublishLock);
const LONG frameIndex = const LONG frameIndex =
m_publishedFrameIndex.load(std::memory_order_acquire); m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 || if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) || m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0) lgmpHostQueuePending(m_frameQueue) != 0)
@@ -1752,7 +1755,7 @@ bool CIndirectDeviceContext::ReplaySharedFrame(uint64_t now, bool& retry)
AcquireSRWLockExclusive(&m_framePublishLock); AcquireSRWLockExclusive(&m_framePublishLock);
const LONG frameIndex = const LONG frameIndex =
m_publishedFrameIndex.load(std::memory_order_acquire); m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 || if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) || m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0) lgmpHostQueuePending(m_frameQueue) != 0)
@@ -1992,7 +1995,7 @@ bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex,
if (published) if (published)
{ {
m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence; m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence;
m_publishedFrameIndex.store( m_submittedFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release); static_cast<LONG>(frameIndex), std::memory_order_release);
} }
ReleaseSRWLockExclusive(&m_framePublishLock); ReleaseSRWLockExclusive(&m_framePublishLock);
@@ -2024,7 +2027,7 @@ bool CIndirectDeviceContext::RepublishFrameBuffer(
} }
const LONG frameIndex = const LONG frameIndex =
m_publishedFrameIndex.load(std::memory_order_acquire); m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 || if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire)) m_frameInFlight[frameIndex].load(std::memory_order_acquire))
{ {
@@ -2150,13 +2153,31 @@ void CIndirectDeviceContext::FailFrameBuffer(unsigned frameIndex)
InterlockedExchange((volatile LONG *)&m_frame[frameIndex]->timingValid, 0); InterlockedExchange((volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
FinalizeFrameBuffer(frameIndex); FinalizeFrameBuffer(frameIndex);
CompleteFrameBuffer(frameIndex); CompleteFrameBuffer(frameIndex, false);
} }
void CIndirectDeviceContext::CompleteFrameBuffer(unsigned frameIndex) void CIndirectDeviceContext::CompleteFrameBuffer(
unsigned frameIndex, bool succeeded)
{ {
if (frameIndex < LGMP_Q_FRAME_BUFFER_LEN) if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
m_frameInFlight[frameIndex].store(false, std::memory_order_release); return;
AcquireSRWLockExclusive(&m_framePublishLock);
if (succeeded)
{
// Completion callbacks may run out of order. Never replace a newer ready
// frame with an older submission.
const uint64_t sequence = m_frameLastPublishSequence[frameIndex];
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (sequence &&
(readyFrameIndex < 0 ||
sequence > m_frameLastPublishSequence[readyFrameIndex]))
m_readyFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release);
}
m_frameInFlight[frameIndex].store(false, std::memory_order_release);
ReleaseSRWLockExclusive(&m_framePublishLock);
} }
void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex, void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex,

View File

@@ -110,7 +110,10 @@ private:
size_t m_alignSize = 0; size_t m_alignSize = 0;
size_t m_frameMemoryOffset = 0; size_t m_frameMemoryOffset = 0;
size_t m_maxFrameSize = 0; size_t m_maxFrameSize = 0;
std::atomic<LONG> m_publishedFrameIndex = -1; // LGMP publication precedes copy completion, so only the ready index is safe
// to retain and replay.
std::atomic<LONG> m_submittedFrameIndex = -1;
std::atomic<LONG> m_readyFrameIndex = -1;
std::atomic<bool> m_frameInFlight[LGMP_Q_FRAME_BUFFER_LEN] = {}; std::atomic<bool> m_frameInFlight[LGMP_Q_FRAME_BUFFER_LEN] = {};
SRWLOCK m_framePublishLock = SRWLOCK_INIT; SRWLOCK m_framePublishLock = SRWLOCK_INIT;
uint64_t m_framePublishSequence = 0; uint64_t m_framePublishSequence = 0;
@@ -270,7 +273,7 @@ public:
bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule); bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule);
bool HasPublishedFrame() const bool HasPublishedFrame() const
{ {
return m_publishedFrameIndex.load(std::memory_order_acquire) >= 0; return m_readyFrameIndex.load(std::memory_order_acquire) >= 0;
} }
void ProcessFrameQueue(); void ProcessFrameQueue();
bool GetSharedFrameTarget(uint64_t now, uint64_t& target); bool GetSharedFrameTarget(uint64_t now, uint64_t& target);
@@ -283,7 +286,7 @@ public:
const CFrameScheduler::Schedule& schedule, bool periodic); const CFrameScheduler::Schedule& schedule, bool periodic);
void AbortFrameBuffer(unsigned frameIndex); void AbortFrameBuffer(unsigned frameIndex);
void FailFrameBuffer(unsigned frameIndex); void FailFrameBuffer(unsigned frameIndex);
void CompleteFrameBuffer(unsigned frameIndex); void CompleteFrameBuffer(unsigned frameIndex, bool succeeded);
void SetFrameTiming(unsigned frameIndex, uint64_t captureTime, void SetFrameTiming(unsigned frameIndex, uint64_t captureTime,
uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime); uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime);
void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const;

View File

@@ -744,7 +744,7 @@ void CSwapChainProcessor::CompletionFunction(
sc->m_devContext->RecordFrameTiming(readyEnd - publishStart); sc->m_devContext->RecordFrameTiming(readyEnd - publishStart);
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime); fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime);
sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex()); sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex(), true);
sc->ReleaseCandidate(candidateIndex); sc->ReleaseCandidate(candidateIndex);
} }