[idd] transport: route immutable texture frames

This commit is contained in:
Geoffrey McRae
2026-08-13 17:33:32 +10:00
parent ba7a037f23
commit deb963774e
16 changed files with 1207 additions and 37 deletions

View File

@@ -68,6 +68,7 @@
<ClCompile Include="transport\CFrameHub.cpp" />
<ClCompile Include="transport\CInputHub.cpp" />
<ClCompile Include="transport\CRecoveryHub.cpp" />
<ClCompile Include="transport\CTexHub.cpp" />
<ClCompile Include="transport\CTransportManager.cpp" />
<ClCompile Include="transport\TransportConfig.cpp" />
<ClCompile Include="transport\TransportFactory.cpp" />
@@ -124,10 +125,12 @@
<ClInclude Include="transport\CFrameHub.h" />
<ClInclude Include="transport\CInputHub.h" />
<ClInclude Include="transport\CRecoveryHub.h" />
<ClInclude Include="transport\CTexHub.h" />
<ClInclude Include="transport\CTransportManager.h" />
<ClInclude Include="transport\DirectFrameBufferMemory.h" />
<ClInclude Include="transport\FrameCaps.h" />
<ClInclude Include="transport\FrameProfile.h" />
<ClInclude Include="transport\FrameIn.h" />
<ClInclude Include="transport\IControlSink.h" />
<ClInclude Include="transport\IControlTransport.h" />
<ClInclude Include="transport\IFrameSink.h" />
@@ -135,6 +138,8 @@
<ClInclude Include="transport\IInputTransport.h" />
<ClInclude Include="transport\IInputSource.h" />
<ClInclude Include="transport\ITransport.h" />
<ClInclude Include="transport\ITexSink.h" />
<ClInclude Include="transport\ITexStage.h" />
<ClInclude Include="transport\TransportConfig.h" />
<ClInclude Include="transport\PreparedFrameBuffer.h" />
<ClInclude Include="transport\TransportFactory.h" />

View File

@@ -175,6 +175,9 @@
<ClInclude Include="transport\CRecoveryHub.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\CTexHub.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\CTransportManager.h">
<Filter>Transport</Filter>
</ClInclude>
@@ -187,6 +190,9 @@
<ClInclude Include="transport\FrameProfile.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\FrameIn.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\IControlSink.h">
<Filter>Transport</Filter>
</ClInclude>
@@ -208,6 +214,12 @@
<ClInclude Include="transport\ITransport.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\ITexSink.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\ITexStage.h">
<Filter>Transport</Filter>
</ClInclude>
<ClInclude Include="transport\TransportConfig.h">
<Filter>Transport</Filter>
</ClInclude>
@@ -357,6 +369,9 @@
<ClCompile Include="transport\CRecoveryHub.cpp">
<Filter>Transport</Filter>
</ClCompile>
<ClCompile Include="transport\CTexHub.cpp">
<Filter>Transport</Filter>
</ClCompile>
<ClCompile Include="transport\CTransportManager.cpp">
<Filter>Transport</Filter>
</ClCompile>

View File

@@ -67,6 +67,7 @@ bool Frame::Valid(FrameDamage damage, const RECT * rects, unsigned count,
void CFrameGraph::Reset()
{
m_cfg = GraphCfg {};
m_generation = 0;
m_nodeCount = 0;
m_leafCount = 0;
m_begun = false;
@@ -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);

View File

@@ -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;
};

View File

@@ -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<ID3D12Resource>& res,
const FrameDesc& frameValue, const FrameTime& timeValue,
const ComPtr<ID3D12Resource>& 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<CTexCore> core = m_core;
if (!ValidTime(time))
{
core->Fail(m_index, m_version, sync);
Clear();
return false;
}
ComPtr<ID3D12Resource> 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);

View File

@@ -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<ID3D12Resource>& res, const D12Sync& sync,
const FrameTime& time, const ComPtr<ID3D12Resource>& 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();
};

View File

@@ -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;

View File

@@ -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<CFrameProcessor> m_frameProcessor;

View File

@@ -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 <cstddef>
#include <cstring>
#include <new>
#include <utility>
namespace
{
template<typename T, std::size_t N>
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<ITransport> 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<uint64_t>& 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<CTexSet>& 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<CTexSet>& 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<CTexSet>& 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<CTexSet>& 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<CTexSet> next;
try
{
next = std::make_shared<CTexSet>();
}
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<CTexSet> 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<CTexSet> 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<CTexSet> 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<CTexSet> 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<CTexSet> 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<CTexSet> active;
std::shared_ptr<CTexSet> 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();
}

View File

@@ -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 <Windows.h>
#include <memory>
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<CTexSet> 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<ITransport> 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<CTexSet> m_active;
std::shared_ptr<CTexSet> m_pending;
std::atomic<uint64_t> & 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<CTexSet>& set, unsigned& route, ITexSink *& sink);
void Finish(const std::shared_ptr<CTexSet>& set, unsigned route,
PushResult result);
void Leave(const std::shared_ptr<CTexSet>& set, unsigned route);
void Disable(const std::shared_ptr<CTexSet>& set, unsigned route,
PushResult result);
void Bump();
explicit CTexHub(std::atomic<uint64_t>& 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;
};

View File

@@ -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 <Windows.h>
#include <new>
@@ -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;
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,15 +1075,27 @@ 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;
@@ -1013,13 +1106,15 @@ CfgResult CTransportManager::Cfg(
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]);

View File

@@ -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
@@ -107,6 +110,8 @@ private:
bool inputFailed = false;
bool inputAbsent = false;
bool frameAdded = false;
bool frameLegacy = false;
ITexSink * texSink = nullptr;
bool frameAbsent = false;
uint64_t serviceRetryAt = 0;
bool exposed = false;
@@ -121,8 +126,10 @@ private:
std::unique_ptr<Entry> m_entries[FRAME_MAX_SINKS];
unsigned m_entryCount = 0;
std::atomic<uint64_t> 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();
};

View File

@@ -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 <stdint.h>
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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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; }
};