diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 7a2e73af..1ec8e7c7 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -68,6 +68,7 @@ + @@ -124,10 +125,12 @@ + + @@ -135,6 +138,8 @@ + + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index c5ef57c4..d3c4edde 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -175,6 +175,9 @@ Transport + + Transport + Transport @@ -187,6 +190,9 @@ Transport + + Transport + Transport @@ -208,6 +214,12 @@ Transport + + Transport + + + Transport + Transport @@ -357,6 +369,9 @@ Transport + + Transport + Transport diff --git a/idd/LGIdd/capture/CFrameGraph.cpp b/idd/LGIdd/capture/CFrameGraph.cpp index a6ab2922..f5f7ad1d 100644 --- a/idd/LGIdd/capture/CFrameGraph.cpp +++ b/idd/LGIdd/capture/CFrameGraph.cpp @@ -66,11 +66,12 @@ bool Frame::Valid(FrameDamage damage, const RECT * rects, unsigned count, void CFrameGraph::Reset() { - m_cfg = GraphCfg {}; - m_nodeCount = 0; - m_leafCount = 0; - m_begun = false; - m_sealed = false; + m_cfg = GraphCfg {}; + m_generation = 0; + m_nodeCount = 0; + m_leafCount = 0; + m_begun = false; + m_sealed = false; } bool CFrameGraph::Begin(const GraphCfg& cfg) @@ -288,6 +289,14 @@ bool CFrameGraph::Seal() return true; } +bool CFrameGraph::Stamp(uint64_t generation) +{ + if (!m_sealed || m_generation || !generation) + return false; + m_generation = generation; + return true; +} + bool CFrameGraph::Same(const GraphCfg& cfg) const { return m_sealed && Frame::Same(m_cfg, cfg); diff --git a/idd/LGIdd/capture/CFrameGraph.h b/idd/LGIdd/capture/CFrameGraph.h index 21fedd18..b513d3de 100644 --- a/idd/LGIdd/capture/CFrameGraph.h +++ b/idd/LGIdd/capture/CFrameGraph.h @@ -131,6 +131,7 @@ private: GraphCfg m_cfg; GraphNode m_nodes[FRAME_GRAPH_MAX_NODES] = {}; GraphLeaf m_leaves[TRANSPORT_MAX_INSTANCES] = {}; + uint64_t m_generation = 0; unsigned m_nodeCount = 0; unsigned m_leafCount = 0; bool m_begun = false; @@ -150,6 +151,7 @@ public: bool Add(BackendId id, uint32_t epoch, bool required, bool primary, const FrameCfg& cfg); bool Seal(); + bool Stamp(uint64_t generation); bool Same(const GraphCfg& cfg) const; bool Want(FrameSignal signal) const; bool Need(FrameOp op) const; @@ -158,6 +160,7 @@ public: LeafDesc& desc) const; const GraphCfg& Cfg() const { return m_cfg; } + uint64_t Generation() const { return m_generation; } const GraphNode * Nodes(unsigned& count) const; const GraphLeaf * Leaves(unsigned& count) const; }; diff --git a/idd/LGIdd/capture/CFrameTex.cpp b/idd/LGIdd/capture/CFrameTex.cpp index 62909b52..c01472c2 100644 --- a/idd/LGIdd/capture/CFrameTex.cpp +++ b/idd/LGIdd/capture/CFrameTex.cpp @@ -48,6 +48,22 @@ static uint64_t NextPool() return Atomic::Next(next); } +static bool ValidTime(const FrameTime& time) +{ + if (!time.postStart || + (!time.gpuValid && (time.gpuStart || time.gpuEnd)) || + (!time.readyValid && time.readyAt)) + return false; + if (time.gpuValid && (!time.gpuStart || + time.gpuStart < time.postStart || + time.gpuEnd < time.gpuStart)) + return false; + if (time.readyValid && (!time.readyAt || + time.readyAt < (time.gpuValid ? time.gpuEnd : time.postStart))) + return false; + return true; +} + static bool ValidFrame(const FrameProfile& profile, const D3D12_RESOURCE_DESC& resource, const FrameDesc& frame) { @@ -279,7 +295,8 @@ struct CTexCore CFrameTex::CFrameTex(uint64_t graphValue, uint64_t poolValue, unsigned nodeValue, unsigned slotValue, uint64_t versionValue, const FrameProfile& profileValue, - const FrameDesc& frameValue, const ComPtr& res, + const FrameDesc& frameValue, const FrameTime& timeValue, + const ComPtr& res, const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue) : m_res(res), m_sync(sync), @@ -291,7 +308,8 @@ CFrameTex::CFrameTex(uint64_t graphValue, uint64_t poolValue, slot(slotValue), version(versionValue), profile(profileValue), - frame(frameValue) + frame(frameValue), + time(timeValue) { } @@ -359,13 +377,19 @@ ID3D12Resource * TexWrite::Get() const resource.Get() : nullptr; } -bool TexWrite::Seal(const FrameDesc& frame, const D12Sync& sync, - TexLease& lease) +bool TexWrite::Seal(const FrameDesc& frame, const FrameTime& time, + const D12Sync& sync, TexLease& lease) { if (!m_core || !sync.Valid()) return false; const std::shared_ptr core = m_core; + if (!ValidTime(time)) + { + core->Fail(m_index, m_version, sync); + Clear(); + return false; + } ComPtr resource; FrameDesc desc = frame; if (!core->Info(m_index, m_version, resource) || @@ -390,8 +414,8 @@ bool TexWrite::Seal(const FrameDesc& frame, const D12Sync& sync, } CFrameTex * raw = new (std::nothrow) CFrameTex(core->graph, core->id, - core->node, m_index, m_version, core->profile, desc, resource, sync, - core->read, core->shared); + core->node, m_index, m_version, core->profile, desc, time, resource, + sync, core->read, core->shared); if (!raw) { core->Drop(index, generation); diff --git a/idd/LGIdd/capture/CFrameTex.h b/idd/LGIdd/capture/CFrameTex.h index 73bc8793..465991b7 100644 --- a/idd/LGIdd/capture/CFrameTex.h +++ b/idd/LGIdd/capture/CFrameTex.h @@ -37,6 +37,18 @@ enum class TexResult : uint8_t FAILED, }; +// Immutable producer boundaries for one node product. A transport adds its +// own transfer or encode timing without rewriting these common values. +struct FrameTime +{ + uint64_t postStart = 0; + uint64_t gpuStart = 0; + uint64_t gpuEnd = 0; + uint64_t readyAt = 0; + bool gpuValid = false; + bool readyValid = false; +}; + // An immutable, pool-owned D3D12 graph product. Node zero is deliberately // excluded because it aliases the acquired IddCx surface. class CFrameTex final @@ -51,7 +63,8 @@ private: CFrameTex(uint64_t graph, uint64_t pool, unsigned node, unsigned slot, uint64_t version, const FrameProfile& profile, const FrameDesc& frame, - const ComPtr& res, const D12Sync& sync, + const FrameTime& time, const ComPtr& res, + const D12Sync& sync, D3D12_RESOURCE_STATES state, bool shared); public: @@ -65,6 +78,7 @@ public: const uint64_t version; const FrameProfile profile; const FrameDesc frame; + const FrameTime time; ID3D12Resource * Get() const { return m_res.Get(); } const D12Sync& Sync() const { return m_sync; } @@ -126,7 +140,8 @@ public: ID3D12Resource * Get() const; // Seal only after producer commands restore the texture to the pool's // configured immutable state and Execute returns this exact sync point. - bool Seal(const FrameDesc& frame, const D12Sync& sync, TexLease& lease); + bool Seal(const FrameDesc& frame, const FrameTime& time, + const D12Sync& sync, TexLease& lease); void Cancel(); }; diff --git a/idd/LGIdd/capture/CSwapChainProcessor.cpp b/idd/LGIdd/capture/CSwapChainProcessor.cpp index 673d0f5a..fdfaea1f 100644 --- a/idd/LGIdd/capture/CSwapChainProcessor.cpp +++ b/idd/LGIdd/capture/CSwapChainProcessor.cpp @@ -533,17 +533,29 @@ void CSwapChainProcessor::QueueGraph(const D12FrameFormat& source, source, checkpoint, m_dx11Device->IsSoftware(), m_renderAdapter, cfg)) return; - if (m_haveGraphCfg && Frame::Same(m_graphCfg, cfg)) + CTransportManager& transport = m_devContext->GetTransport(); + const uint64_t revision = transport.FrameRev(); + if (m_haveGraphCfg && m_graphRev == revision && + Frame::Same(m_graphCfg, cfg)) return; m_graphCfg = cfg; m_haveGraphCfg = true; m_graphPending = true; + m_graphRev = revision; m_graphRetryAt = 0; } void CSwapChainProcessor::CfgGraph() { + CTransportManager& transport = m_devContext->GetTransport(); + const uint64_t revision = transport.FrameRev(); + if (m_haveGraphCfg && revision != m_graphRev) + { + m_graphRev = revision; + m_graphPending = true; + m_graphRetryAt = 0; + } if (!m_graphPending) return; @@ -553,7 +565,7 @@ void CSwapChainProcessor::CfgGraph() m_graphPending = false; const CfgResult result = - m_devContext->GetTransport().Cfg(m_graphCfg, m_graph); + transport.Cfg(m_graphCfg, m_graphRev, m_graph); if (result == CfgResult::ACCEPTED) { m_graphRetryAt = 0; diff --git a/idd/LGIdd/capture/CSwapChainProcessor.h b/idd/LGIdd/capture/CSwapChainProcessor.h index 153a65e1..81c2c615 100644 --- a/idd/LGIdd/capture/CSwapChainProcessor.h +++ b/idd/LGIdd/capture/CSwapChainProcessor.h @@ -61,6 +61,7 @@ private: GraphCfg m_graphCfg; bool m_haveGraphCfg = false; bool m_graphPending = false; + uint64_t m_graphRev = 0; uint64_t m_graphRetryAt = 0; CPostProcessor m_postProcessors[CAPTURE_PIPELINE_SLOTS]; std::unique_ptr m_frameProcessor; diff --git a/idd/LGIdd/transport/CTexHub.cpp b/idd/LGIdd/transport/CTexHub.cpp new file mode 100644 index 00000000..35e79c05 --- /dev/null +++ b/idd/LGIdd/transport/CTexHub.cpp @@ -0,0 +1,644 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "transport/CTexHub.h" + +#include "capture/CFrameGraph.h" +#include "capture/CFrameTex.h" +#include "d3d/CInteropPool.h" +#include "transport/ITexSink.h" +#include "transport/ITransport.h" + +#include +#include +#include +#include + +namespace +{ + template + T& Append(T (&items)[N], unsigned& count) + { + if (count == N) + { + for (unsigned i = 1; i < count; ++i) + items[i - 1] = items[i]; + --count; + } + return items[count++]; + } +} + +struct CTexSet +{ + struct Route + { + std::shared_ptr owner; + ITexSink * sink = nullptr; + BackendId id = 0; + uint32_t epoch = 0; + unsigned node = 0; + unsigned leaf = 0; + FrameProfile profile; + FrameCfg cfg; + bool required = false; + bool primary = false; + bool active = false; + bool dropped = false; + CfgResult fault = CfgResult::ACCEPTED; + unsigned calls = 0; + HANDLE idle = nullptr; + + ~Route() + { + if (idle) + CloseHandle(idle); + } + }; + + CSRWLock lock; + CFrameGraph graph; + uint64_t generation = 0; + unsigned count = 0; + Route routes[TRANSPORT_MAX_INSTANCES]; +}; + +static bool SameFrame(const FrameDesc& left, const FrameDesc& right, + bool ignoreFlags = false) +{ + if (left.content != right.content || + left.damage != right.damage || + left.count != right.count || + left.count > FRAME_DAMAGE_MAX || + !D12::Same(left.format, right.format, ignoreFlags ? + D12::FormatCmp::NO_FLAGS : D12::FormatCmp::EXACT)) + return false; + return !left.count || memcmp(left.rects, right.rects, + left.count * sizeof(*left.rects)) == 0; +} + +static unsigned Find(const CTexSet& set, const FrameIn& frame) +{ + for (unsigned i = 0; i < set.count; ++i) + { + const CTexSet::Route& route = set.routes[i]; + if (route.id == frame.desc.id && + route.epoch == frame.desc.epoch && + route.node == frame.desc.node && + Frame::Same(route.profile, frame.desc.profile)) + return i; + } + return TRANSPORT_MAX_INSTANCES; +} + +static bool Valid(const CTexSet& set, const CTexSet::Route& route, + const FrameIn& frame, const CFrameTex& tex, FrameStorage storage) +{ + const FrameProfile product = + Frame::Store(route.profile, FrameStorage::D3D12_TEXTURE); + LeafDesc expected; + if (!set.graph.Desc(route.leaf, frame.desc.frame.content, expected) || + expected.id != frame.desc.id || + expected.epoch != frame.desc.epoch || + expected.node != frame.desc.node || + !Frame::Same(expected.profile, frame.desc.profile) || + !SameFrame(expected.frame, frame.desc.frame) || + !SameFrame(expected.frame, tex.frame, true)) + return false; + return + frame.graph != 0 && + frame.desc.node != 0 && + frame.desc.frame.content && + route.profile.storage == storage && + tex.graph == frame.graph && + tex.node == route.node && + Frame::Same(tex.profile, product); +} + +CTexStage::CTexStage(CTexStage&& other) noexcept : + m_owner(other.m_owner), m_next(std::move(other.m_next)) +{ + other.m_owner = nullptr; +} + +CTexStage& CTexStage::operator=(CTexStage&& other) noexcept +{ + if (this == &other) + return *this; + if (m_owner) + m_owner->Abort(*this); + m_owner = other.m_owner; + m_next = std::move(other.m_next); + other.m_owner = nullptr; + return *this; +} + +CTexStage::~CTexStage() +{ + if (m_owner) + m_owner->Abort(*this); +} + +CTexHub::CTexHub(std::atomic& rev) : m_rev(rev) +{ + m_idle = CreateEvent(nullptr, TRUE, TRUE, nullptr); +} + +CTexHub::~CTexHub() +{ + Stop(); + if (m_idle) + CloseHandle(m_idle); +} + +bool CTexHub::Enter(const FrameIn& frame, bool lease, + std::shared_ptr& set, unsigned& route, ITexSink *& sink) +{ + route = TRANSPORT_MAX_INSTANCES; + sink = nullptr; + { + CSRWExclusiveLock lock(m_lock); + if (!m_open || m_stopped || !m_active) + return false; + set = m_active; + if (!m_calls++) + ResetEvent(m_idle); + } + + CSRWExclusiveLock lock(set->lock); + route = Find(*set, frame); + if (route >= set->count || !set->routes[route].active || !lease || + frame.graph != set->generation) + return false; + + CTexSet::Route& target = set->routes[route]; + if (!target.calls++) + ResetEvent(target.idle); + sink = target.sink; + return true; +} + +void CTexHub::Finish(const std::shared_ptr& set, + unsigned route, PushResult result) +{ + if (result == PushResult::REJECTED || result == PushResult::FAILED) + Disable(set, route, result); + Leave(set, route); +} + +void CTexHub::Bump() +{ + Atomic::Next(m_rev, std::memory_order_release); +} + +bool CTexHub::Match(const FaultRec& fault, BackendId id, uint32_t epoch, + const FrameCfg& cfg, bool anyCfg) +{ + return + fault.id == id && + fault.epoch == epoch && + (anyCfg || fault.result == CfgResult::FAILED || + Frame::Same(fault.cfg, cfg)); +} + +CfgResult CTexHub::Faulted(BackendId id, uint32_t epoch, + const FrameCfg& cfg) const +{ + CSRWSharedLock lock(m_lock); + for (unsigned i = 0; i < m_faultCount; ++i) + if (Match(m_faults[i], id, epoch, cfg)) + return m_faults[i].result; + return CfgResult::ACCEPTED; +} + +void CTexHub::Rebind(BackendId id, uint32_t epoch) +{ + if (!id || !epoch) + return; + CSRWExclusiveLock lock(m_lock); + for (unsigned i = 0; i < m_faultCount;) + { + if (m_faults[i].id != id || m_faults[i].epoch != epoch) + { + ++i; + continue; + } + m_faults[i] = m_faults[--m_faultCount]; + m_faults[m_faultCount] = FaultRec {}; + } +} + +void CTexHub::Leave( + const std::shared_ptr& set, unsigned route) +{ + if (set && route < set->count) + { + CSRWExclusiveLock setLock(set->lock); + CTexSet::Route& target = set->routes[route]; + if (target.calls && !--target.calls && target.idle) + SetEvent(target.idle); + } + CSRWExclusiveLock lock(m_lock); + if (m_calls && !--m_calls) + SetEvent(m_idle); +} + +void CTexHub::Disable(const std::shared_ptr& set, + unsigned route, PushResult result) +{ + bool found = false; + CTexSet::Route failed; + { + CSRWExclusiveLock lock(set->lock); + if (route < set->count && !set->routes[route].dropped && + (set->routes[route].active || + (result == PushResult::FAILED && + set->routes[route].fault == CfgResult::REJECTED))) + { + CTexSet::Route& target = set->routes[route]; + target.active = false; + target.fault = result == PushResult::FAILED ? + CfgResult::FAILED : CfgResult::REJECTED; + failed.id = target.id; + failed.epoch = target.epoch; + failed.cfg = target.cfg; + found = true; + } + } + if (!found) + return; + + FaultRec record; + record.id = failed.id; + record.epoch = failed.epoch; + record.cfg = failed.cfg; + record.result = result == PushResult::FAILED ? + CfgResult::FAILED : CfgResult::REJECTED; + + bool wake = false; + { + CSRWExclusiveLock lock(m_lock); + for (unsigned i = 0; i < m_faultCount; ++i) + if (Match(m_faults[i], failed.id, failed.epoch, failed.cfg, + result == PushResult::FAILED)) + { + if (result == PushResult::FAILED && + m_faults[i].result != CfgResult::FAILED) + { + m_faults[i].result = CfgResult::FAILED; + wake = true; + } + if (result != PushResult::FAILED) + { + lock.Unlock(); + if (wake) + Bump(); + return; + } + break; + } + bool faulted = false; + for (unsigned i = 0; i < m_faultCount; ++i) + if (Match(m_faults[i], failed.id, failed.epoch, failed.cfg)) + { + faulted = true; + break; + } + if (!faulted) + { + Append(m_faults, m_faultCount) = record; + wake = true; + } + if (result == PushResult::FAILED) + { + bool queued = false; + for (unsigned i = 0; i < m_failureCount; ++i) + if (m_failures[i].id == failed.id && + m_failures[i].epoch == failed.epoch) + { + queued = true; + break; + } + if (!queued) + { + Failure& source = Append(m_failures, m_failureCount); + source.id = failed.id; + source.epoch = failed.epoch; + } + wake = true; + } + } + if (wake) + Bump(); +} + +bool CTexHub::TakeFailure(BackendId& id, uint32_t& epoch) +{ + CSRWExclusiveLock lock(m_lock); + if (!m_failureCount) + return false; + id = m_failures[0].id; + epoch = m_failures[0].epoch; + for (unsigned i = 1; i < m_failureCount; ++i) + m_failures[i - 1] = m_failures[i]; + m_failures[--m_failureCount] = Failure {}; + return true; +} + +CfgResult CTexHub::Hold(CTexStage& stage) +{ + if (stage.m_owner) + return CfgResult::REJECTED; + if (!m_idle) + return CfgResult::FAILED; + { + CSRWExclusiveLock lock(m_lock); + if (m_stopped || m_changing || m_pending) + return CfgResult::RETRY; + m_changing = true; + m_open = false; + stage.m_owner = this; + if (m_calls) + ResetEvent(m_idle); + else + SetEvent(m_idle); + } + return WaitForSingleObject(m_idle, INFINITE) == WAIT_OBJECT_0 ? + CfgResult::ACCEPTED : CfgResult::FAILED; +} + +CfgResult CTexHub::Prep(const CFrameGraph& graph, + const Bind * binds, unsigned count, CTexStage& stage) +{ + if (stage.m_owner != this || stage.m_next || !graph.Generation() || + count > TRANSPORT_MAX_INSTANCES || (count && !binds)) + return CfgResult::REJECTED; + + unsigned leafCount = 0; + const GraphLeaf * leaves = graph.Leaves(leafCount); + if (!leaves || leafCount != count) + return CfgResult::REJECTED; + + std::shared_ptr next; + try + { + next = std::make_shared(); + } + catch (const std::bad_alloc&) + { + return CfgResult::FAILED; + } + next->generation = graph.Generation(); + next->graph = graph; + + for (unsigned leaf = 0; leaf < leafCount; ++leaf) + { + const GraphLeaf& route = leaves[leaf]; + const Bind * binding = nullptr; + for (unsigned bind = 0; bind < count; ++bind) + if (binds[bind].id == route.id && binds[bind].epoch == route.epoch) + { + if (binding) + return CfgResult::FAILED; + binding = &binds[bind]; + } + if (!binding || !binding->owner) + return CfgResult::FAILED; + + // Legacy transports deliberately have no texture sink yet. Their normal + // frame service remains active while this route table stays dormant. + if (!binding->sink) + continue; + + CTexSet::Route& target = next->routes[next->count++]; + target.owner = binding->owner; + target.sink = binding->sink; + target.id = route.id; + target.epoch = route.epoch; + target.node = route.node; + target.leaf = leaf; + target.profile = route.cfg.profile; + target.cfg = route.cfg; + target.required = route.required; + target.primary = route.primary; + target.active = true; + target.idle = CreateEvent(nullptr, TRUE, TRUE, nullptr); + if (!target.idle) + return CfgResult::FAILED; + } + + { + CSRWExclusiveLock lock(m_lock); + if (m_stopped || !m_changing || m_open || m_pending) + return CfgResult::RETRY; + m_pending = next; + stage.m_next = next; + } + return CfgResult::ACCEPTED; +} + +void CTexHub::Commit(CTexStage& stage) noexcept +{ + std::shared_ptr old; + { + CSRWExclusiveLock lock(m_lock); + if (stage.m_owner != this || stage.m_next != m_pending) + return; + old = std::move(m_active); + m_active = std::move(m_pending); + m_open = !m_stopped; + m_changing = false; + stage.m_owner = nullptr; + stage.m_next.reset(); + } + old.reset(); +} + +void CTexHub::Abort(CTexStage& stage) noexcept +{ + std::shared_ptr drop; + { + CSRWExclusiveLock lock(m_lock); + if (stage.m_owner != this) + return; + if (stage.m_next == m_pending) + { + drop = std::move(m_pending); + m_open = !m_stopped; + m_changing = false; + } + stage.m_owner = nullptr; + stage.m_next.reset(); + } + drop.reset(); +} + +PushResult CTexHub::Push(FrameIn frame, TexLease lease) noexcept +{ + std::shared_ptr set; + unsigned index; + ITexSink * sink = nullptr; + if (!Enter(frame, !!lease, set, index, sink)) + { + if (set) + Leave(set, TRANSPORT_MAX_INSTANCES); + return PushResult::STALE; + } + + const D12SyncState state = lease->Status(); + bool valid; + { + CSRWSharedLock lock(set->lock); + valid = sink && + Valid(*set, set->routes[index], frame, *lease.Get(), + FrameStorage::D3D12_TEXTURE) && + state != D12SyncState::FAILED && + (!lease->time.readyValid || state == D12SyncState::READY); + } + if (!valid) + { + const PushResult result = state == D12SyncState::FAILED && sink ? + PushResult::FAILED : PushResult::STALE; + Finish(set, index, result); + return result; + } + + const PushResult result = sink->Push(std::move(frame), std::move(lease)); + Finish(set, index, result); + return result; +} + +PushResult CTexHub::Push(FrameIn frame, D11Lease lease) noexcept +{ + std::shared_ptr set; + unsigned index; + ITexSink * sink = nullptr; + if (!Enter(frame, !!lease, set, index, sink)) + { + if (set) + Leave(set, TRANSPORT_MAX_INSTANCES); + return PushResult::STALE; + } + + const D12SyncState state = lease.Status(); + const CFrameTex * source = lease.Frame(); + const D12SyncState sourceState = source ? + source->Status() : D12SyncState::FAILED; + bool valid; + { + CSRWSharedLock lock(set->lock); + const CTexSet::Route& route = set->routes[index]; + valid = sink && + source && + Frame::Same(lease.Profile(), route.profile) && + Valid(*set, route, frame, *source, + FrameStorage::D3D11_TEXTURE) && + state != D12SyncState::FAILED && + sourceState != D12SyncState::FAILED && + (!source->time.readyValid || + (state == D12SyncState::READY && + sourceState == D12SyncState::READY)); + } + if (!valid) + { + const PushResult result = sink && + (state == D12SyncState::FAILED || + sourceState == D12SyncState::FAILED) ? + PushResult::FAILED : PushResult::STALE; + Finish(set, index, result); + return result; + } + + const PushResult result = sink->Push(std::move(frame), std::move(lease)); + Finish(set, index, result); + return result; +} + +void CTexHub::Drop(BackendId id, uint32_t epoch) +{ + if (!id || !epoch) + return; + + std::shared_ptr set; + { + CSRWSharedLock lock(m_lock); + if (m_stopped) + return; + set = m_active; + } + if (!set) + { + Rebind(id, epoch); + return; + } + + HANDLE waits[TRANSPORT_MAX_INSTANCES] = {}; + unsigned waitCount = 0; + bool changed = false; + { + CSRWExclusiveLock lock(set->lock); + for (unsigned i = 0; i < set->count; ++i) + if (set->routes[i].id == id && set->routes[i].epoch == epoch) + { + CTexSet::Route& target = set->routes[i]; + changed |= target.active; + target.active = false; + target.dropped = true; + if (target.calls) + { + ResetEvent(target.idle); + waits[waitCount++] = target.idle; + } + } + } + for (unsigned i = 0; i < waitCount; ++i) + WaitForSingleObject(waits[i], INFINITE); + Rebind(id, epoch); + if (changed) + Bump(); +} + +void CTexHub::Stop() +{ + std::shared_ptr active; + std::shared_ptr pending; + { + CSRWExclusiveLock lock(m_lock); + if (m_stopped) + return; + m_stopped = true; + m_changing = true; + m_open = false; + if (m_calls) + ResetEvent(m_idle); + else if (m_idle) + SetEvent(m_idle); + } + + if (m_idle) + WaitForSingleObject(m_idle, INFINITE); + { + CSRWExclusiveLock lock(m_lock); + active = std::move(m_active); + pending = std::move(m_pending); + } + pending.reset(); + active.reset(); +} diff --git a/idd/LGIdd/transport/CTexHub.h b/idd/LGIdd/transport/CTexHub.h new file mode 100644 index 00000000..01f4b1b2 --- /dev/null +++ b/idd/LGIdd/transport/CTexHub.h @@ -0,0 +1,140 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "Atomic.h" +#include "CSRWLock.h" +#include "transport/FrameIn.h" +#include "transport/FrameProfile.h" +#include "transport/TransportConfig.h" + +#include +#include + +class CFrameGraph; +class CTransportManager; +class D11Lease; +class ITransport; +class ITexSink; +class TexLease; +class CTexHub; +struct CTexSet; + +class CTexStage +{ + friend class CTexHub; + +private: + CTexHub * m_owner = nullptr; + std::shared_ptr m_next; + +public: + CTexStage() = default; + CTexStage(const CTexStage&) = delete; + CTexStage& operator=(const CTexStage&) = delete; + CTexStage(CTexStage&& other) noexcept; + CTexStage& operator=(CTexStage&& other) noexcept; + ~CTexStage(); +}; + +class CTexHub +{ + friend class CTransportManager; + friend class CTexStage; + +private: + struct Bind + { + std::shared_ptr owner; + BackendId id = 0; + uint32_t epoch = 0; + ITexSink * sink = nullptr; + }; + + struct FaultRec + { + BackendId id = 0; + uint32_t epoch = 0; + FrameCfg cfg; + CfgResult result = CfgResult::ACCEPTED; + }; + + struct Failure + { + BackendId id = 0; + uint32_t epoch = 0; + }; + + static const unsigned MAX_FAULTS = + TRANSPORT_MAX_INSTANCES * FRAME_PROFILE_MAX; + + mutable CSRWLock m_lock; + std::shared_ptr m_active; + std::shared_ptr m_pending; + std::atomic & m_rev; + HANDLE m_idle = nullptr; + unsigned m_calls = 0; + bool m_open = true; + bool m_changing = false; + bool m_stopped = false; + FaultRec m_faults[MAX_FAULTS] = {}; + unsigned m_faultCount = 0; + Failure m_failures[TRANSPORT_MAX_INSTANCES] = {}; + unsigned m_failureCount = 0; + + static bool Match(const FaultRec& fault, BackendId id, uint32_t epoch, + const FrameCfg& cfg, bool anyCfg = false); + + bool Enter(const FrameIn& frame, bool lease, + std::shared_ptr& set, unsigned& route, ITexSink *& sink); + void Finish(const std::shared_ptr& set, unsigned route, + PushResult result); + void Leave(const std::shared_ptr& set, unsigned route); + void Disable(const std::shared_ptr& set, unsigned route, + PushResult result); + void Bump(); + + explicit CTexHub(std::atomic& rev); + ~CTexHub(); + + CTexHub(const CTexHub&) = delete; + CTexHub& operator=(const CTexHub&) = delete; + + CfgResult Hold(CTexStage& stage); + CfgResult Prep(const CFrameGraph& graph, + const Bind * binds, unsigned count, CTexStage& stage); + void Commit(CTexStage& stage) noexcept; + void Abort(CTexStage& stage) noexcept; + + CfgResult Faulted(BackendId id, uint32_t epoch, + const FrameCfg& cfg) const; + bool TakeFailure(BackendId& id, uint32_t& epoch); + void Rebind(BackendId id, uint32_t epoch); + + // Drop closes only this route and drains its calls before the transport can + // stop. Accepted asynchronous leases remain the transport's responsibility. + void Drop(BackendId id, uint32_t epoch); + void Stop(); + +public: + PushResult Push(FrameIn frame, TexLease lease) noexcept; + PushResult Push(FrameIn frame, D11Lease lease) noexcept; +}; diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp index 5c87f6e1..1e9d4844 100644 --- a/idd/LGIdd/transport/CTransportManager.cpp +++ b/idd/LGIdd/transport/CTransportManager.cpp @@ -20,9 +20,11 @@ #include "transport/CTransportManager.h" +#include "Atomic.h" #include "capture/CFrameGraph.h" #include "CDebug.h" #include "Seq.h" +#include "transport/ITexStage.h" #include #include @@ -117,12 +119,27 @@ CTransportManager::Entry::~Entry() CloseHandle(idleEvent); } -CTransportManager::CTransportManager() +CTransportManager::CTransportManager() : m_tex(m_frameRev) { m_phaseIdle = CreateEvent(nullptr, TRUE, TRUE, nullptr); m_stoppedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); } +uint64_t CTransportManager::FrameRev() const +{ + return Atomic::Load(m_frameRev, std::memory_order_acquire); +} + +void CTransportManager::BumpFrameRev() +{ + Atomic::Next(m_frameRev, std::memory_order_release); +} + +uint64_t CTransportManager::NextGraph() +{ + return Seq::Inc(m_graphSerial); +} + CTransportManager::~CTransportManager() { Stop(); @@ -469,6 +486,7 @@ bool CTransportManager::AddServices(Entry& entry) bool inputFailed = false; bool inputAbsent = false; bool frameAdded = false; + bool frameBound = false; bool frameAbsent = false; uint64_t retryAt = 0; { @@ -524,11 +542,24 @@ bool CTransportManager::AddServices(Entry& entry) else if (attach && !frameAdded && !frameAbsent) { IFrameSink * frame = transport->FrameSink(); + ITexSink * tex = transport->TexSink(); if (frame && m_frames.Bind(id, epoch, primary, *frame)) { CSRWExclusiveLock entryLock(entry.lock); - entry.frameAdded = true; - frameAdded = true; + entry.frameAdded = true; + entry.frameLegacy = true; + entry.texSink = nullptr; + frameAdded = true; + frameBound = true; + } + else if (!frame && tex) + { + CSRWExclusiveLock entryLock(entry.lock); + entry.frameAdded = true; + entry.frameLegacy = false; + entry.texSink = tex; + frameAdded = true; + frameBound = true; } else if (frame || primary) frameRetry = true; @@ -567,6 +598,12 @@ bool CTransportManager::AddServices(Entry& entry) entry.serviceRetryAt = now + SERVICE_RETRY_DELAY_MS; } + if (frameBound) + { + m_tex.Rebind(id, epoch); + BumpFrameRev(); + } + const bool servicesReady = (!(services & TRANSPORT_SERVICE_FRAME) || frameAdded) && (!(services & TRANSPORT_SERVICE_CONTROL) || controlAdded) && @@ -576,6 +613,37 @@ bool CTransportManager::AddServices(Entry& entry) void CTransportManager::HandleServiceFailures() { + BackendId frameId = 0; + uint32_t frameEpoch = 0; + while (m_tex.TakeFailure(frameId, frameEpoch)) + { + Entry * entries[FRAME_MAX_SINKS] = {}; + const unsigned count = Entries(entries); + for (unsigned i = 0; i < count; ++i) + { + Entry& entry = *entries[i]; + bool restart = false; + { + CSRWSharedLock entryLock(entry.lock); + if (entry.id != frameId || entry.epoch != frameEpoch || + !entry.frameAdded || !entry.texSink) + continue; + restart = !entry.exposed; + } + + DetachRecovery(entry); + RemoveServices(entry); + if (restart) + ScheduleRetry(entry); + else + { + CSRWExclusiveLock entryLock(entry.lock); + entry.state = State::FAILED; + } + break; + } + } + ControlToken token; while (m_control.TakeFailure(token)) { @@ -687,6 +755,7 @@ void CTransportManager::RemoveServices(Entry& entry) BackendId id = 0; uint32_t epoch = 0; bool frameAdded = false; + bool frameLegacy = false; bool controlAdded = false; bool inputAdded = false; { @@ -694,20 +763,28 @@ void CTransportManager::RemoveServices(Entry& entry) id = entry.id; epoch = entry.epoch; frameAdded = entry.frameAdded; + frameLegacy = entry.frameLegacy; controlAdded = entry.controlAdded; inputAdded = entry.inputAdded; entry.frameAdded = false; + entry.frameLegacy = false; + entry.texSink = nullptr; entry.controlAdded = false; entry.inputAdded = false; } m_input.RevokeInteraction(id, epoch); + m_tex.Drop(id, epoch); if (inputAdded) m_input.Unbind(id, epoch); if (controlAdded) m_control.Remove(id, epoch); - if (frameAdded) + if (frameLegacy) m_frames.Unbind(id, epoch); + if (frameAdded) + { + BumpFrameRev(); + } } void CTransportManager::RetryEntry(Entry& entry, uint64_t now, @@ -729,6 +806,8 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now, { CSRWExclusiveLock entryLock(entry.lock); + const bool frameService = + (entry.config.services & TRANSPORT_SERVICE_FRAME) != 0; entry.transport.reset(); entry.directMemory = DirectFrameBufferMemory {}; entry.directMemoryValid = false; @@ -740,6 +819,8 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now, entry.frameAbsent = false; entry.serviceRetryAt = 0; Seq::Inc(entry.epoch); + if (frameService) + BumpFrameRev(); } if (OpenEntry(entry) != OpenResult::SUCCESS) @@ -994,32 +1075,46 @@ bool CTransportManager::Setup(size_t alignment) return success; } -CfgResult CTransportManager::Cfg( - const GraphCfg& cfg, CFrameGraph& graph) +CfgResult CTransportManager::Cfg(const GraphCfg& cfg, + uint64_t revision, CFrameGraph& graph, ITexStage * activation) { + if (!revision || revision != FrameRev()) + return CfgResult::RETRY; + CFrameGraph next; if (!next.Begin(cfg)) return CfgResult::REJECTED; if (!BeginPhase(Phase::CFG, true)) return CfgResult::RETRY; + CTexStage texStage; + CfgResult held = m_tex.Hold(texStage); + if (held != CfgResult::ACCEPTED) + { + m_tex.Abort(texStage); + EndPhase(); + return held; + } + struct Route { - Entry * entry = nullptr; + Entry * entry = nullptr; std::shared_ptr transport; - BackendId id = 0; - uint32_t epoch = 0; - bool required = false; - bool primary = false; - bool prepared = false; - bool eligible = false; - FrameProfile profiles[FRAME_PROFILE_MAX] = {}; - unsigned profileCount = 0; + BackendId id = 0; + uint32_t epoch = 0; + bool required = false; + bool primary = false; + bool prepared = false; + bool eligible = false; + ITexSink * texSink = nullptr; + FrameProfile profiles[FRAME_PROFILE_MAX] = {}; + unsigned profileCount = 0; }; Route routes[FRAME_MAX_SINKS]; unsigned routeCount = 0; - CfgResult result = CfgResult::ACCEPTED; + CfgResult result = revision == FrameRev() ? + CfgResult::ACCEPTED : CfgResult::RETRY; Entry * entries[FRAME_MAX_SINKS] = {}; const unsigned count = Entries(entries); @@ -1051,6 +1146,7 @@ CfgResult CTransportManager::Cfg( route.entry = &entry; State state; bool frameAbsent = false; + bool frameAdded = false; { CSRWSharedLock entryLock(entry.lock); route.transport = entry.transport; @@ -1058,12 +1154,14 @@ CfgResult CTransportManager::Cfg( route.epoch = entry.epoch; route.required = entry.required; route.primary = entry.primary; + route.texSink = entry.texSink; state = entry.state; frameAbsent = entry.frameAbsent; + frameAdded = entry.frameAdded; } route.eligible = route.transport && !frameAbsent && - (state == State::INITIALIZED || state == State::READY); + frameAdded && state == State::READY; if (!route.eligible) { if (!route.required && @@ -1073,6 +1171,16 @@ CfgResult CTransportManager::Cfg( (state == State::FAILED ? CfgResult::FAILED : CfgResult::RETRY); break; } + if (route.texSink && !activation) + { + route.eligible = false; + if (route.required) + { + result = CfgResult::REJECTED; + break; + } + continue; + } unsigned profileCount = 0; const FrameProfile * profiles = @@ -1114,6 +1222,22 @@ CfgResult CTransportManager::Cfg( if (!next.Can(candidate)) continue; + if (route.texSink) + { + const CfgResult fault = + m_tex.Faulted(route.id, route.epoch, candidate); + if (fault == CfgResult::FAILED) + { + routeResult = fault; + break; + } + if (fault == CfgResult::REJECTED) + { + routeResult = fault; + continue; + } + } + routeResult = route.transport->Probe(candidate); if (routeResult == CfgResult::NEXT) continue; @@ -1160,17 +1284,53 @@ CfgResult CTransportManager::Cfg( if (result == CfgResult::ACCEPTED && !next.Seal()) result = CfgResult::FAILED; + if (result == CfgResult::ACCEPTED && + !next.Stamp(NextGraph())) + result = CfgResult::FAILED; + + CTexHub::Bind binds[FRAME_MAX_SINKS]; + unsigned bindCount = 0; + if (result == CfgResult::ACCEPTED) + for (unsigned i = 0; i < routeCount; ++i) + if (routes[i].prepared) + { + CTexHub::Bind& bind = binds[bindCount++]; + bind.owner = routes[i].transport; + bind.id = routes[i].id; + bind.epoch = routes[i].epoch; + bind.sink = routes[i].texSink; + } + + bool activationReady = false; + if (result == CfgResult::ACCEPTED && activation) + { + result = activation->Prep(next); + activationReady = result == CfgResult::ACCEPTED; + } + if (result == CfgResult::ACCEPTED) + result = m_tex.Prep(next, binds, bindCount, texStage); + if (result == CfgResult::ACCEPTED && revision != FrameRev()) + result = CfgResult::RETRY; + if (result == CfgResult::ACCEPTED) { for (unsigned i = 0; i < routeCount; ++i) if (routes[i].prepared) routes[i].transport->Commit(); graph = next; + m_tex.Commit(texStage); + if (activationReady) + activation->Commit(); } else + { for (unsigned i = routeCount; i > 0; --i) if (routes[i - 1].prepared) routes[i - 1].transport->Abort(); + m_tex.Abort(texStage); + if (activationReady) + activation->Abort(); + } for (unsigned i = routeCount; i > 0; --i) EndCall(*routes[i - 1].entry, routes[i - 1].transport); @@ -1343,6 +1503,8 @@ void CTransportManager::Stop() return; } + m_tex.Stop(); + for (unsigned i = count; i > 0; --i) { DetachRecovery(*entries[i - 1]); diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h index 61b62820..385bdee9 100644 --- a/idd/LGIdd/transport/CTransportManager.h +++ b/idd/LGIdd/transport/CTransportManager.h @@ -20,11 +20,13 @@ #pragma once +#include "Atomic.h" #include "CSRWLock.h" #include "transport/CControlHub.h" #include "transport/CFrameHub.h" #include "transport/CInputHub.h" #include "transport/CRecoveryHub.h" +#include "transport/CTexHub.h" #include "transport/ITransport.h" #include "transport/TransportConfig.h" @@ -34,6 +36,7 @@ static_assert(TRANSPORT_MAX_INSTANCES == FRAME_MAX_SINKS, "The transport and frame limits must match"); class CFrameGraph; +class ITexStage; struct GraphCfg; class CTransportManager final : public FrameCaps @@ -106,8 +109,10 @@ private: bool inputAdded = false; bool inputFailed = false; bool inputAbsent = false; - bool frameAdded = false; - bool frameAbsent = false; + bool frameAdded = false; + bool frameLegacy = false; + ITexSink * texSink = nullptr; + bool frameAbsent = false; uint64_t serviceRetryAt = 0; bool exposed = false; bool setupDone = false; @@ -121,8 +126,10 @@ private: std::unique_ptr m_entries[FRAME_MAX_SINKS]; unsigned m_entryCount = 0; + std::atomic m_frameRev = 1; CControlHub m_control; CFrameHub m_frames; + CTexHub m_tex; CInputHub m_input; CRecoveryHub m_recovery; Entry * m_primary = nullptr; @@ -136,6 +143,7 @@ private: bool m_started = false; bool m_stopping = false; bool m_stopped = false; + uint64_t m_graphSerial = 0; unsigned Entries(Entry * entries[FRAME_MAX_SINKS]) const; Entry * Primary() const; @@ -160,6 +168,8 @@ private: void ScheduleRetry(Entry& entry); void RemoveServices(Entry& entry); void Expose(Entry& entry); + void BumpFrameRev(); + uint64_t NextGraph(); public: CTransportManager(); @@ -173,7 +183,8 @@ public: OpenResult Open(); bool Initialize(); bool Setup(size_t alignment); - CfgResult Cfg(const GraphCfg& cfg, CFrameGraph& graph); + CfgResult Cfg(const GraphCfg& cfg, uint64_t revision, + CFrameGraph& graph, ITexStage * activation = nullptr); ProcessResult Process(ITransportActions& actions); void Stop(); void SyncRecovery(); @@ -186,6 +197,8 @@ public: DirectFrameBufferMemory GetDirectMemory() const; IFrameTransport& Frames(); + CTexHub& Tex() { return m_tex; } + uint64_t FrameRev() const; IControlTransport& Control(); IInputTransport& Input(); }; diff --git a/idd/LGIdd/transport/FrameIn.h b/idd/LGIdd/transport/FrameIn.h new file mode 100644 index 00000000..c3623f4a --- /dev/null +++ b/idd/LGIdd/transport/FrameIn.h @@ -0,0 +1,42 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "capture/CFrameGraph.h" + +#include + +enum class PushResult : uint8_t +{ + ACCEPTED, + BUSY, + STALE, + REJECTED, + FAILED, +}; + +// The texture is supplied separately as exactly one typed lease. Node zero is +// never valid because it is the borrowed IddCx acquisition surface. +struct FrameIn +{ + uint64_t graph = 0; + LeafDesc desc; +}; diff --git a/idd/LGIdd/transport/ITexSink.h b/idd/LGIdd/transport/ITexSink.h new file mode 100644 index 00000000..ae830ed5 --- /dev/null +++ b/idd/LGIdd/transport/ITexSink.h @@ -0,0 +1,40 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "capture/CFrameTex.h" +#include "d3d/CInteropPool.h" +#include "transport/FrameIn.h" + +class ITexSink +{ +public: + virtual ~ITexSink() = default; + + // Push is bounded and nonblocking. ACCEPTED transfers the self-contained + // frame and lease to the transport; both remain retained until its + // asynchronous GPU use completes. Other results retain neither object and + // leave sibling routes and providers untouched. A later executor must make + // the next accepted product full-damage after BUSY or another dropped + // partial update. + virtual PushResult Push(FrameIn frame, TexLease lease) noexcept = 0; + virtual PushResult Push(FrameIn frame, D11Lease lease) noexcept = 0; +}; diff --git a/idd/LGIdd/transport/ITexStage.h b/idd/LGIdd/transport/ITexStage.h new file mode 100644 index 00000000..2db470ac --- /dev/null +++ b/idd/LGIdd/transport/ITexStage.h @@ -0,0 +1,40 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "transport/FrameProfile.h" + +class CFrameGraph; + +// The future graph executor stages every fallible pool and interop resource +// here. Commit and Abort are infallible so transport configuration and graph +// activation remain one transaction. A non-ACCEPTED Prep leaves no pending +// state; an ACCEPTED Prep remains dormant until Commit. Commit publishes the +// new producer only after texture admission has opened. Abort may resume the +// old producer because the old admission set is reopened first. +class ITexStage +{ +public: + virtual ~ITexStage() = default; + virtual CfgResult Prep(const CFrameGraph& graph) noexcept = 0; + virtual void Commit() noexcept = 0; + virtual void Abort() noexcept = 0; +}; diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h index ec2a13a9..e46492d3 100644 --- a/idd/LGIdd/transport/ITransport.h +++ b/idd/LGIdd/transport/ITransport.h @@ -32,6 +32,7 @@ class IControlSink; class IFrameSink; class IInputSource; +class ITexSink; struct SourceKey { @@ -151,8 +152,11 @@ public: // Profiles is an immutable, ordered preference list of at most // FRAME_PROFILE_MAX entries whose pointer remains valid for the lifetime // of this instance. - // Probe has no side effects. Prepare changes pending state only; Commit - // promotes it without failure, while Abort preserves the active route. + // Probe has no side effects. Prepare changes pending state only and is safe + // while the active texture sink is admitting frames. Commit promotes it + // without failure, while Abort preserves the active route. A texture + // transport retains old-generation resources until every accepted lease + // completes; Stop drains all accepted leases before destroying its sink. virtual const FrameProfile * Profiles(unsigned& count) const = 0; virtual CfgResult Probe(const FrameCfg& cfg) const = 0; virtual CfgResult Prepare(const FrameCfg& cfg) = 0; @@ -171,6 +175,7 @@ public: // Component pointers are fixed after Setup and remain valid until Stop. virtual IFrameSink * FrameSink() { return nullptr; } + virtual ITexSink * TexSink() { return nullptr; } virtual IControlSink * Control() { return nullptr; } virtual IInputSource * Input() { return nullptr; } };