/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. */ #include "transport/CFrameHub.h" #include "CDebug.h" static const uint64_t RETRY_NS = 1000000ULL; static void Earlier(uint64_t value, uint64_t& target) { if (value && (!target || value < target)) target = value; } static bool ContentAfter(uint64_t value, uint64_t current) { return value && (!current || static_cast(value - current) > 0); } CFrameHub::CFrameHub() { m_wakeEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); for (Sink& sink : m_sinks) sink.drained = CreateEvent(nullptr, TRUE, TRUE, nullptr); } CFrameHub::~CFrameHub() { for (Sink& sink : m_sinks) { const BackendId backend = sink.backend.load(std::memory_order_acquire); const uint32_t epoch = sink.epoch.load(std::memory_order_acquire); if (backend && epoch) Unbind(backend, epoch); } for (Sink& sink : m_sinks) { if (sink.drained) CloseHandle(sink.drained); } if (m_wakeEvent) CloseHandle(m_wakeEvent); } bool CFrameHub::Bind(BackendId backend, uint32_t epoch, bool primary, IFrameSink& target) { if (!backend || !epoch || !m_wakeEvent) return false; Sink * selected = nullptr; { CSRWExclusiveLock lock(m_listLock); if (primary) { if (!m_sinks[0].active.load(std::memory_order_acquire) && !m_sinks[0].reserved) selected = &m_sinks[0]; } else for (unsigned i = 1; i < FRAME_MAX_SINKS; ++i) if (!m_sinks[i].active.load(std::memory_order_acquire) && !m_sinks[i].reserved) { selected = &m_sinks[i]; break; } if (!selected || !selected->drained) return false; selected->reserved = true; } { CSRWExclusiveLock lock(selected->callLock); selected->target = ⌖ selected->backend.store(backend, std::memory_order_release); selected->epoch.store(epoch, std::memory_order_release); selected->primary = primary; selected->needsFullCopy = true; selected->lastContent = 0; selected->blockedContent = 0; selected->blockedFrameSize = 0; selected->blockedAllocation = false; selected->pitch = 0; selected->width = 0; selected->height = 0; selected->format = DXGI_FORMAT_UNKNOWN; selected->frameType = FRAME_TYPE_INVALID; for (Sink::ResourceLane& lane : selected->lanes) lane = {}; selected->outstanding.store(0, std::memory_order_release); SetEvent(selected->drained); target.SetFrameScheduleEvent(m_wakeEvent); } { CSRWExclusiveLock lock(m_listLock); selected->active.store(true, std::memory_order_release); selected->reserved = false; } target.ForceFrame(); SetEvent(m_wakeEvent); return true; } void CFrameHub::Unbind(BackendId backend, uint32_t epoch) { Sink * selected = nullptr; { CSRWExclusiveLock lock(m_listLock); for (Sink& sink : m_sinks) if (sink.active.load(std::memory_order_acquire) && sink.backend.load(std::memory_order_acquire) == backend && sink.epoch.load(std::memory_order_acquire) == epoch) { sink.active.store(false, std::memory_order_release); sink.reserved = true; selected = &sink; break; } } if (!selected) return; { CSRWExclusiveLock lock(selected->callLock); if (selected->target) selected->target->SetFrameScheduleEvent(nullptr); } WaitForSingleObject(selected->drained, INFINITE); { CSRWExclusiveLock lock(selected->callLock); selected->target = nullptr; selected->backend.store(0, std::memory_order_release); selected->epoch.store(0, std::memory_order_release); selected->primary = false; selected->needsFullCopy = true; selected->lastContent = 0; selected->blockedContent = 0; selected->blockedFrameSize = 0; selected->blockedAllocation = false; for (Sink::ResourceLane& lane : selected->lanes) lane = {}; } { CSRWExclusiveLock lock(m_listLock); selected->reserved = false; } SetEvent(m_wakeEvent); } unsigned CFrameHub::Snapshot(SinkRef refs[FRAME_MAX_SINKS]) const { unsigned count = 0; CSRWSharedLock lock(m_listLock); for (unsigned i = 0; i < FRAME_MAX_SINKS; ++i) if (m_sinks[i].active.load(std::memory_order_acquire)) refs[count++] = { const_cast(&m_sinks[i]), i, m_sinks[i].primary }; return count; } bool CFrameHub::BatchValid( const Batch& batch, const FrameBatchToken& token) const { return token.serial && batch.active && batch.serial == token.serial; } void CFrameHub::ReleaseTarget(Batch& batch, BatchTarget& target) { if (!target.active || target.releasePending) return; target.releasePending = true; if (target.resourceLane < FRAME_SINK_BUFFERS) target.sink->lanes[target.resourceLane].busy = false; if (target.sink->outstanding.fetch_sub( 1, std::memory_order_acq_rel) == 1) SetEvent(target.sink->drained); target.active = false; for (unsigned i = 0; i < batch.count; ++i) if (batch.targets[i].active) return; batch.active = false; } void CFrameHub::CompleteTarget( Batch& batch, BatchTarget& target, bool succeeded) { CSRWExclusiveLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) { target.sink->target->CompleteFrameBuffer(target.localSlot, succeeded); if (succeeded) { if (ContentAfter(target.content, target.sink->lastContent)) { target.sink->lastContent = target.content; target.sink->pitch = target.pitch; target.sink->width = target.width; target.sink->height = target.height; target.sink->format = target.format; target.sink->frameType = target.frameType; target.sink->needsFullCopy = false; target.sink->blockedContent = 0; target.sink->blockedFrameSize = 0; target.sink->blockedAllocation = false; } } else target.sink->needsFullCopy = true; } ReleaseTarget(batch, target); } size_t CFrameHub::GetMaxFrameSize() const { SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) if (refs[i].primary) { CSRWSharedLock call(refs[i].sink->callLock); if (refs[i].sink->active.load(std::memory_order_acquire) && refs[i].sink->target) return refs[i].sink->target->GetMaxFrameSize(); } return 0; } uint64_t CFrameHub::NextContentSerial() { uint64_t serial = m_nextContent.fetch_add( 1, std::memory_order_acq_rel) + 1; if (!serial) serial = m_nextContent.fetch_add( 1, std::memory_order_acq_rel) + 1; return serial; } void CFrameHub::FrameProductReady(uint64_t contentSerial) { uint64_t newest = m_newestContent.load(std::memory_order_acquire); while (ContentAfter(contentSerial, newest) && !m_newestContent.compare_exchange_weak(newest, contentSerial, std::memory_order_acq_rel, std::memory_order_acquire)) { } SetEvent(m_wakeEvent); } bool CFrameHub::NeedsFrame() const { const uint64_t newest = m_newestContent.load(std::memory_order_acquire); SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { CSRWSharedLock call(refs[i].sink->callLock); if (!refs[i].sink->active.load(std::memory_order_acquire) || !refs[i].sink->target) continue; if (refs[i].sink->blockedContent == newest && refs[i].sink->blockedFrameSize && (refs[i].sink->blockedAllocation || refs[i].sink->target->GetMaxFrameSize() < refs[i].sink->blockedFrameSize)) continue; if (refs[i].sink->needsFullCopy || ContentAfter(newest, refs[i].sink->lastContent)) return true; } return false; } bool CFrameHub::GetFramePlan( uint64_t now, bool, FramePlan& plan) { plan = {}; SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { Sink& sink = *refs[i].sink; CSRWSharedLock call(sink.callLock); if (!sink.active.load(std::memory_order_acquire) || !sink.target) continue; sink.target->ProcessDeliveries(); uint64_t deliveryTarget = 0; if (sink.target->GetPendingDeliveryTarget(now, deliveryTarget)) { if (deliveryTarget <= now) { bool retry = false; if (sink.target->RetryPendingDelivery(now, retry)) plan.progressed = true; else if (retry) Earlier(now + RETRY_NS, plan.nextWake); } else Earlier(deliveryTarget, plan.nextWake); } uint64_t target = 0; CFrameScheduler::Schedule schedule = {}; bool periodic = false; bool republish = false; if (!sink.target->GetPublishTarget( now, target, schedule, periodic, republish)) continue; if (target > now) { Earlier(target, plan.nextWake); continue; } if (republish && sink.target->HasPublishedFrame()) { if (sink.target->RepublishFrameBuffer(schedule)) { plan.progressed = true; continue; } else { Earlier(now + RETRY_NS, plan.nextWake); continue; } } if (plan.count == FRAME_MAX_SINKS) continue; FramePlanTarget& request = plan.targets[plan.count++]; request.sink = refs[i].index; request.backend = sink.backend.load(std::memory_order_acquire); request.epoch = sink.epoch.load(std::memory_order_acquire); request.schedule = schedule; request.commitSchedule = schedule; request.periodic = periodic; request.primary = sink.primary; } return plan.count != 0; } bool CFrameHub::GetImmediateFramePlan(uint64_t now, FramePlan& plan) { plan = {}; SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { Sink& sink = *refs[i].sink; CSRWSharedLock call(sink.callLock); if (!sink.active.load(std::memory_order_acquire) || !sink.target) continue; sink.target->ProcessDeliveries(); uint64_t deliveryTarget = 0; if (sink.target->GetPendingDeliveryTarget(now, deliveryTarget) && deliveryTarget <= now) { bool retry = false; if (sink.target->RetryPendingDelivery(now, retry)) plan.progressed = true; } uint64_t target = 0; CFrameScheduler::Schedule schedule = {}; bool periodic = false; bool republish = false; sink.target->GetPublishTarget( now, target, schedule, periodic, republish); if (plan.count == FRAME_MAX_SINKS) continue; FramePlanTarget& request = plan.targets[plan.count++]; request.sink = refs[i].index; request.backend = sink.backend.load(std::memory_order_acquire); request.epoch = sink.epoch.load(std::memory_order_acquire); request.schedule = schedule; request.schedule.deliveryDeadlineSerial = 0; request.schedule.phaseEligible = false; request.commitSchedule = schedule; request.periodic = false; request.primary = sink.primary; } return plan.count != 0; } void CFrameHub::MissFramePlan(const FramePlan& plan, uint64_t now) { for (unsigned i = 0; i < plan.count; ++i) { const FramePlanTarget& request = plan.targets[i]; if (!request.periodic || !request.schedule.deliveryDeadlineSerial || request.schedule.deadline > now || request.sink >= FRAME_MAX_SINKS) continue; Sink& sink = m_sinks[request.sink]; CSRWSharedLock call(sink.callLock); if (sink.active.load(std::memory_order_acquire) && sink.target && sink.backend.load(std::memory_order_acquire) == request.backend && sink.epoch.load(std::memory_order_acquire) == request.epoch) sink.target->FrameMissed( request.commitSchedule, now, request.periodic); } } bool CFrameHub::PrepareFrameBatch(const FramePlan& plan, uint64_t contentSerial, unsigned pitch, size_t frameSize, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects, bool allowReadyReplacement, PreparedFrameBatch& prepared) { prepared = {}; if (!contentSerial || !pitch || !frameSize) return false; Batch * batch = nullptr; unsigned batchSlot = 0; for (; batchSlot < FRAME_BATCHES; ++batchSlot) { Batch& candidate = m_batches[batchSlot]; CSRWExclusiveLock lock = CSRWExclusiveLock::Try(candidate.lock); if (!lock || candidate.active) continue; candidate.active = true; candidate.count = 0; candidate.serial = m_nextSerial.fetch_add( 1, std::memory_order_acq_rel) + 1; if (!candidate.serial) candidate.serial = m_nextSerial.fetch_add( 1, std::memory_order_acq_rel) + 1; for (BatchTarget& target : candidate.targets) target = {}; prepared.token = { static_cast(batchSlot), candidate.serial }; batch = &candidate; lock.Unlock(); break; } if (!batch) return false; CSRWExclusiveLock batchLock(batch->lock); for (unsigned i = 0; i < plan.count && batch->count < FRAME_MAX_SINKS; ++i) { const FramePlanTarget& request = plan.targets[i]; if (request.sink >= FRAME_MAX_SINKS) continue; Sink& sink = m_sinks[request.sink]; CSRWExclusiveLock call(sink.callLock); if (!sink.active.load(std::memory_order_acquire) || !sink.target || sink.backend.load(std::memory_order_acquire) != request.backend || sink.epoch.load(std::memory_order_acquire) != request.epoch || !sink.target->FrameBufferAvailable( request.schedule, allowReadyReplacement)) continue; const size_t maxFrameSize = sink.target->GetMaxFrameSize(); if (!maxFrameSize || frameSize > maxFrameSize) { sink.blockedContent = contentSerial; sink.blockedFrameSize = frameSize; sink.blockedAllocation = false; continue; } const bool layoutChanged = sink.pitch != pitch || sink.width != dstFormat.width || sink.height != dstFormat.height || sink.format != dstFormat.desc.Format || sink.frameType != dstFormat.format; const bool forceFull = sink.needsFullCopy || layoutChanged || sink.lastContent + 1 != contentSerial; const RECT * damage = forceFull ? nullptr : dirtyRects; const unsigned damageCount = forceFull ? 0 : nbDirtyRects; SinkTarget result = sink.target->PrepareFrameBuffer( pitch, srcFormat, dstFormat, damage, damageCount, request.schedule, allowReadyReplacement); if (!result.mem || result.capacity < frameSize) { if (result.mem) { sink.blockedContent = contentSerial; sink.blockedFrameSize = frameSize; sink.blockedAllocation = true; sink.target->AbortFrameBuffer(result.slot); } continue; } unsigned resourceLane = FRAME_SINK_BUFFERS; for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane) { const Sink::ResourceLane& candidate = sink.lanes[lane]; if (!candidate.busy && candidate.valid && candidate.mem == result.mem && candidate.heapOffset == result.heapOffset && candidate.localSlot == result.slot && candidate.direct == request.primary) { resourceLane = lane; break; } } if (resourceLane == FRAME_SINK_BUFFERS) for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane) if (!sink.lanes[lane].busy && !sink.lanes[lane].valid) { resourceLane = lane; break; } if (resourceLane == FRAME_SINK_BUFFERS) for (unsigned lane = 0; lane < FRAME_SINK_BUFFERS; ++lane) if (!sink.lanes[lane].busy) { resourceLane = lane; break; } if (resourceLane == FRAME_SINK_BUFFERS) { sink.target->AbortFrameBuffer(result.slot); continue; } Sink::ResourceLane& lane = sink.lanes[resourceLane]; lane.mem = result.mem; lane.heapOffset = result.heapOffset; lane.localSlot = result.slot; lane.direct = request.primary; lane.valid = true; lane.busy = true; unsigned index = batch->count; if (sink.primary && index) { for (unsigned move = index; move != 0; --move) { batch->targets[move] = batch->targets[move - 1]; prepared.targets[move] = prepared.targets[move - 1]; } index = 0; } ++batch->count; BatchTarget& target = batch->targets[index]; target.sink = &sink; target.backend = request.backend; target.epoch = request.epoch; target.localSlot = result.slot; target.resourceLane = resourceLane; target.schedule = request.commitSchedule; target.deliverySchedule = request.schedule; target.content = contentSerial; target.pitch = pitch; target.width = dstFormat.width; target.height = dstFormat.height; target.format = dstFormat.desc.Format; target.frameType = dstFormat.format; target.periodic = request.periodic; target.active = true; if (sink.outstanding.fetch_add( 1, std::memory_order_acq_rel) == 0) ResetEvent(sink.drained); PreparedFrameBuffer& output = prepared.targets[index]; output.token.sink = request.backend; output.token.epoch = request.epoch; output.token.slot = result.slot; output.token.serial = batch->serial; output.resourceSlot = request.sink * FRAME_SINK_BUFFERS + resourceLane; output.mem = result.mem; output.heapOffset = result.heapOffset; output.capacity = result.capacity; output.direct = request.primary; output.fullCopy = forceFull || result.fullCopy; } prepared.count = batch->count; if (!batch->count) { batch->active = false; prepared = {}; return false; } return true; } uint32_t CFrameHub::PublishFrameBatch(const FrameBatchToken& token) { if (token.slot >= FRAME_BATCHES) return 0; Batch& batch = m_batches[token.slot]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token)) return 0; uint32_t accepted = 0; for (unsigned i = 0; i < batch.count; ++i) { BatchTarget& target = batch.targets[i]; if (!target.active) continue; CSRWExclusiveLock call(target.sink->callLock); bool delivered = false; const bool valid = target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch; if (!valid || !target.sink->target->PublishFrameBuffer( target.localSlot, target.deliverySchedule, delivered)) { if (valid) target.sink->target->AbortFrameBuffer(target.localSlot); target.sink->needsFullCopy = true; ReleaseTarget(batch, target); continue; } target.published = true; target.delivered = delivered; target.submitted = delivered && target.sink->target->TryFrameSubmitted( target.localSlot, target.deliverySchedule); if (!target.submitted) target.schedule.phaseEligible = false; accepted |= 1U << i; } return accepted; } void CFrameHub::CommitFrameBatch(const FrameBatchToken& token) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token)) return; for (unsigned i = 0; i < batch.count; ++i) { BatchTarget& target = batch.targets[i]; if (!target.active || !target.published) continue; { CSRWExclusiveLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->CommitFrameBuffer(target.localSlot, target.schedule, target.periodic, target.delivered); } target.committed = true; if (target.completionPending) CompleteTarget(batch, target, target.completionSucceeded); } } void CFrameHub::AbortFrameBatch(const FrameBatchToken& token) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token)) return; for (unsigned i = 0; i < batch.count; ++i) { BatchTarget& target = batch.targets[i]; if (!target.active) continue; CSRWExclusiveLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->AbortFrameBuffer(target.localSlot); target.sink->needsFullCopy = true; ReleaseTarget(batch, target); } } void CFrameHub::FailFrameBatch(const FrameBatchToken& token) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token)) return; for (unsigned i = 0; i < batch.count; ++i) { BatchTarget& target = batch.targets[i]; if (!target.active) continue; CSRWExclusiveLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) { if (target.published) target.sink->target->FailFrameBuffer(target.localSlot); else target.sink->target->AbortFrameBuffer(target.localSlot); } target.sink->needsFullCopy = true; ReleaseTarget(batch, target); } } void CFrameHub::WriteFrameTarget(const FrameBatchToken& token, unsigned index, void * src, size_t offset, size_t len, bool setWritePos) const { if (token.slot >= FRAME_BATCHES) return; Batch& batch = const_cast(m_batches[token.slot]); CSRWSharedLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; CSRWSharedLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->WriteFrameBuffer( target.localSlot, src, offset, len, setWritePos); } void CFrameHub::WriteFrameTargetRows(const FrameBatchToken& token, unsigned index, void * src, size_t offset, size_t rowBytes, size_t pitch, unsigned rows) const { if (token.slot >= FRAME_BATCHES) return; Batch& batch = const_cast(m_batches[token.slot]); CSRWSharedLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; CSRWSharedLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->WriteFrameBufferRows(target.localSlot, src, offset, rowBytes, pitch, rows); } void CFrameHub::FinalizeFrameTarget( const FrameBatchToken& token, unsigned index) const { if (token.slot >= FRAME_BATCHES) return; Batch& batch = const_cast(m_batches[token.slot]); CSRWSharedLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; CSRWSharedLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->FinalizeFrameBuffer(target.localSlot); } void CFrameHub::SetFrameTargetTiming(const FrameBatchToken& token, unsigned index, uint64_t captureTime, uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime, uint64_t holdTime, uint64_t completedAt) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWSharedLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; CSRWSharedLock call(target.sink->callLock); if (target.sink->target && target.sink->backend.load(std::memory_order_acquire) == target.backend && target.sink->epoch.load(std::memory_order_acquire) == target.epoch) target.sink->target->SetFrameTiming(target.localSlot, captureTime, postProcessTime, copyTime, readyTime, holdTime, target.schedule, completedAt); } void CFrameHub::TryRecordFrameTiming(const FrameBatchToken& token, unsigned index, uint64_t duration) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWSharedLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; CSRWSharedLock call(target.sink->callLock); if (target.sink->target && 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, unsigned index, bool succeeded) { if (token.slot >= FRAME_BATCHES) return; Batch& batch = m_batches[token.slot]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; if (!target.committed) { target.completionPending = true; target.completionSucceeded = succeeded; return; } CompleteTarget(batch, target, succeeded); } void CFrameHub::ObserveFrame(uint64_t now) { SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { CSRWSharedLock call(refs[i].sink->callLock); if (refs[i].sink->active.load(std::memory_order_acquire) && refs[i].sink->target) refs[i].sink->target->ObserveFrame(now); } } void CFrameHub::ForceFrame() { SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { CSRWSharedLock call(refs[i].sink->callLock); if (refs[i].sink->active.load(std::memory_order_acquire) && refs[i].sink->target) refs[i].sink->target->ForceFrame(); } } void CFrameHub::FrameSuperseded() { SinkRef refs[FRAME_MAX_SINKS]; const unsigned count = Snapshot(refs); for (unsigned i = 0; i < count; ++i) { CSRWSharedLock call(refs[i].sink->callLock); if (refs[i].sink->active.load(std::memory_order_acquire) && refs[i].sink->target) refs[i].sink->target->FrameSuperseded(); } }