mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[idd] transport: route immutable texture frames
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
644
idd/LGIdd/transport/CTexHub.cpp
Normal file
644
idd/LGIdd/transport/CTexHub.cpp
Normal 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();
|
||||
}
|
||||
140
idd/LGIdd/transport/CTexHub.h
Normal file
140
idd/LGIdd/transport/CTexHub.h
Normal 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;
|
||||
};
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
42
idd/LGIdd/transport/FrameIn.h
Normal file
42
idd/LGIdd/transport/FrameIn.h
Normal 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;
|
||||
};
|
||||
40
idd/LGIdd/transport/ITexSink.h
Normal file
40
idd/LGIdd/transport/ITexSink.h
Normal 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;
|
||||
};
|
||||
40
idd/LGIdd/transport/ITexStage.h
Normal file
40
idd/LGIdd/transport/ITexStage.h
Normal 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;
|
||||
};
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user