[idd] capture: version frame graph changes

This commit is contained in:
Geoffrey McRae
2026-08-13 22:47:42 +10:00
parent 70bc66b3f1
commit 1dcab65ab9
7 changed files with 243 additions and 79 deletions

View File

@@ -319,7 +319,8 @@ struct CFrameExec::Core : std::enable_shared_from_this<CFrameExec::Core>
CfgResult Init(const CFrameGraph& source,
const std::shared_ptr<CD3D11Device>& d11Device,
const std::shared_ptr<CD3D12Device>& d12Device, CTexHub& texHub)
const std::shared_ptr<CD3D12Device>& d12Device, CTexHub& texHub,
bool inheritedFull)
{
d11 = d11Device;
d12 = d12Device;
@@ -337,6 +338,7 @@ struct CFrameExec::Core : std::enable_shared_from_this<CFrameExec::Core>
return CfgResult::REJECTED;
unsigned texLeaves = 0;
bool continuous = true;
for (unsigned leaf = 0; leaf < leafCount; ++leaf)
if (leaves[leaf].tex)
{
@@ -347,11 +349,14 @@ struct CFrameExec::Core : std::enable_shared_from_this<CFrameExec::Core>
d11Node[leaves[leaf].node] |=
leaves[leaf].cfg.profile.storage ==
FrameStorage::D3D11_TEXTURE;
continuous &= leaves[leaf].continuous;
++texLeaves;
}
if (!texLeaves)
return CfgResult::ACCEPTED;
Atomic::Store(forceFull, inheritedFull || !continuous,
std::memory_order_relaxed);
if (!nodes[0].texRefs || !queue.Init(device.Get(),
D3D12_COMMAND_LIST_TYPE_DIRECT, L"Frame Graph",
CD3D12CommandSlot::FAST, EXEC_LANES, false, true))
@@ -413,6 +418,12 @@ struct CFrameExec::Core : std::enable_shared_from_this<CFrameExec::Core>
return CfgResult::ACCEPTED;
}
bool NeedsFull()
{
CSRWExclusiveLock lock(runLock);
return Atomic::Load(forceFull, std::memory_order_acquire);
}
FrameContentRef Content(uint64_t serial, const D12FrameFormat& format,
uint64_t captureTime, bool full, FrameDamage damage,
const RECT * rects, unsigned count)
@@ -689,6 +700,7 @@ CfgResult CFrameExec::Prep(const CFrameGraph& graph) noexcept
{
std::shared_ptr<CD3D11Device> d11;
std::shared_ptr<CD3D12Device> d12;
std::shared_ptr<Core> active;
CTexHub * hub = nullptr;
{
CSRWSharedLock lock(m_lock);
@@ -696,6 +708,7 @@ CfgResult CFrameExec::Prep(const CFrameGraph& graph) noexcept
return CfgResult::REJECTED;
d11 = m_d11;
d12 = m_d12;
active = m_active;
hub = m_hub;
}
if (!d11 || !d12 || !hub)
@@ -716,7 +729,8 @@ CfgResult CFrameExec::Prep(const CFrameGraph& graph) noexcept
CfgResult result = CfgResult::FAILED;
try
{
result = next->Init(graph, d11, d12, *hub);
result = next->Init(graph, d11, d12, *hub,
!active || active->NeedsFull());
}
catch (...)
{

View File

@@ -241,7 +241,7 @@ unsigned CFrameGraph::Checkpoint(const FrameProfile& requested)
}
bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
bool primary, bool tex, const FrameCfg& cfg)
bool primary, bool tex, bool continuous, const FrameCfg& cfg)
{
if (!id || !epoch || !Can(cfg) ||
m_leafCount == TRANSPORT_MAX_INSTANCES)
@@ -267,6 +267,7 @@ bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
leaf.required = required;
leaf.primary = primary;
leaf.tex = tex;
leaf.continuous = continuous;
leaf.cfg = cfg;
for (unsigned current = node;
@@ -299,7 +300,8 @@ bool CFrameGraph::Seal()
bool CFrameGraph::Stamp(uint64_t generation)
{
if (!m_sealed || m_generation || !generation)
if (!m_sealed || m_generation || !generation ||
!m_leafCount || !m_nodeCount)
return false;
m_generation = generation;
return true;
@@ -307,12 +309,29 @@ bool CFrameGraph::Stamp(uint64_t generation)
bool CFrameGraph::Same(const GraphCfg& cfg) const
{
return m_sealed && Frame::Same(m_cfg, cfg);
return Ready() && Frame::Same(m_cfg, cfg);
}
const GraphLeaf * CFrameGraph::FindLeaf(BackendId id, uint32_t epoch,
bool tex, const FrameCfg& frame) const
{
if (!Ready())
return nullptr;
for (unsigned i = 0; i < m_leafCount; ++i)
{
const GraphLeaf& leaf = m_leaves[i];
if (leaf.id == id &&
leaf.epoch == epoch &&
leaf.tex == tex &&
Frame::Same(leaf.cfg, frame))
return &leaf;
}
return nullptr;
}
bool CFrameGraph::Need(FrameOp op) const
{
if (!m_sealed || op == FrameOp::SRC)
if (!Ready() || op == FrameOp::SRC)
return false;
for (unsigned i = 1; i < m_nodeCount; ++i)
if (m_nodes[i].op == op && m_nodes[i].refs)
@@ -322,7 +341,7 @@ bool CFrameGraph::Need(FrameOp op) const
bool CFrameGraph::Want(FrameSignal signal) const
{
if (!m_sealed)
if (!Ready())
return false;
for (unsigned i = 0; i < m_leafCount; ++i)
if (m_leaves[i].cfg.profile.signal == signal)
@@ -332,7 +351,7 @@ bool CFrameGraph::Want(FrameSignal signal) const
bool CFrameGraph::Shared(unsigned node) const
{
if (!m_sealed || !node || node >= m_nodeCount ||
if (!Ready() || !node || node >= m_nodeCount ||
!m_nodes[node].texRefs)
return false;
for (unsigned i = 0; i < m_leafCount; ++i)
@@ -346,7 +365,7 @@ bool CFrameGraph::Shared(unsigned node) const
bool CFrameGraph::Desc(unsigned nodeIndex,
const FrameContentRef& content, FrameDesc& desc) const
{
if (!content)
if (!Ready() || !content)
return false;
const D12FrameFormat& source = content->format;
@@ -358,8 +377,7 @@ bool CFrameGraph::Desc(unsigned nodeIndex,
if (!validProfile || !Frame::Same(sourceProfile, m_cfg.src))
return false;
if (!m_sealed ||
!nodeIndex ||
if (!nodeIndex ||
nodeIndex >= m_nodeCount ||
!content->serial ||
source.width != m_cfg.srcWidth ||
@@ -412,7 +430,7 @@ bool CFrameGraph::Desc(unsigned nodeIndex,
bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const
{
if (!m_sealed || leaf >= m_leafCount)
if (!Ready() || leaf >= m_leafCount)
return false;
const GraphLeaf& route = m_leaves[leaf];
@@ -430,12 +448,12 @@ bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
const GraphNode * CFrameGraph::Nodes(unsigned& count) const
{
count = m_sealed ? m_nodeCount : 0;
count = Ready() ? m_nodeCount : 0;
return count ? m_nodes : nullptr;
}
const GraphLeaf * CFrameGraph::Leaves(unsigned& count) const
{
count = m_sealed ? m_leafCount : 0;
count = Ready() ? m_leafCount : 0;
return count ? m_leaves : nullptr;
}

View File

@@ -91,6 +91,8 @@ struct GraphLeaf
bool required = false;
bool primary = false;
bool tex = false;
// Partial damage remains valid across the preceding graph generation.
bool continuous = false;
FrameCfg cfg;
};
@@ -152,10 +154,13 @@ public:
bool Begin(const GraphCfg& cfg);
bool Can(const FrameCfg& cfg) const;
bool Add(BackendId id, uint32_t epoch, bool required, bool primary,
bool tex, const FrameCfg& cfg);
bool tex, bool continuous, const FrameCfg& cfg);
bool Seal();
bool Stamp(uint64_t generation);
bool Ready() const { return m_sealed && m_generation; }
bool Same(const GraphCfg& cfg) const;
const GraphLeaf * FindLeaf(BackendId id, uint32_t epoch, bool tex,
const FrameCfg& frame) const;
bool Want(FrameSignal signal) const;
bool Need(FrameOp op) const;
bool Shared(unsigned node) const;
@@ -165,7 +170,7 @@ public:
LeafDesc& desc) const;
const GraphCfg& Cfg() const { return m_cfg; }
uint64_t Generation() const { return m_generation; }
uint64_t Generation() const { return Ready() ? m_generation : 0; }
const GraphNode * Nodes(unsigned& count) const;
const GraphLeaf * Leaves(unsigned& count) const;
};

View File

@@ -33,6 +33,8 @@
namespace
{
static const uint64_t ROUTE_RETRY_DELAY_MS = 500;
template<typename T, std::size_t N>
T& Append(T (&items)[N], unsigned& count)
{
@@ -50,6 +52,13 @@ struct CTexSet
{
struct Route
{
enum class State : uint8_t
{
ACTIVE,
FAULTED,
DROPPED,
};
std::shared_ptr<ITransport> owner;
ITexSink * sink = nullptr;
BackendId id = 0;
@@ -60,8 +69,7 @@ struct CTexSet
FrameCfg cfg;
bool required = false;
bool primary = false;
bool active = false;
bool dropped = false;
State state = State::ACTIVE;
CfgResult fault = CfgResult::ACCEPTED;
unsigned calls = 0;
HANDLE idle = nullptr;
@@ -184,7 +192,8 @@ bool CTexHub::Enter(const FrameIn& frame, bool lease,
CSRWExclusiveLock lock(set->lock);
route = Find(*set, frame);
if (route >= set->count || !set->routes[route].active || !lease ||
if (route >= set->count ||
set->routes[route].state != CTexSet::Route::State::ACTIVE || !lease ||
frame.graph != set->generation)
return false;
@@ -228,6 +237,30 @@ CfgResult CTexHub::Faulted(BackendId id, uint32_t epoch,
return CfgResult::ACCEPTED;
}
bool CTexHub::Active(uint64_t generation, BackendId id,
uint32_t epoch, const FrameCfg& cfg) const
{
std::shared_ptr<CTexSet> set;
{
CSRWSharedLock lock(m_lock);
set = m_active;
}
if (!set || !generation || set->generation != generation)
return false;
CSRWSharedLock lock(set->lock);
for (unsigned i = 0; i < set->count; ++i)
{
const CTexSet::Route& route = set->routes[i];
if (route.id == id &&
route.epoch == epoch &&
route.state == CTexSet::Route::State::ACTIVE &&
Frame::Same(route.cfg, cfg))
return true;
}
return false;
}
void CTexHub::Rebind(BackendId id, uint32_t epoch)
{
if (!id || !epoch)
@@ -243,6 +276,39 @@ void CTexHub::Rebind(BackendId id, uint32_t epoch)
m_faults[i] = m_faults[--m_faultCount];
m_faults[m_faultCount] = FaultRec {};
}
for (unsigned i = 0; i < m_failureCount;)
{
if (m_failures[i].id != id || m_failures[i].epoch != epoch)
{
++i;
continue;
}
m_failures[i] = m_failures[--m_failureCount];
m_failures[m_failureCount] = Failure {};
}
}
void CTexHub::Retry(uint64_t now)
{
bool wake = false;
{
CSRWExclusiveLock lock(m_lock);
for (unsigned i = 0; i < m_faultCount;)
{
const FaultRec& fault = m_faults[i];
if (fault.result != CfgResult::REJECTED || !fault.retryAt ||
now < fault.retryAt)
{
++i;
continue;
}
m_faults[i] = m_faults[--m_faultCount];
m_faults[m_faultCount] = FaultRec {};
wake = true;
}
}
if (wake)
Bump();
}
void CTexHub::Leave(
@@ -267,13 +333,14 @@ void CTexHub::Disable(const std::shared_ptr<CTexSet>& set,
CTexSet::Route failed;
{
CSRWExclusiveLock lock(set->lock);
if (route < set->count && !set->routes[route].dropped &&
(set->routes[route].active ||
if (route < set->count &&
set->routes[route].state != CTexSet::Route::State::DROPPED &&
(set->routes[route].state == CTexSet::Route::State::ACTIVE ||
(result == PushResult::FAILED &&
set->routes[route].fault == CfgResult::REJECTED)))
{
CTexSet::Route& target = set->routes[route];
target.active = false;
target.state = CTexSet::Route::State::FAULTED;
target.fault = result == PushResult::FAILED ?
CfgResult::FAILED : CfgResult::REJECTED;
failed.id = target.id;
@@ -291,6 +358,8 @@ void CTexHub::Disable(const std::shared_ptr<CTexSet>& set,
record.cfg = failed.cfg;
record.result = result == PushResult::FAILED ?
CfgResult::FAILED : CfgResult::REJECTED;
record.retryAt = result == PushResult::REJECTED ?
GetTickCount64() + ROUTE_RETRY_DELAY_MS : 0;
bool wake = false;
{
@@ -303,6 +372,7 @@ void CTexHub::Disable(const std::shared_ptr<CTexSet>& set,
m_faults[i].result != CfgResult::FAILED)
{
m_faults[i].result = CfgResult::FAILED;
m_faults[i].retryAt = 0;
wake = true;
}
if (result != PushResult::FAILED)
@@ -438,7 +508,7 @@ CfgResult CTexHub::Prep(const CFrameGraph& graph,
target.cfg = route.cfg;
target.required = route.required;
target.primary = route.primary;
target.active = true;
target.state = CTexSet::Route::State::ACTIVE;
target.idle = CreateEvent(nullptr, TRUE, TRUE, nullptr);
if (!target.idle)
return CfgResult::FAILED;
@@ -616,9 +686,8 @@ void CTexHub::Drop(BackendId id, uint32_t epoch)
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;
changed |= target.state == CTexSet::Route::State::ACTIVE;
target.state = CTexSet::Route::State::DROPPED;
if (target.calls)
{
ResetEvent(target.idle);

View File

@@ -75,6 +75,7 @@ private:
uint32_t epoch = 0;
FrameCfg cfg;
CfgResult result = CfgResult::ACCEPTED;
uint64_t retryAt = 0;
};
struct Failure
@@ -125,6 +126,9 @@ private:
CfgResult Faulted(BackendId id, uint32_t epoch,
const FrameCfg& cfg) const;
bool Active(uint64_t generation, BackendId id, uint32_t epoch,
const FrameCfg& cfg) const;
void Retry(uint64_t now);
bool TakeFailure(BackendId& id, uint32_t& epoch);
void Rebind(BackendId id, uint32_t epoch);

View File

@@ -31,7 +31,9 @@
#include <utility>
static const uint64_t RETRY_DELAY_MS = 500;
static const uint64_t FRAME_RETRY_DELAY_MS = 500;
static const uint64_t SERVICE_RETRY_DELAY_MS = 250;
static std::atomic<uint64_t> s_graphGeneration = 0;
class CSourceEvents final : public ITransportEvents
{
@@ -135,9 +137,25 @@ void CTransportManager::BumpFrameRev()
Atomic::Next(m_frameRev, std::memory_order_release);
}
uint64_t CTransportManager::NextGraph()
void CTransportManager::ScheduleFrameRetry(Entry& entry)
{
return Seq::Inc(m_graphSerial);
CSRWExclusiveLock entryLock(entry.lock);
if (!entry.frameRetryAt)
entry.frameRetryAt = GetTickCount64() + FRAME_RETRY_DELAY_MS;
}
void CTransportManager::RetryFrame(Entry& entry, uint64_t now)
{
bool retry = false;
{
CSRWExclusiveLock entryLock(entry.lock);
if (!entry.frameRetryAt || now < entry.frameRetryAt)
return;
entry.frameRetryAt = 0;
retry = entry.state == State::READY && entry.frameAdded;
}
if (retry)
BumpFrameRev();
}
CTransportManager::~CTransportManager()
@@ -771,6 +789,7 @@ void CTransportManager::RemoveServices(Entry& entry)
entry.texSink = nullptr;
entry.controlAdded = false;
entry.inputAdded = false;
entry.frameRetryAt = 0;
}
m_input.RevokeInteraction(id, epoch);
@@ -1105,6 +1124,7 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
bool required = false;
bool primary = false;
bool prepared = false;
bool selected = false;
bool eligible = false;
ITexSink * texSink = nullptr;
FrameProfile profiles[FRAME_PROFILE_MAX] = {};
@@ -1238,9 +1258,30 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
}
}
const bool tex = route.texSink != nullptr;
const GraphLeaf * previous = graph.FindLeaf(
route.id, route.epoch, tex, candidate);
if (previous && previous->required == route.required &&
previous->primary == route.primary &&
(!tex || m_tex.Active(graph.Generation(),
route.id, route.epoch, candidate)))
{
if (!next.Add(route.id, route.epoch, route.required,
route.primary, tex, graph.Same(cfg), candidate))
{
routeResult = CfgResult::FAILED;
break;
}
routeResult = CfgResult::ACCEPTED;
route.selected = true;
break;
}
routeResult = route.transport->Probe(candidate);
if (routeResult == CfgResult::NEXT)
continue;
if (routeResult == CfgResult::REJECTED)
ScheduleFrameRetry(*route.entry);
if (routeResult != CfgResult::ACCEPTED)
break;
@@ -1250,21 +1291,24 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
route.transport->Abort();
continue;
}
if (routeResult == CfgResult::REJECTED)
ScheduleFrameRetry(*route.entry);
if (routeResult != CfgResult::ACCEPTED)
break;
if (!next.Add(route.id, route.epoch, route.required,
route.primary, route.texSink != nullptr, candidate))
route.primary, tex, false, candidate))
{
route.transport->Abort();
routeResult = CfgResult::FAILED;
break;
}
route.prepared = true;
route.selected = true;
break;
}
if (route.prepared)
if (route.selected)
continue;
route.transport->Abort();
@@ -1285,14 +1329,14 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
result = CfgResult::FAILED;
if (result == CfgResult::ACCEPTED &&
!next.Stamp(NextGraph()))
!next.Stamp(Atomic::Next(s_graphGeneration)))
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)
if (routes[i].selected)
{
CTexHub::Bind& bind = binds[bindCount++];
bind.owner = routes[i].transport;
@@ -1315,8 +1359,15 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
if (result == CfgResult::ACCEPTED)
{
for (unsigned i = 0; i < routeCount; ++i)
{
if (routes[i].prepared)
routes[i].transport->Commit();
if (routes[i].selected)
{
CSRWExclusiveLock entryLock(routes[i].entry->lock);
routes[i].entry->frameRetryAt = 0;
}
}
graph = next;
m_tex.Commit(texStage);
if (activationReady)
@@ -1360,12 +1411,14 @@ ITransport::ProcessResult CTransportManager::Process(
const uint64_t now = GetTickCount64();
m_recovery.Tick(now);
m_tex.Retry(now);
HandleServiceFailures();
Entry * entries[FRAME_MAX_SINKS] = {};
const unsigned count = Entries(entries);
for (unsigned i = 0; i < count; ++i)
{
Entry& entry = *entries[i];
RetryFrame(entry, now);
if (!BeginCall(entry, Call::PROCESS, false))
continue;

View File

@@ -113,6 +113,7 @@ private:
bool frameLegacy = false;
ITexSink * texSink = nullptr;
bool frameAbsent = false;
uint64_t frameRetryAt = 0;
uint64_t serviceRetryAt = 0;
bool exposed = false;
bool setupDone = false;
@@ -143,7 +144,6 @@ 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;
@@ -169,7 +169,8 @@ private:
void RemoveServices(Entry& entry);
void Expose(Entry& entry);
void BumpFrameRev();
uint64_t NextGraph();
void ScheduleFrameRetry(Entry& entry);
void RetryFrame(Entry& entry, uint64_t now);
public:
CTransportManager();