[idd] transport: track asynchronous frame readiness

This commit is contained in:
Geoffrey McRae
2026-08-12 20:09:21 +10:00
parent 8792c3c446
commit 51f432db00
8 changed files with 641 additions and 185 deletions

View File

@@ -701,13 +701,12 @@ bool CFrameScheduler::TryFrameSubmitted(const Schedule& schedule,
publication.deadlineSerial = schedule.deadlineSerial; publication.deadlineSerial = schedule.deadlineSerial;
publication.frameSerial = frameSerial; publication.frameSerial = frameSerial;
publication.deadline = schedule.deadline; publication.deadline = schedule.deadline;
publication.committed = true;
registered = true; registered = true;
} }
return registered; return registered;
} }
void CFrameScheduler::FramePublished(const Schedule& schedule, void CFrameScheduler::FrameCommitted(const Schedule& schedule,
uint32_t frameSerial, uint64_t now, bool periodic) uint32_t frameSerial, uint64_t now, bool periodic)
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
@@ -721,6 +720,25 @@ void CFrameScheduler::FramePublished(const Schedule& schedule,
if (publication) if (publication)
publication->committed = true; publication->committed = true;
if (periodic)
{
AdvanceCurrentDeadline();
AdvanceDeadline(now);
}
Client * client = FindClient(m_schedule.clientID);
if (client)
client->nextDelivery = m_nextDeadline;
}
}
void CFrameScheduler::FramePublished(const Schedule& schedule,
uint32_t frameSerial)
{
CSRWExclusiveLock lock(m_lock);
if (m_scheduling && schedule.clientID == m_schedule.clientID &&
schedule.generation == m_schedule.generation &&
schedule.epoch == m_schedule.epoch)
{
if (schedule.forceTicket > m_forceAckTicket) if (schedule.forceTicket > m_forceAckTicket)
m_forceAckTicket = schedule.forceTicket; m_forceAckTicket = schedule.forceTicket;
if (schedule.republishTicket > m_republishAckTicket) if (schedule.republishTicket > m_republishAckTicket)
@@ -735,26 +753,16 @@ void CFrameScheduler::FramePublished(const Schedule& schedule,
client->deliveredFrameValid = true; client->deliveredFrameValid = true;
} }
++m_publishedFrames; ++m_publishedFrames;
if (periodic)
{
AdvanceCurrentDeadline();
AdvanceDeadline(now);
}
if (client)
client->nextDelivery = m_nextDeadline;
} }
} }
void CFrameScheduler::FrameRetained(const Schedule& schedule, void CFrameScheduler::FrameRetained(const Schedule& schedule)
uint64_t now, bool periodic)
{ {
bool wake = false; bool wake = false;
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_scheduling && schedule.clientID == m_schedule.clientID && if (m_scheduling && schedule.clientID == m_schedule.clientID &&
schedule.generation == m_schedule.generation && schedule.generation == m_schedule.generation &&
schedule.epoch == m_schedule.epoch && schedule.epoch == m_schedule.epoch)
schedule.deadlineSerial == m_deadlineSerial &&
schedule.deadline == m_nextDeadline)
{ {
if (schedule.forceTicket > m_forceAckTicket) if (schedule.forceTicket > m_forceAckTicket)
m_forceAckTicket = schedule.forceTicket; m_forceAckTicket = schedule.forceTicket;
@@ -766,16 +774,6 @@ void CFrameScheduler::FrameRetained(const Schedule& schedule,
++m_republishRequestTicket; ++m_republishRequestTicket;
wake = true; wake = true;
} }
if (periodic)
{
AdvanceCurrentDeadline();
AdvanceDeadline(now);
}
Client * client = FindClient(m_schedule.clientID);
if (client)
client->nextDelivery = m_nextDeadline;
} }
lock.Unlock(); lock.Unlock();
@@ -783,6 +781,11 @@ void CFrameScheduler::FrameRetained(const Schedule& schedule,
WakePublisher(); WakePublisher();
} }
void CFrameScheduler::FrameFailed()
{
ForceFrame();
}
bool CFrameScheduler::TryFrameCompleted(const Schedule& schedule, bool CFrameScheduler::TryFrameCompleted(const Schedule& schedule,
uint32_t frameSerial, uint64_t completedAt) uint32_t frameSerial, uint64_t completedAt)
{ {

View File

@@ -172,10 +172,11 @@ public:
void FrameMissed(const Schedule& schedule, uint64_t now, bool periodic); void FrameMissed(const Schedule& schedule, uint64_t now, bool periodic);
void FrameSuperseded(); void FrameSuperseded();
bool TryFrameSubmitted(const Schedule& schedule, uint32_t frameSerial); bool TryFrameSubmitted(const Schedule& schedule, uint32_t frameSerial);
void FramePublished(const Schedule& schedule, uint32_t frameSerial, void FrameCommitted(const Schedule& schedule, uint32_t frameSerial,
uint64_t now, bool periodic); uint64_t now, bool periodic);
void FrameRetained(const Schedule& schedule, uint64_t now, void FramePublished(const Schedule& schedule, uint32_t frameSerial);
bool periodic); void FrameRetained(const Schedule& schedule);
void FrameFailed();
void FrameRepublished(const Schedule& schedule, uint32_t frameSerial); void FrameRepublished(const Schedule& schedule, uint32_t frameSerial);
bool TryFrameCompleted(const Schedule& schedule, uint32_t frameSerial, bool TryFrameCompleted(const Schedule& schedule, uint32_t frameSerial,
uint64_t completedAt); uint64_t completedAt);

View File

@@ -27,6 +27,13 @@ static bool ContentAfter(uint64_t value, uint64_t current)
static_cast<int64_t>(value - current) > 0); static_cast<int64_t>(value - current) > 0);
} }
static bool TokenMatches(const FrameToken& left, const FrameToken& right)
{
return left.backend == right.backend &&
left.epoch == right.epoch && left.slot == right.slot &&
left.serial == right.serial;
}
CFrameHub::CFrameHub() CFrameHub::CFrameHub()
{ {
m_wakeEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); m_wakeEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
@@ -89,8 +96,14 @@ bool CFrameHub::Bind(BackendId backend, uint32_t epoch, bool primary,
selected->backend.store(backend, std::memory_order_release); selected->backend.store(backend, std::memory_order_release);
selected->epoch.store(epoch, std::memory_order_release); selected->epoch.store(epoch, std::memory_order_release);
selected->primary = primary; selected->primary = primary;
selected->outstanding.store(0, std::memory_order_release);
SetEvent(selected->drained);
}
{
CSRWExclusiveLock lock(selected->laneLock);
selected->needsFullCopy = true; selected->needsFullCopy = true;
selected->lastContent = 0; selected->lastContent = 0;
selected->lastTerminalContent = 0;
selected->blockedContent = 0; selected->blockedContent = 0;
selected->blockedFrameSize = 0; selected->blockedFrameSize = 0;
selected->blockedAllocation = false; selected->blockedAllocation = false;
@@ -101,11 +114,10 @@ bool CFrameHub::Bind(BackendId backend, uint32_t epoch, bool primary,
selected->frameType = FRAME_TYPE_INVALID; selected->frameType = FRAME_TYPE_INVALID;
for (Sink::ResourceLane& lane : selected->lanes) for (Sink::ResourceLane& lane : selected->lanes)
lane = {}; lane = {};
selected->outstanding.store(0, std::memory_order_release);
SetEvent(selected->drained);
target.SetFrameScheduleEvent(m_wakeEvent);
} }
target.SetFrameEvents(this);
target.SetFrameScheduleEvent(m_wakeEvent);
{ {
CSRWExclusiveLock lock(m_listLock); CSRWExclusiveLock lock(m_listLock);
selected->active.store(true, std::memory_order_release); selected->active.store(true, std::memory_order_release);
@@ -135,21 +147,52 @@ void CFrameHub::Unbind(BackendId backend, uint32_t epoch)
if (!selected) if (!selected)
return; return;
IFrameSink * target;
{ {
CSRWExclusiveLock lock(selected->callLock); CSRWExclusiveLock lock(selected->callLock);
if (selected->target) target = selected->target;
selected->target->SetFrameScheduleEvent(nullptr); }
if (target)
target->SetFrameScheduleEvent(nullptr);
FrameToken cancel[FRAME_SINK_BUFFERS] = {};
unsigned cancelCount = 0;
{
CSRWExclusiveLock lock(selected->laneLock);
for (Sink::ResourceLane& lane : selected->lanes)
{
if (lane.phase == Sink::ResourceLane::PENDING)
cancel[cancelCount++] = lane.token;
else if (lane.phase == Sink::ResourceLane::FILLING)
lane.cancelRequested = true;
}
}
for (unsigned i = 0; i < cancelCount; ++i)
for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane)
{
CSRWSharedLock lock(selected->laneLock);
if (!TokenMatches(selected->lanes[lane].token, cancel[i]))
continue;
lock.Unlock();
CancelLane(*selected, lane, cancel[i]);
break;
} }
WaitForSingleObject(selected->drained, INFINITE); WaitForSingleObject(selected->drained, INFINITE);
if (target)
target->SetFrameEvents(nullptr);
{ {
CSRWExclusiveLock lock(selected->callLock); CSRWExclusiveLock lock(selected->callLock);
selected->target = nullptr; selected->target = nullptr;
selected->backend.store(0, std::memory_order_release); selected->backend.store(0, std::memory_order_release);
selected->epoch.store(0, std::memory_order_release); selected->epoch.store(0, std::memory_order_release);
selected->primary = false; selected->primary = false;
}
{
CSRWExclusiveLock lock(selected->laneLock);
selected->needsFullCopy = true; selected->needsFullCopy = true;
selected->lastContent = 0; selected->lastContent = 0;
selected->lastTerminalContent = 0;
selected->blockedContent = 0; selected->blockedContent = 0;
selected->blockedFrameSize = 0; selected->blockedFrameSize = 0;
selected->blockedAllocation = false; selected->blockedAllocation = false;
@@ -186,7 +229,10 @@ void CFrameHub::ReleaseTarget(Batch& batch, BatchTarget& target)
return; return;
target.releasePending = true; target.releasePending = true;
if (target.resourceLane < FRAME_SINK_BUFFERS) if (target.resourceLane < FRAME_SINK_BUFFERS)
{
CSRWExclusiveLock lock(target.sink->laneLock);
target.sink->lanes[target.resourceLane].busy = false; target.sink->lanes[target.resourceLane].busy = false;
}
if (target.sink->outstanding.fetch_sub( if (target.sink->outstanding.fetch_sub(
1, std::memory_order_acq_rel) == 1) 1, std::memory_order_acq_rel) == 1)
SetEvent(target.sink->drained); SetEvent(target.sink->drained);
@@ -199,37 +245,276 @@ void CFrameHub::ReleaseTarget(Batch& batch, BatchTarget& target)
batch.active = false; batch.active = false;
} }
void CFrameHub::CompleteTarget( bool CFrameHub::DetachTarget(Batch& batch, BatchTarget& target,
Batch& batch, BatchTarget& target, bool succeeded) FrameToken& token)
{ {
CSRWExclusiveLock call(target.sink->callLock); if (!target.active || target.releasePending ||
if (target.sink->target && target.resourceLane >= FRAME_SINK_BUFFERS)
target.sink->backend.load(std::memory_order_acquire) == return false;
target.backend &&
target.sink->epoch.load(std::memory_order_acquire) == target.epoch) token.backend = target.backend;
token.epoch = target.epoch;
token.slot = target.localSlot;
token.serial = batch.serial;
{ {
target.sink->target->CompleteFrameBuffer(target.localSlot, succeeded); CSRWExclusiveLock lock(target.sink->laneLock);
if (succeeded) Sink::ResourceLane& lane =
{ target.sink->lanes[target.resourceLane];
if (ContentAfter(target.content, target.sink->lastContent)) if (!lane.busy || lane.phase != Sink::ResourceLane::IDLE)
{ return false;
target.sink->lastContent = target.content; lane.token = token;
target.sink->pitch = target.pitch; lane.schedule = target.schedule;
target.sink->width = target.width; lane.content = target.content;
target.sink->height = target.height; lane.captureTime = target.captureTime;
target.sink->format = target.format; lane.postProcessTime = target.postProcessTime;
target.sink->frameType = target.frameType; lane.copyTime = target.copyTime;
target.sink->needsFullCopy = false; lane.readyTime = target.readyTime;
target.sink->blockedContent = 0; lane.holdTime = target.holdTime;
target.sink->blockedFrameSize = 0; lane.filledAt = target.filledAt;
target.sink->blockedAllocation = false; lane.workStart = target.workStart;
lane.pitch = target.pitch;
lane.width = target.width;
lane.height = target.height;
lane.format = target.format;
lane.frameType = target.frameType;
lane.phase = Sink::ResourceLane::FILLING;
lane.timingValid = target.timingValid;
lane.resultPending = false;
lane.callActive = false;
lane.cancelRequested = false;
} }
target.releasePending = true;
target.active = false;
for (unsigned i = 0; i < batch.count; ++i)
if (batch.targets[i].active)
return true;
batch.active = false;
return true;
}
void CFrameHub::FillLane(Sink& sink, unsigned laneIndex,
const FrameToken& token)
{
if (laneIndex >= FRAME_SINK_BUFFERS)
return;
{
CSRWExclusiveLock lock(sink.laneLock);
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (lane.phase != Sink::ResourceLane::FILLING ||
!TokenMatches(lane.token, token))
return;
lane.callActive = true;
} }
IFrameSink * target = sink.target;
const FrameFill fill = target ? target->FrameFilled(token) :
FrameFill::REJECTED;
bool terminal = false;
bool cancel = false;
FrameDone result = FrameDone::FAILED;
uint64_t readyAt = 0;
{
CSRWExclusiveLock lock(sink.laneLock);
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (lane.phase != Sink::ResourceLane::FILLING ||
!TokenMatches(lane.token, token))
return;
lane.callActive = false;
if (lane.resultPending)
{
terminal = true;
result = lane.pendingResult;
readyAt = lane.pendingReadyAt;
lane.resultPending = false;
}
else if (fill == FrameFill::READY)
{
terminal = true;
result = FrameDone::READY;
}
else if (fill == FrameFill::REJECTED)
terminal = true;
else else
target.sink->needsFullCopy = true; {
lane.phase = Sink::ResourceLane::PENDING;
cancel = lane.cancelRequested ||
!sink.active.load(std::memory_order_acquire);
}
}
if (terminal)
CompleteLane(sink, laneIndex, token, result, readyAt);
else if (cancel)
CancelLane(sink, laneIndex, token);
}
void CFrameHub::CancelLane(Sink& sink, unsigned laneIndex,
const FrameToken& token)
{
if (laneIndex >= FRAME_SINK_BUFFERS)
return;
{
CSRWExclusiveLock lock(sink.laneLock);
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (lane.phase != Sink::ResourceLane::PENDING ||
!TokenMatches(lane.token, token))
return;
lane.phase = Sink::ResourceLane::CANCELING;
lane.callActive = true;
}
IFrameSink * target = sink.target;
if (target)
target->CancelFrame(token);
FrameDone result = FrameDone::SUPERSEDED;
uint64_t readyAt = 0;
{
CSRWExclusiveLock lock(sink.laneLock);
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (lane.phase != Sink::ResourceLane::CANCELING ||
!TokenMatches(lane.token, token))
return;
lane.callActive = false;
if (lane.resultPending)
{
result = lane.pendingResult;
readyAt = lane.pendingReadyAt;
lane.resultPending = false;
}
}
CompleteLane(sink, laneIndex, token, result, readyAt);
}
void CFrameHub::CompleteLane(Sink& sink, unsigned laneIndex,
const FrameToken& token, FrameDone result, uint64_t readyAt)
{
if (laneIndex >= FRAME_SINK_BUFFERS)
return;
Sink::ResourceLane completed;
{
CSRWExclusiveLock lock(sink.laneLock);
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (!lane.busy || lane.phase == Sink::ResourceLane::IDLE ||
lane.phase == Sink::ResourceLane::COMPLETING ||
!TokenMatches(lane.token, token))
return;
lane.phase = Sink::ResourceLane::COMPLETING;
completed = lane;
}
if (!readyAt)
readyAt = CFrameScheduler::Nanotime();
IFrameSink * target = sink.target;
if (target)
{
if (result == FrameDone::READY && completed.timingValid)
{
uint64_t readyTime = completed.readyTime;
if (readyAt >= completed.filledAt)
readyTime += readyAt - completed.filledAt;
target->SetFrameTiming(completed.localSlot, completed.captureTime,
completed.postProcessTime, completed.copyTime, readyTime,
completed.holdTime, completed.schedule, readyAt);
if (completed.workStart && readyAt >= completed.workStart)
target->TryRecordFrameTiming(readyAt - completed.workStart);
}
target->CompleteFrameBuffer(completed.localSlot, result);
}
{
CSRWExclusiveLock lock(sink.laneLock);
const bool newestTerminal =
ContentAfter(completed.content, sink.lastTerminalContent);
if (newestTerminal)
sink.lastTerminalContent = completed.content;
if (result == FrameDone::READY &&
(newestTerminal ||
completed.content == sink.lastTerminalContent))
{
if (ContentAfter(completed.content, sink.lastContent))
{
sink.lastContent = completed.content;
sink.pitch = completed.pitch;
sink.width = completed.width;
sink.height = completed.height;
sink.format = completed.format;
sink.frameType = completed.frameType;
sink.needsFullCopy = false;
sink.blockedContent = 0;
sink.blockedFrameSize = 0;
sink.blockedAllocation = false;
}
}
else if (newestTerminal)
sink.needsFullCopy = true;
Sink::ResourceLane& lane = sink.lanes[laneIndex];
if (lane.phase == Sink::ResourceLane::COMPLETING &&
TokenMatches(lane.token, token))
{
lane.token = {};
lane.schedule = {};
lane.content = 0;
lane.phase = Sink::ResourceLane::IDLE;
lane.timingValid = false;
lane.resultPending = false;
lane.callActive = false;
lane.cancelRequested = false;
lane.busy = false;
}
}
if (sink.outstanding.fetch_sub(1, std::memory_order_acq_rel) == 1)
SetEvent(sink.drained);
SetEvent(m_wakeEvent);
}
void CFrameHub::OnFrameDone(const FrameToken& token, FrameDone result,
uint64_t readyAt)
{
if (!token.backend || !token.epoch || !token.serial)
return;
for (Sink& sink : m_sinks)
{
if (sink.backend.load(std::memory_order_acquire) != token.backend ||
sink.epoch.load(std::memory_order_acquire) != token.epoch)
continue;
unsigned laneIndex = FRAME_SINK_BUFFERS;
{
CSRWExclusiveLock lock(sink.laneLock);
for (unsigned i = 0; i < FRAME_SINK_BUFFERS; ++i)
{
Sink::ResourceLane& lane = sink.lanes[i];
if (!lane.busy || !TokenMatches(lane.token, token) ||
lane.phase == Sink::ResourceLane::IDLE ||
lane.phase == Sink::ResourceLane::COMPLETING)
continue;
if (lane.callActive)
{
if (!lane.resultPending)
{
lane.pendingResult = result;
lane.pendingReadyAt = readyAt;
lane.resultPending = true;
}
return;
}
if (lane.phase == Sink::ResourceLane::PENDING ||
lane.phase == Sink::ResourceLane::CANCELING)
laneIndex = i;
break;
}
}
if (laneIndex < FRAME_SINK_BUFFERS)
CompleteLane(sink, laneIndex, token, result, readyAt);
return;
} }
ReleaseTarget(batch, target);
} }
size_t CFrameHub::GetMaxFrameSize() const size_t CFrameHub::GetMaxFrameSize() const
@@ -281,11 +566,12 @@ bool CFrameHub::NeedsFrame() const
if (!refs[i].sink->active.load(std::memory_order_acquire) || if (!refs[i].sink->active.load(std::memory_order_acquire) ||
!refs[i].sink->target) !refs[i].sink->target)
continue; continue;
const size_t maxFrameSize = refs[i].sink->target->GetMaxFrameSize();
CSRWSharedLock state(refs[i].sink->laneLock);
if (refs[i].sink->blockedContent == newest && if (refs[i].sink->blockedContent == newest &&
refs[i].sink->blockedFrameSize && refs[i].sink->blockedFrameSize &&
(refs[i].sink->blockedAllocation || (refs[i].sink->blockedAllocation ||
refs[i].sink->target->GetMaxFrameSize() < maxFrameSize < refs[i].sink->blockedFrameSize))
refs[i].sink->blockedFrameSize))
continue; continue;
if (refs[i].sink->needsFullCopy || if (refs[i].sink->needsFullCopy ||
ContentAfter(newest, refs[i].sink->lastContent)) ContentAfter(newest, refs[i].sink->lastContent))
@@ -485,18 +771,24 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
const size_t maxFrameSize = sink.target->GetMaxFrameSize(); const size_t maxFrameSize = sink.target->GetMaxFrameSize();
if (!maxFrameSize || frameSize > maxFrameSize) if (!maxFrameSize || frameSize > maxFrameSize)
{ {
CSRWExclusiveLock lock(sink.laneLock);
sink.blockedContent = contentSerial; sink.blockedContent = contentSerial;
sink.blockedFrameSize = frameSize; sink.blockedFrameSize = frameSize;
sink.blockedAllocation = false; sink.blockedAllocation = false;
continue; continue;
} }
bool forceFull;
{
CSRWSharedLock lock(sink.laneLock);
const bool layoutChanged = sink.pitch != pitch || const bool layoutChanged = sink.pitch != pitch ||
sink.width != dstFormat.width || sink.height != dstFormat.height || sink.width != dstFormat.width ||
sink.height != dstFormat.height ||
sink.format != dstFormat.desc.Format || sink.format != dstFormat.desc.Format ||
sink.frameType != dstFormat.format; sink.frameType != dstFormat.format;
const bool forceFull = sink.needsFullCopy || layoutChanged || forceFull = sink.needsFullCopy || layoutChanged ||
sink.lastContent + 1 != contentSerial; sink.lastContent + 1 != contentSerial;
}
const RECT * damage = forceFull ? nullptr : dirtyRects; const RECT * damage = forceFull ? nullptr : dirtyRects;
const unsigned damageCount = forceFull ? 0 : nbDirtyRects; const unsigned damageCount = forceFull ? 0 : nbDirtyRects;
SinkTarget result = sink.target->PrepareFrameBuffer( SinkTarget result = sink.target->PrepareFrameBuffer(
@@ -506,14 +798,19 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
{ {
if (result.mem) if (result.mem)
{ {
{
CSRWExclusiveLock lock(sink.laneLock);
sink.blockedContent = contentSerial; sink.blockedContent = contentSerial;
sink.blockedFrameSize = frameSize; sink.blockedFrameSize = frameSize;
sink.blockedAllocation = true; sink.blockedAllocation = true;
}
sink.target->AbortFrameBuffer(result.slot); sink.target->AbortFrameBuffer(result.slot);
} }
continue; continue;
} }
{
CSRWExclusiveLock lock(sink.laneLock);
unsigned resourceLane = FRAME_SINK_BUFFERS; unsigned resourceLane = FRAME_SINK_BUFFERS;
for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane) for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane)
{ {
@@ -544,6 +841,7 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
} }
if (resourceLane == FRAME_SINK_BUFFERS) if (resourceLane == FRAME_SINK_BUFFERS)
{ {
lock.Unlock();
sink.target->AbortFrameBuffer(result.slot); sink.target->AbortFrameBuffer(result.slot);
continue; continue;
} }
@@ -555,6 +853,7 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
lane.direct = request.primary; lane.direct = request.primary;
lane.valid = true; lane.valid = true;
lane.busy = true; lane.busy = true;
lane.phase = Sink::ResourceLane::IDLE;
unsigned index = batch->count; unsigned index = batch->count;
if (sink.primary && index) if (sink.primary && index)
@@ -588,7 +887,7 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
ResetEvent(sink.drained); ResetEvent(sink.drained);
PreparedFrameBuffer& output = prepared.targets[index]; PreparedFrameBuffer& output = prepared.targets[index];
output.token.sink = request.backend; output.token.backend = request.backend;
output.token.epoch = request.epoch; output.token.epoch = request.epoch;
output.token.slot = result.slot; output.token.slot = result.slot;
output.token.serial = batch->serial; output.token.serial = batch->serial;
@@ -600,6 +899,7 @@ bool CFrameHub::PrepareFrameBatch(const FramePlan& plan,
output.direct = request.primary; output.direct = request.primary;
output.fullCopy = forceFull || result.fullCopy; output.fullCopy = forceFull || result.fullCopy;
} }
}
prepared.count = batch->count; prepared.count = batch->count;
if (!batch->count) if (!batch->count)
{ {
@@ -636,7 +936,10 @@ uint32_t CFrameHub::PublishFrameBatch(const FrameBatchToken& token)
{ {
if (valid) if (valid)
target.sink->target->AbortFrameBuffer(target.localSlot); target.sink->target->AbortFrameBuffer(target.localSlot);
{
CSRWExclusiveLock state(target.sink->laneLock);
target.sink->needsFullCopy = true; target.sink->needsFullCopy = true;
}
ReleaseTarget(batch, target); ReleaseTarget(batch, target);
continue; continue;
} }
@@ -658,6 +961,14 @@ void CFrameHub::CommitFrameBatch(const FrameBatchToken& token)
if (token.slot >= FRAME_BATCHES) if (token.slot >= FRAME_BATCHES)
return; return;
Batch& batch = m_batches[token.slot]; Batch& batch = m_batches[token.slot];
struct FillRequest
{
Sink * sink;
unsigned lane;
FrameToken token;
bool succeeded;
} fill[FRAME_MAX_SINKS] = {};
unsigned fillCount = 0;
CSRWExclusiveLock lock(batch.lock); CSRWExclusiveLock lock(batch.lock);
if (!BatchValid(batch, token)) if (!BatchValid(batch, token))
return; return;
@@ -678,8 +989,22 @@ void CFrameHub::CommitFrameBatch(const FrameBatchToken& token)
} }
target.committed = true; target.committed = true;
if (target.completionPending) if (target.completionPending)
CompleteTarget(batch, target, target.completionSucceeded); {
FrameToken frameToken;
const unsigned lane = target.resourceLane;
Sink * sink = target.sink;
if (DetachTarget(batch, target, frameToken))
fill[fillCount++] = {
sink, lane, frameToken, target.completionSucceeded };
} }
}
lock.Unlock();
for (unsigned i = 0; i < fillCount; ++i)
if (fill[i].succeeded)
FillLane(*fill[i].sink, fill[i].lane, fill[i].token);
else
CompleteLane(*fill[i].sink, fill[i].lane, fill[i].token,
FrameDone::FAILED, CFrameScheduler::Nanotime());
} }
void CFrameHub::AbortFrameBatch(const FrameBatchToken& token) void CFrameHub::AbortFrameBatch(const FrameBatchToken& token)
@@ -701,7 +1026,10 @@ void CFrameHub::AbortFrameBatch(const FrameBatchToken& token)
target.backend && target.backend &&
target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->epoch.load(std::memory_order_acquire) == target.epoch)
target.sink->target->AbortFrameBuffer(target.localSlot); target.sink->target->AbortFrameBuffer(target.localSlot);
{
CSRWExclusiveLock state(target.sink->laneLock);
target.sink->needsFullCopy = true; target.sink->needsFullCopy = true;
}
ReleaseTarget(batch, target); ReleaseTarget(batch, target);
} }
} }
@@ -730,7 +1058,10 @@ void CFrameHub::FailFrameBatch(const FrameBatchToken& token)
else else
target.sink->target->AbortFrameBuffer(target.localSlot); target.sink->target->AbortFrameBuffer(target.localSlot);
} }
{
CSRWExclusiveLock state(target.sink->laneLock);
target.sink->needsFullCopy = true; target.sink->needsFullCopy = true;
}
ReleaseTarget(batch, target); ReleaseTarget(batch, target);
} }
} }
@@ -804,19 +1135,18 @@ void CFrameHub::SetFrameTargetTiming(const FrameBatchToken& token,
if (token.slot >= FRAME_BATCHES) if (token.slot >= FRAME_BATCHES)
return; return;
Batch& batch = m_batches[token.slot]; Batch& batch = m_batches[token.slot];
CSRWSharedLock lock(batch.lock); CSRWExclusiveLock lock(batch.lock);
if (!BatchValid(batch, token) || index >= batch.count || if (!BatchValid(batch, token) || index >= batch.count ||
!batch.targets[index].active) !batch.targets[index].active)
return; return;
BatchTarget& target = batch.targets[index]; BatchTarget& target = batch.targets[index];
CSRWSharedLock call(target.sink->callLock); target.captureTime = captureTime;
if (target.sink->target && target.postProcessTime = postProcessTime;
target.sink->backend.load(std::memory_order_acquire) == target.copyTime = copyTime;
target.backend && target.readyTime = readyTime;
target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.holdTime = holdTime;
target.sink->target->SetFrameTiming(target.localSlot, captureTime, target.filledAt = completedAt;
postProcessTime, copyTime, readyTime, holdTime, target.timingValid = true;
target.schedule, completedAt);
} }
void CFrameHub::TryRecordFrameTiming(const FrameBatchToken& token, void CFrameHub::TryRecordFrameTiming(const FrameBatchToken& token,
@@ -825,17 +1155,13 @@ void CFrameHub::TryRecordFrameTiming(const FrameBatchToken& token,
if (token.slot >= FRAME_BATCHES) if (token.slot >= FRAME_BATCHES)
return; return;
Batch& batch = m_batches[token.slot]; Batch& batch = m_batches[token.slot];
CSRWSharedLock lock(batch.lock); CSRWExclusiveLock lock(batch.lock);
if (!BatchValid(batch, token) || index >= batch.count || if (!BatchValid(batch, token) || index >= batch.count ||
!batch.targets[index].active) !batch.targets[index].active)
return; return;
BatchTarget& target = batch.targets[index]; BatchTarget& target = batch.targets[index];
CSRWSharedLock call(target.sink->callLock); if (target.filledAt >= duration)
if (target.sink->target && target.workStart = target.filledAt - duration;
target.sink->backend.load(std::memory_order_acquire) ==
target.backend &&
target.sink->epoch.load(std::memory_order_acquire) == target.epoch)
target.sink->target->TryRecordFrameTiming(duration);
} }
void CFrameHub::CompleteFrameTarget(const FrameBatchToken& token, void CFrameHub::CompleteFrameTarget(const FrameBatchToken& token,
@@ -855,7 +1181,17 @@ void CFrameHub::CompleteFrameTarget(const FrameBatchToken& token,
target.completionSucceeded = succeeded; target.completionSucceeded = succeeded;
return; return;
} }
CompleteTarget(batch, target, succeeded); FrameToken frameToken;
const unsigned lane = target.resourceLane;
Sink * sink = target.sink;
if (!DetachTarget(batch, target, frameToken))
return;
lock.Unlock();
if (succeeded)
FillLane(*sink, lane, frameToken);
else
CompleteLane(*sink, lane, frameToken, FrameDone::FAILED,
CFrameScheduler::Nanotime());
} }
void CFrameHub::ObserveFrame(uint64_t now) void CFrameHub::ObserveFrame(uint64_t now)

View File

@@ -27,22 +27,55 @@
#include <atomic> #include <atomic>
class CFrameHub final : public IFrameTransport class CFrameHub final : public IFrameTransport, public IFrameEvents
{ {
private: private:
struct Sink struct Sink
{ {
struct ResourceLane struct ResourceLane
{ {
enum Phase
{
IDLE,
FILLING,
PENDING,
CANCELING,
COMPLETING,
};
uint8_t * mem = nullptr; uint8_t * mem = nullptr;
uint64_t heapOffset = 0; uint64_t heapOffset = 0;
unsigned localSlot = 0; unsigned localSlot = 0;
bool direct = false; bool direct = false;
bool valid = false; bool valid = false;
bool busy = false; bool busy = false;
FrameToken token = {};
CFrameScheduler::Schedule schedule = {};
uint64_t content = 0;
uint64_t captureTime = 0;
uint64_t postProcessTime = 0;
uint64_t copyTime = 0;
uint64_t readyTime = 0;
uint64_t holdTime = 0;
uint64_t filledAt = 0;
uint64_t workStart = 0;
unsigned pitch = 0;
unsigned width = 0;
unsigned height = 0;
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
FrameType frameType = FRAME_TYPE_INVALID;
Phase phase = IDLE;
FrameDone pendingResult = FrameDone::FAILED;
uint64_t pendingReadyAt = 0;
bool timingValid = false;
bool resultPending = false;
bool callActive = false;
bool cancelRequested = false;
}; };
CSRWLock callLock; CSRWLock callLock;
CSRWLock laneLock;
IFrameSink * target = nullptr; IFrameSink * target = nullptr;
HANDLE drained = nullptr; HANDLE drained = nullptr;
std::atomic<unsigned> outstanding = 0; std::atomic<unsigned> outstanding = 0;
@@ -53,6 +86,7 @@ private:
bool primary = false; bool primary = false;
bool needsFullCopy = true; bool needsFullCopy = true;
uint64_t lastContent = 0; uint64_t lastContent = 0;
uint64_t lastTerminalContent = 0;
uint64_t blockedContent = 0; uint64_t blockedContent = 0;
size_t blockedFrameSize = 0; size_t blockedFrameSize = 0;
bool blockedAllocation = false; bool blockedAllocation = false;
@@ -87,6 +121,14 @@ private:
bool completionPending = false; bool completionPending = false;
bool completionSucceeded = false; bool completionSucceeded = false;
bool releasePending = false; bool releasePending = false;
uint64_t captureTime = 0;
uint64_t postProcessTime = 0;
uint64_t copyTime = 0;
uint64_t readyTime = 0;
uint64_t holdTime = 0;
uint64_t filledAt = 0;
uint64_t workStart = 0;
bool timingValid = false;
bool active = false; bool active = false;
}; };
@@ -117,7 +159,14 @@ private:
unsigned Snapshot(SinkRef refs[FRAME_MAX_SINKS]) const; unsigned Snapshot(SinkRef refs[FRAME_MAX_SINKS]) const;
bool BatchValid(const Batch& batch, const FrameBatchToken& token) const; bool BatchValid(const Batch& batch, const FrameBatchToken& token) const;
void ReleaseTarget(Batch& batch, BatchTarget& target); void ReleaseTarget(Batch& batch, BatchTarget& target);
void CompleteTarget(Batch& batch, BatchTarget& target, bool succeeded); bool DetachTarget(Batch& batch, BatchTarget& target,
FrameToken& token);
void FillLane(Sink& sink, unsigned laneIndex,
const FrameToken& token);
void CancelLane(Sink& sink, unsigned laneIndex,
const FrameToken& token);
void CompleteLane(Sink& sink, unsigned laneIndex,
const FrameToken& token, FrameDone result, uint64_t readyAt);
public: public:
CFrameHub(); CFrameHub();
@@ -130,6 +179,9 @@ public:
IFrameSink& sink); IFrameSink& sink);
void Unbind(BackendId backend, uint32_t epoch); void Unbind(BackendId backend, uint32_t epoch);
void OnFrameDone(const FrameToken& token, FrameDone result,
uint64_t readyAt) override;
size_t GetMaxFrameSize() const override; size_t GetMaxFrameSize() const override;
uint64_t NextContentSerial() override; uint64_t NextContentSerial() override;
void FrameProductReady(uint64_t contentSerial) override; void FrameProductReady(uint64_t contentSerial) override;

View File

@@ -28,11 +28,22 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
class IFrameEvents
{
public:
virtual ~IFrameEvents() = default;
virtual void OnFrameDone(const FrameToken& token, FrameDone result,
uint64_t readyAt) = 0;
};
class IFrameSink class IFrameSink
{ {
public: public:
virtual ~IFrameSink() = default; virtual ~IFrameSink() = default;
// Clearing events waits for callbacks through the previous pointer to end.
virtual void SetFrameEvents(IFrameEvents * events) = 0;
virtual size_t GetMaxFrameSize() const = 0; virtual size_t GetMaxFrameSize() const = 0;
virtual bool FrameBufferAvailable( virtual bool FrameBufferAvailable(
@@ -60,7 +71,12 @@ public:
bool deliveredToOwner) = 0; bool deliveredToOwner) = 0;
virtual void AbortFrameBuffer(unsigned slot) = 0; virtual void AbortFrameBuffer(unsigned slot) = 0;
virtual void FailFrameBuffer(unsigned slot) = 0; virtual void FailFrameBuffer(unsigned slot) = 0;
virtual void CompleteFrameBuffer(unsigned slot, bool succeeded) = 0; // A terminal callback raised before this returns takes precedence over the
// return value.
virtual FrameFill FrameFilled(const FrameToken& token) = 0;
// On return, the sink no longer accesses the frame identified by token.
virtual void CancelFrame(const FrameToken& token) = 0;
virtual void CompleteFrameBuffer(unsigned slot, FrameDone result) = 0;
virtual void SetFrameTiming(unsigned slot, uint64_t captureTime, virtual void SetFrameTiming(unsigned slot, uint64_t captureTime,
uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime, uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime,
uint64_t holdTime, const CFrameScheduler::Schedule& schedule, uint64_t holdTime, const CFrameScheduler::Schedule& schedule,

View File

@@ -33,12 +33,26 @@ enum : unsigned
struct FrameToken struct FrameToken
{ {
uint32_t sink = 0; uint32_t backend = 0;
uint32_t epoch = 0; uint32_t epoch = 0;
uint32_t slot = 0; uint32_t slot = 0;
uint64_t serial = 0; uint64_t serial = 0;
}; };
enum class FrameFill
{
READY,
PENDING,
REJECTED,
};
enum class FrameDone
{
READY,
FAILED,
SUPERSEDED,
};
struct PreparedFrameBuffer struct PreparedFrameBuffer
{ {
FrameToken token; FrameToken token;

View File

@@ -202,6 +202,7 @@ bool CLGMPFrameTransport::Setup(size_t alignSize)
m_readyFrameIndex.store(-1, std::memory_order_release); m_readyFrameIndex.store(-1, std::memory_order_release);
m_deferredOwnerFrameIndex = -1; m_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0; m_framePublishSequence = 0;
m_frameReadySequence = 0;
memset(m_frameLastPublishSequence, 0, memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence)); sizeof(m_frameLastPublishSequence));
for (FrameDelivery& delivery : m_frameDelivery) for (FrameDelivery& delivery : m_frameDelivery)
@@ -222,6 +223,7 @@ void CLGMPFrameTransport::DeInit()
m_readyFrameIndex.store(-1, std::memory_order_release); m_readyFrameIndex.store(-1, std::memory_order_release);
m_deferredOwnerFrameIndex = -1; m_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0; m_framePublishSequence = 0;
m_frameReadySequence = 0;
memset(m_frameLastPublishSequence, 0, memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence)); sizeof(m_frameLastPublishSequence));
memset(m_frameCompleted, 0, sizeof(m_frameCompleted)); memset(m_frameCompleted, 0, sizeof(m_frameCompleted));
@@ -845,6 +847,8 @@ SinkTarget CLGMPFrameTransport::PrepareFrameBuffer(
flags |= FRAME_FLAG_TRUNCATED; flags |= FRAME_FLAG_TRUNCATED;
fi->formatVer = m_formatVer; fi->formatVer = m_formatVer;
if (!m_frameSerial)
++m_frameSerial;
fi->frameSerial = m_frameSerial++; fi->frameSerial = m_frameSerial++;
fi->screenWidth = srcFormat.width; fi->screenWidth = srcFormat.width;
fi->screenHeight = srcFormat.height; fi->screenHeight = srcFormat.height;
@@ -1008,6 +1012,8 @@ bool CLGMPFrameTransport::PublishFrameBuffer(unsigned frameIndex,
if (published) if (published)
{ {
m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence; m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence;
m_frameSchedule[frameIndex] = schedule;
m_frameDelivered[frameIndex] = deliveredToOwner;
m_deferredOwnerFrameIndex = schedule.clientID && !deliveredToOwner ? m_deferredOwnerFrameIndex = schedule.clientID && !deliveredToOwner ?
static_cast<LONG>(frameIndex) : -1; static_cast<LONG>(frameIndex) : -1;
m_submittedFrameIndex.store( m_submittedFrameIndex.store(
@@ -1122,15 +1128,14 @@ void CLGMPFrameTransport::CommitFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule, bool periodic, const CFrameScheduler::Schedule& schedule, bool periodic,
bool deliveredToOwner) bool deliveredToOwner)
{ {
UNREFERENCED_PARAMETER(deliveredToOwner);
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN) if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return; return;
const uint64_t now = CFrameScheduler::Nanotime(); const uint64_t now = CFrameScheduler::Nanotime();
if (deliveredToOwner) m_frameScheduler.FrameCommitted(
m_frameScheduler.FramePublished(
schedule, m_frame[frameIndex]->frameSerial, now, periodic); schedule, m_frame[frameIndex]->frameSerial, now, periodic);
else
m_frameScheduler.FrameRetained(schedule, now, periodic);
} }
bool CLGMPFrameTransport::TryFrameSubmitted(unsigned frameIndex, bool CLGMPFrameTransport::TryFrameSubmitted(unsigned frameIndex,
@@ -1200,16 +1205,21 @@ void CLGMPFrameTransport::FailFrameBuffer(unsigned frameIndex)
InterlockedExchange( InterlockedExchange(
(volatile LONG *)&m_frame[frameIndex]->timingValid, 0); (volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
FinalizeFrameBuffer(frameIndex); FinalizeFrameBuffer(frameIndex);
CompleteFrameBuffer(frameIndex, false); AbortFrameBuffer(frameIndex);
} }
void CLGMPFrameTransport::CompleteFrameBuffer( void CLGMPFrameTransport::CompleteFrameBuffer(
unsigned frameIndex, bool succeeded) unsigned frameIndex, FrameDone result)
{ {
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN) if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return; return;
const bool succeeded = result == FrameDone::READY;
CSRWExclusiveLock lock(m_framePublishLock); CSRWExclusiveLock lock(m_framePublishLock);
const CFrameScheduler::Schedule schedule = m_frameSchedule[frameIndex];
const uint32_t frameSerial = m_frame[frameIndex]->frameSerial;
const bool delivered = m_frameDelivered[frameIndex];
const uint64_t sequence = m_frameLastPublishSequence[frameIndex];
m_frameCompleted[frameIndex] = succeeded; m_frameCompleted[frameIndex] = succeeded;
if (!succeeded && if (!succeeded &&
m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex)) m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex))
@@ -1218,7 +1228,6 @@ void CLGMPFrameTransport::CompleteFrameBuffer(
{ {
// Completion callbacks may run out of order. Never replace a newer ready // Completion callbacks may run out of order. Never replace a newer ready
// frame with an older submission. // frame with an older submission.
const uint64_t sequence = m_frameLastPublishSequence[frameIndex];
const LONG readyFrameIndex = const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire); m_readyFrameIndex.load(std::memory_order_acquire);
if (sequence && if (sequence &&
@@ -1228,6 +1237,22 @@ void CLGMPFrameTransport::CompleteFrameBuffer(
static_cast<LONG>(frameIndex), std::memory_order_release); static_cast<LONG>(frameIndex), std::memory_order_release);
} }
m_frameInFlight[frameIndex].store(false, std::memory_order_release); m_frameInFlight[frameIndex].store(false, std::memory_order_release);
const bool newerThanReady =
sequence && sequence > m_frameReadySequence;
if (result == FrameDone::READY && newerThanReady)
m_frameReadySequence = sequence;
if (result == FrameDone::READY && newerThanReady)
{
if (delivered)
m_frameScheduler.FramePublished(schedule, frameSerial);
else
m_frameScheduler.FrameRetained(schedule);
}
else if (result == FrameDone::FAILED && newerThanReady)
m_frameScheduler.FrameFailed();
else if (result == FrameDone::SUPERSEDED && newerThanReady)
m_frameScheduler.FrameSuperseded();
} }
void CLGMPFrameTransport::SetFrameTiming(unsigned frameIndex, void CLGMPFrameTransport::SetFrameTiming(unsigned frameIndex,

View File

@@ -101,7 +101,11 @@ private:
bool m_frameCompleted[LGMP_Q_FRAME_BUFFER_LEN] = {}; bool m_frameCompleted[LGMP_Q_FRAME_BUFFER_LEN] = {};
CSRWLock m_framePublishLock; CSRWLock m_framePublishLock;
uint64_t m_framePublishSequence = 0; uint64_t m_framePublishSequence = 0;
uint64_t m_frameReadySequence = 0;
uint64_t m_frameLastPublishSequence[LGMP_Q_FRAME_BUFFER_LEN] = {}; uint64_t m_frameLastPublishSequence[LGMP_Q_FRAME_BUFFER_LEN] = {};
CFrameScheduler::Schedule
m_frameSchedule[LGMP_Q_FRAME_BUFFER_LEN] = {};
bool m_frameDelivered[LGMP_Q_FRAME_BUFFER_LEN] = {};
FrameDelivery m_frameDelivery[LGMP_Q_FRAME_BUFFER_LEN] = {}; FrameDelivery m_frameDelivery[LGMP_Q_FRAME_BUFFER_LEN] = {};
OwnerDelivery m_ownerDelivery[LGMP_Q_FRAME_LEN] = {}; OwnerDelivery m_ownerDelivery[LGMP_Q_FRAME_LEN] = {};
@@ -160,6 +164,7 @@ public:
CLGMPFrameTransport(const CLGMPFrameTransport&) = delete; CLGMPFrameTransport(const CLGMPFrameTransport&) = delete;
CLGMPFrameTransport& operator=(const CLGMPFrameTransport&) = delete; CLGMPFrameTransport& operator=(const CLGMPFrameTransport&) = delete;
void SetFrameEvents(IFrameEvents *) override {}
size_t GetMaxFrameSize() const override { return m_maxFrameSize; } size_t GetMaxFrameSize() const override { return m_maxFrameSize; }
bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule, bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule,
@@ -189,8 +194,12 @@ public:
bool deliveredToOwner) override; bool deliveredToOwner) override;
void AbortFrameBuffer(unsigned frameIndex) override; void AbortFrameBuffer(unsigned frameIndex) override;
void FailFrameBuffer(unsigned frameIndex) override; void FailFrameBuffer(unsigned frameIndex) override;
void CompleteFrameBuffer( FrameFill FrameFilled(const FrameToken&) override
unsigned frameIndex, bool succeeded) override; {
return FrameFill::READY;
}
void CancelFrame(const FrameToken&) override {}
void CompleteFrameBuffer(unsigned frameIndex, FrameDone result) override;
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,
uint64_t holdTime, const CFrameScheduler::Schedule& schedule, uint64_t holdTime, const CFrameScheduler::Schedule& schedule,