/** * 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); } 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() { 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->outstanding.store(0, std::memory_order_release); SetEvent(selected->drained); } { CSRWExclusiveLock lock(selected->laneLock); selected->needsFullCopy = true; selected->lastContent = 0; selected->lastTerminalContent = 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 = {}; } target.SetFrameEvents(this); 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; IFrameSink * target; { CSRWExclusiveLock lock(selected->callLock); target = selected->target; } 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); if (target) target->SetFrameEvents(nullptr); { 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; } { CSRWExclusiveLock lock(selected->laneLock); selected->needsFullCopy = true; selected->lastContent = 0; selected->lastTerminalContent = 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) { CSRWExclusiveLock lock(target.sink->laneLock); 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; } bool CFrameHub::DetachTarget(Batch& batch, BatchTarget& target, FrameToken& token) { if (!target.active || target.releasePending || target.resourceLane >= FRAME_SINK_BUFFERS) return false; token.backend = target.backend; token.epoch = target.epoch; token.slot = target.localSlot; token.serial = batch.serial; { CSRWExclusiveLock lock(target.sink->laneLock); Sink::ResourceLane& lane = target.sink->lanes[target.resourceLane]; if (!lane.busy || lane.phase != Sink::ResourceLane::IDLE) return false; lane.token = token; lane.schedule = target.schedule; lane.content = target.content; lane.captureTime = target.captureTime; lane.postProcessTime = target.postProcessTime; lane.copyTime = target.copyTime; lane.readyTime = target.readyTime; lane.holdTime = target.holdTime; lane.filledAt = target.filledAt; 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 { 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; } } 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; const size_t maxFrameSize = refs[i].sink->target->GetMaxFrameSize(); CSRWSharedLock state(refs[i].sink->laneLock); if (refs[i].sink->blockedContent == newest && refs[i].sink->blockedFrameSize && (refs[i].sink->blockedAllocation || maxFrameSize < 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) { CSRWExclusiveLock lock(sink.laneLock); sink.blockedContent = contentSerial; sink.blockedFrameSize = frameSize; sink.blockedAllocation = false; continue; } bool forceFull; { CSRWSharedLock lock(sink.laneLock); const bool layoutChanged = sink.pitch != pitch || sink.width != dstFormat.width || sink.height != dstFormat.height || sink.format != dstFormat.desc.Format || sink.frameType != dstFormat.format; 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) { { CSRWExclusiveLock lock(sink.laneLock); sink.blockedContent = contentSerial; sink.blockedFrameSize = frameSize; sink.blockedAllocation = true; } sink.target->AbortFrameBuffer(result.slot); } continue; } { CSRWExclusiveLock lock(sink.laneLock); 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) { lock.Unlock(); 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; lane.phase = Sink::ResourceLane::IDLE; 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.backend = 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); { CSRWExclusiveLock state(target.sink->laneLock); 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]; struct FillRequest { Sink * sink; unsigned lane; FrameToken token; bool succeeded; } fill[FRAME_MAX_SINKS] = {}; unsigned fillCount = 0; 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) { 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) { 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); { CSRWExclusiveLock state(target.sink->laneLock); 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); } { CSRWExclusiveLock state(target.sink->laneLock); 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]; CSRWExclusiveLock lock(batch.lock); if (!BatchValid(batch, token) || index >= batch.count || !batch.targets[index].active) return; BatchTarget& target = batch.targets[index]; target.captureTime = captureTime; target.postProcessTime = postProcessTime; target.copyTime = copyTime; target.readyTime = readyTime; target.holdTime = holdTime; target.filledAt = completedAt; target.timingValid = true; } void CFrameHub::TryRecordFrameTiming(const FrameBatchToken& token, unsigned index, uint64_t duration) { 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.filledAt >= duration) target.workStart = target.filledAt - 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; } 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) { 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(); } }