[idd] capture: execute texture frame graphs

This commit is contained in:
Geoffrey McRae
2026-08-13 21:56:41 +10:00
parent deb963774e
commit 70bc66b3f1
25 changed files with 1484 additions and 111 deletions

View File

@@ -0,0 +1,773 @@
/**
* 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 "capture/CFrameExec.h"
#include "d3d/CD3D11Device.h"
#include "d3d/CD3D12Device.h"
#include "d3d/CInteropPool.h"
#include "d3d/CInteropResource.h"
#include "postprocess/effect/CColorTransformEffect.h"
#include "postprocess/effect/CDownsampleEffect.h"
#include "postprocess/effect/CFormatEffect.h"
#include "postprocess/effect/CHDR16to10Effect.h"
#include "transport/CTexHub.h"
#include <cstring>
#include <new>
#include <utility>
namespace
{
static const unsigned EXEC_LANES = 2;
static const unsigned EXEC_SLOTS = 4;
enum class ExecOp : uint8_t
{
COPY,
CAL,
LUT,
SCALE,
HDR10,
FORMAT,
};
D12FrameFormat MakeFormat(const FrameProfile& profile,
unsigned width, unsigned height,
const std::shared_ptr<const D12ColorTransform>& transform,
D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE)
{
D12FrameFormat format = {};
D12::Set(format,
Frame::Store(profile, FrameStorage::D3D12_TEXTURE),
width, height, flags);
format.colorTransform = transform;
return format;
}
bool Copy(ID3D12GraphicsCommandList * list,
ID3D12Resource * src, ID3D12Resource * dst)
{
if (!list || !src || !dst || src == dst)
return false;
const D3D12_RESOURCE_DESC in = src->GetDesc();
const D3D12_RESOURCE_DESC out = dst->GetDesc();
if (!D12::Same(in, out, D12::DescCmp::COPY))
return false;
// Leave the immutable input in COMMON. COPY_SOURCE is promoted
// implicitly, allowing the legacy and texture queues to read it without
// cross-queue state ownership. Only this private destination transitions.
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = dst;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
list->ResourceBarrier(1, &barrier);
list->CopyResource(dst, src);
std::swap(barrier.Transition.StateBefore,
barrier.Transition.StateAfter);
list->ResourceBarrier(1, &barrier);
return true;
}
CfgResult CfgFromTex(TexResult result)
{
switch (result)
{
case TexResult::OK:
return CfgResult::ACCEPTED;
case TexResult::BUSY:
return CfgResult::RETRY;
case TexResult::REJECTED:
return CfgResult::REJECTED;
case TexResult::FAILED:
return CfgResult::FAILED;
}
return CfgResult::FAILED;
}
bool Supports(ID3D12Device3 * device, ExecOp op,
DXGI_FORMAT src, DXGI_FORMAT dst)
{
if (op == ExecOp::COPY)
return true;
D3D12_FEATURE_DATA_FORMAT_SUPPORT input = { src };
D3D12_FEATURE_DATA_FORMAT_SUPPORT output = { dst };
if (!device || FAILED(device->CheckFeatureSupport(
D3D12_FEATURE_FORMAT_SUPPORT, &input, sizeof(input))) ||
FAILED(device->CheckFeatureSupport(
D3D12_FEATURE_FORMAT_SUPPORT, &output, sizeof(output))))
return false;
const D3D12_FORMAT_SUPPORT1 read = op == ExecOp::SCALE ?
D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE :
D3D12_FORMAT_SUPPORT1_SHADER_LOAD;
return (input.Support1 & read) == read &&
(output.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE) != 0;
}
}
struct CFrameExec::Core : std::enable_shared_from_this<CFrameExec::Core>
{
struct Fx
{
ExecOp op = ExecOp::COPY;
ComPtr<ID3D12Resource> scratch;
std::unique_ptr<CColorTransformEffect> color;
std::unique_ptr<CDownsampleEffect> scale;
std::unique_ptr<CHDR16to10Effect> hdr;
std::unique_ptr<CFormatEffect> convert;
bool Init(const ComPtr<ID3D12Device3>& device, ExecOp selected,
const D12FrameFormat& src, const D12FrameFormat& dst, bool exported)
{
op = selected;
PostProcessStatus status = PostProcessStatus::SUCCESS;
D12FrameFormat output = src;
output.desc.Flags = dst.desc.Flags;
switch (op)
{
case ExecOp::COPY:
if (src.desc.Width != dst.desc.Width ||
src.desc.Height != dst.desc.Height ||
src.desc.Format != dst.desc.Format)
return false;
break;
case ExecOp::CAL:
color.reset(new (std::nothrow)
CColorTransformEffect(CalPart::MATRIX, true));
if (!color || !color->Init(device))
return false;
status = color->Cfg(device, src, output);
break;
case ExecOp::LUT:
color.reset(new (std::nothrow)
CColorTransformEffect(CalPart::LUT, true));
if (!color || !color->Init(device))
return false;
status = color->Cfg(device, src, output);
break;
case ExecOp::SCALE:
scale.reset(new (std::nothrow)
CDownsampleEffect(dst.width, dst.height));
if (!scale || !scale->Init(device))
return false;
status = scale->Cfg(device, src, output);
break;
case ExecOp::HDR10:
hdr.reset(new (std::nothrow) CHDR16to10Effect);
if (!hdr || !hdr->Init(device))
return false;
status = hdr->Cfg(device, src, output);
break;
case ExecOp::FORMAT:
convert.reset(new (std::nothrow) CFormatEffect);
if (!convert || !convert->Init(device))
return false;
status = convert->Cfg(device, src, dst);
output = dst;
break;
}
if (status != PostProcessStatus::SUCCESS ||
!D12::Same(output, dst, D12::FormatCmp::IMAGE))
return false;
if (!exported && !PostProcessUtil::CreateDefaultTexture(
device, dst.desc, scratch))
return false;
return true;
}
bool Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& list,
ID3D12Resource * src, ID3D12Resource * dst)
{
if (!src || !dst)
return false;
if (op == ExecOp::COPY)
return Copy(list.Get(), src, dst);
ComPtr<ID3D12Resource> input = src;
RECT rect = {};
unsigned count = 0;
switch (op)
{
case ExecOp::CAL:
case ExecOp::LUT:
return color && color->Run(
device, list, input, dst, &rect, &count);
case ExecOp::SCALE:
return scale && scale->Run(
device, list, input, dst, &rect, &count);
case ExecOp::HDR10:
return hdr && hdr->Run(
device, list, input, dst, &rect, &count);
case ExecOp::FORMAT:
return convert && convert->Run(
device, list, input, dst, &rect, &count);
case ExecOp::COPY:
break;
}
return false;
}
};
struct Lane
{
Fx nodes[FRAME_GRAPH_MAX_NODES];
};
CSRWLock runLock;
std::shared_ptr<CD3D11Device> d11;
std::shared_ptr<CD3D12Device> d12;
ComPtr<ID3D12Device3> device;
CTexHub * hub = nullptr;
CFrameGraph graph;
CD3D12CommandQueue queue;
CTexPool pools[FRAME_GRAPH_MAX_NODES];
CInteropPool interop[FRAME_GRAPH_MAX_NODES];
Lane lanes[EXEC_LANES];
D12FrameFormat formats[FRAME_GRAPH_MAX_NODES] = {};
ExecOp ops[FRAME_GRAPH_MAX_NODES] = {};
bool exported[FRAME_GRAPH_MAX_NODES] = {};
bool d11Node[FRAME_GRAPH_MAX_NODES] = {};
unsigned readers[FRAME_GRAPH_MAX_NODES] = {};
unsigned nodeCount = 0;
unsigned leafCount = 0;
unsigned nextLane = 0;
bool live = false;
std::atomic<bool> forceFull { true };
~Core()
{
// Effect descriptor heaps, scratch textures, and pool destinations are
// referenced by submitted lists. Drain before member destruction.
queue.WaitForIdle();
}
static bool Select(const GraphNode& parent,
const GraphNode& node, ExecOp& op)
{
switch (node.op)
{
case FrameOp::CAL:
op = ExecOp::CAL;
return true;
case FrameOp::LUT:
op = ExecOp::LUT;
return true;
case FrameOp::SCALE:
op = ExecOp::SCALE;
return true;
case FrameOp::HDR10:
if (parent.profile.signal == FrameSignal::SCRGB_LINEAR &&
node.profile.signal == FrameSignal::PQ_BT2020)
{
op = ExecOp::HDR10;
return true;
}
break;
case FrameOp::SDR:
case FrameOp::SCRGB:
break;
case FrameOp::SRC:
return false;
}
if (parent.width == node.width && parent.height == node.height &&
Frame::Same(parent.profile, node.profile))
{
op = ExecOp::COPY;
return true;
}
if (parent.width == node.width && parent.height == node.height &&
parent.profile.signal == FrameSignal::SRGB &&
node.profile.signal == FrameSignal::SRGB &&
parent.profile.pixel != node.profile.pixel)
{
op = ExecOp::FORMAT;
return true;
}
return false;
}
CfgResult Init(const CFrameGraph& source,
const std::shared_ptr<CD3D11Device>& d11Device,
const std::shared_ptr<CD3D12Device>& d12Device, CTexHub& texHub)
{
d11 = d11Device;
d12 = d12Device;
hub = &texHub;
graph = source;
device = d12 ? d12->GetDevice() : nullptr;
if (!d11 || !d12 || !device || !graph.Generation())
return CfgResult::FAILED;
const GraphNode * nodes = graph.Nodes(nodeCount);
const GraphLeaf * leaves = graph.Leaves(leafCount);
if (!nodes || !leaves || !nodeCount ||
nodeCount > FRAME_GRAPH_MAX_NODES ||
leafCount > TRANSPORT_MAX_INSTANCES)
return CfgResult::REJECTED;
unsigned texLeaves = 0;
for (unsigned leaf = 0; leaf < leafCount; ++leaf)
if (leaves[leaf].tex)
{
if (!leaves[leaf].node || leaves[leaf].node >= nodeCount)
return CfgResult::REJECTED;
exported[leaves[leaf].node] = true;
++readers[leaves[leaf].node];
d11Node[leaves[leaf].node] |=
leaves[leaf].cfg.profile.storage ==
FrameStorage::D3D11_TEXTURE;
++texLeaves;
}
if (!texLeaves)
return CfgResult::ACCEPTED;
if (!nodes[0].texRefs || !queue.Init(device.Get(),
D3D12_COMMAND_LIST_TYPE_DIRECT, L"Frame Graph",
CD3D12CommandSlot::FAST, EXEC_LANES, false, true))
return CfgResult::FAILED;
const GraphCfg& cfg = graph.Cfg();
formats[0] = MakeFormat(cfg.src, cfg.srcWidth, cfg.srcHeight,
cfg.transform);
for (unsigned node = 1; node < nodeCount; ++node)
{
if (!nodes[node].texRefs)
continue;
if (nodes[node].texRefs > nodes[node].refs ||
nodes[node].parent >= node ||
!nodes[nodes[node].parent].texRefs ||
!Select(nodes[nodes[node].parent], nodes[node], ops[node]))
return CfgResult::REJECTED;
D3D12_RESOURCE_FLAGS flags = ops[node] == ExecOp::COPY &&
exported[node] ?
D3D12_RESOURCE_FLAG_NONE :
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (exported[node] && readers[node] > 1)
flags |= D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS;
formats[node] = MakeFormat(nodes[node].profile,
nodes[node].width, nodes[node].height, cfg.transform, flags);
if (!Supports(device.Get(), ops[node],
formats[nodes[node].parent].desc.Format,
formats[node].desc.Format))
return CfgResult::REJECTED;
if (exported[node])
{
const FrameProfile profile = Frame::Store(
nodes[node].profile, FrameStorage::D3D12_TEXTURE);
const TexResult result = pools[node].Init(device.Get(),
graph.Generation(), node, profile, formats[node].desc,
D3D12_RESOURCE_STATE_COMMON, EXEC_SLOTS, graph.Shared(node));
if (result != TexResult::OK)
return CfgFromTex(result);
if (d11Node[node])
{
const TexResult interopResult = interop[node].Init(d11, d12);
if (interopResult != TexResult::OK)
return CfgFromTex(interopResult);
}
}
}
for (unsigned lane = 0; lane < EXEC_LANES; ++lane)
for (unsigned node = 1; node < nodeCount; ++node)
if (nodes[node].texRefs && !lanes[lane].nodes[node].Init(
device, ops[node], formats[nodes[node].parent], formats[node],
exported[node]))
return CfgResult::REJECTED;
live = true;
return CfgResult::ACCEPTED;
}
FrameContentRef Content(uint64_t serial, const D12FrameFormat& format,
uint64_t captureTime, bool full, FrameDamage damage,
const RECT * rects, unsigned count)
{
if (!serial || count > FRAME_DAMAGE_MAX ||
(count && !rects) ||
(damage == FrameDamage::RECTS) != (count != 0))
return {};
FrameContent * raw = new (std::nothrow) FrameContent;
if (!raw)
return {};
raw->serial = serial;
raw->captureTime = captureTime;
raw->damage = full ? FrameDamage::FULL : damage;
raw->count = raw->damage == FrameDamage::RECTS ? count : 0;
if (raw->count)
memcpy(raw->rects, rects, raw->count * sizeof(*rects));
raw->format = format;
try
{
return FrameContentRef(raw);
}
catch (const std::bad_alloc&)
{
return {};
}
}
TexResult Run(CInteropResource& src, const D12FrameFormat& sourceFormat,
uint64_t serial, uint64_t captureTime, uint64_t postStart,
FrameDamage damage, const RECT * rects, unsigned count) noexcept
{
if (!live)
return TexResult::OK;
CSRWExclusiveLock run = CSRWExclusiveLock::Try(runLock);
if (!run)
{
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::BUSY;
}
const bool full = Atomic::Swap(forceFull, false, std::memory_order_acq_rel);
ID3D12Resource * source = src.GetRes().Get();
if (!source)
{
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::REJECTED;
}
const D3D12_RESOURCE_DESC actual = source->GetDesc();
if (!D12::Same(actual, sourceFormat.desc, D12::DescCmp::COPY))
{
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::REJECTED;
}
const FrameContentRef content = Content(
serial, sourceFormat, captureTime, full, damage, rects, count);
FrameDesc check;
unsigned first = 0;
while (++first < nodeCount && !exported[first]) {}
if (!content || first == nodeCount ||
!graph.Desc(first, content, check))
{
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::REJECTED;
}
CD3D12CommandSlot * slot = nullptr;
unsigned lane = 0;
for (unsigned attempt = 0; attempt < EXEC_LANES; ++attempt)
{
lane = (nextLane + attempt) % EXEC_LANES;
slot = queue.Try(lane);
if (slot)
{
nextLane = (lane + 1) % EXEC_LANES;
break;
}
}
if (!slot)
{
Atomic::Store(forceFull, true, std::memory_order_release);
return queue.Failed() ? TexResult::FAILED : TexResult::BUSY;
}
TexWrite writes[FRAME_GRAPH_MAX_NODES];
const GraphNode * nodes = graph.Nodes(nodeCount);
TexResult result = TexResult::OK;
for (unsigned node = 1; node < nodeCount; ++node)
if (exported[node])
{
result = pools[node].Try(writes[node]);
if (result != TexResult::OK)
break;
}
if (result != TexResult::OK)
{
for (unsigned node = 1; node < nodeCount; ++node)
writes[node].Cancel();
slot->Cancel();
Atomic::Store(forceFull, true, std::memory_order_release);
return result;
}
if (!src.Signal() || !src.Sync(*slot))
{
for (unsigned node = 1; node < nodeCount; ++node)
writes[node].Cancel();
slot->Cancel();
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::FAILED;
}
ComPtr<ID3D12GraphicsCommandList> list = slot->GetGfxList();
ID3D12Resource * outputs[FRAME_GRAPH_MAX_NODES] = {};
outputs[0] = source;
bool recorded = !!list && !!outputs[0];
for (unsigned node = 1; recorded && node < nodeCount; ++node)
if (nodes[node].texRefs)
{
ID3D12Resource * output = exported[node] ?
writes[node].Get() : lanes[lane].nodes[node].scratch.Get();
recorded = output && lanes[lane].nodes[node].Run(
device, list, outputs[nodes[node].parent], output);
outputs[node] = output;
}
if (!recorded)
{
for (unsigned node = 1; node < nodeCount; ++node)
writes[node].Cancel();
slot->Cancel();
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::FAILED;
}
D12Sync sync;
if (!slot->Execute(&sync))
{
const bool submitted = slot->HasSubmittedWork();
for (unsigned node = 1; node < nodeCount; ++node)
if (submitted)
writes[node].Fail(sync);
else
writes[node].Cancel();
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::FAILED;
}
const FrameTime time = { postStart, 0, 0, 0, false, false };
TexLease leases[FRAME_GRAPH_MAX_NODES];
const std::shared_ptr<void> hold = shared_from_this();
bool sealed = true;
for (unsigned node = 1; node < nodeCount; ++node)
if (exported[node])
{
FrameDesc desc;
if (!graph.Desc(node, content, desc) ||
!writes[node].Seal(desc, time, sync, leases[node], hold))
{
sealed = false;
break;
}
}
if (!sealed)
{
for (unsigned node = 1; node < nodeCount; ++node)
{
if (writes[node])
writes[node].Fail(sync);
leases[node].Reset();
}
Atomic::Store(forceFull, true, std::memory_order_release);
return TexResult::FAILED;
}
bool dropped = false;
bool failed = false;
const GraphLeaf * leaves = graph.Leaves(leafCount);
for (unsigned leaf = 0; leaf < leafCount; ++leaf)
{
if (!leaves[leaf].tex)
continue;
FrameIn frame;
frame.graph = graph.Generation();
if (!graph.Desc(leaf, content, frame.desc))
{
dropped = true;
continue;
}
PushResult pushed = PushResult::STALE;
const unsigned node = leaves[leaf].node;
if (leaves[leaf].cfg.profile.storage ==
FrameStorage::D3D12_TEXTURE)
{
pushed = hub->Push(frame, leases[node]);
}
else
{
D11Lease lease;
const TexResult opened = interop[node].Get(leases[node], lease);
if (opened == TexResult::OK)
pushed = hub->Push(frame, std::move(lease));
else if (opened == TexResult::BUSY)
pushed = PushResult::BUSY;
else
{
pushed = opened == TexResult::REJECTED ?
PushResult::REJECTED : PushResult::FAILED;
hub->Fault(frame, pushed);
}
}
if (pushed == PushResult::BUSY || pushed == PushResult::STALE)
dropped = true;
else if (pushed == PushResult::FAILED)
failed = true;
}
if (dropped)
Atomic::Store(forceFull, true, std::memory_order_release);
if (failed)
return TexResult::FAILED;
return dropped ? TexResult::BUSY : TexResult::OK;
}
};
CFrameExec::~CFrameExec()
{
Reset();
}
bool CFrameExec::Init(const std::shared_ptr<CD3D11Device>& d11,
const std::shared_ptr<CD3D12Device>& d12, CTexHub& hub)
{
if (!d11 || !d12 || !d11->GetDevice() || !d12->GetDevice())
return false;
CSRWExclusiveLock lock(m_lock);
if (m_d11 || m_d12 || m_hub)
return false;
m_d11 = d11;
m_d12 = d12;
m_hub = &hub;
return true;
}
void CFrameExec::Reset()
{
std::shared_ptr<Core> active;
std::shared_ptr<Core> pending;
{
CSRWExclusiveLock lock(m_lock);
active = std::move(m_active);
pending = std::move(m_pending);
m_hub = nullptr;
m_d12.reset();
m_d11.reset();
}
pending.reset();
active.reset();
}
uint64_t CFrameExec::NextSerial()
{
return Atomic::Next(m_serial);
}
CfgResult CFrameExec::Prep(const CFrameGraph& graph) noexcept
{
std::shared_ptr<CD3D11Device> d11;
std::shared_ptr<CD3D12Device> d12;
CTexHub * hub = nullptr;
{
CSRWSharedLock lock(m_lock);
if (m_pending)
return CfgResult::REJECTED;
d11 = m_d11;
d12 = m_d12;
hub = m_hub;
}
if (!d11 || !d12 || !hub)
return CfgResult::FAILED;
std::shared_ptr<Core> next;
try
{
next.reset(new (std::nothrow) Core);
}
catch (const std::bad_alloc&)
{
return CfgResult::FAILED;
}
if (!next)
return CfgResult::FAILED;
CfgResult result = CfgResult::FAILED;
try
{
result = next->Init(graph, d11, d12, *hub);
}
catch (...)
{
return CfgResult::FAILED;
}
if (result != CfgResult::ACCEPTED)
return result;
{
CSRWExclusiveLock lock(m_lock);
if (m_pending || m_d11 != d11 || m_d12 != d12 || m_hub != hub)
return CfgResult::RETRY;
m_pending = next;
}
return CfgResult::ACCEPTED;
}
void CFrameExec::Commit() noexcept
{
std::shared_ptr<Core> old;
{
CSRWExclusiveLock lock(m_lock);
if (!m_pending)
return;
old = std::move(m_active);
m_active = std::move(m_pending);
}
old.reset();
}
void CFrameExec::Abort() noexcept
{
std::shared_ptr<Core> drop;
{
CSRWExclusiveLock lock(m_lock);
drop = std::move(m_pending);
}
drop.reset();
}
TexResult CFrameExec::Run(CInteropResource& src,
const D12FrameFormat& format, uint64_t captureTime, uint64_t postStart,
FrameDamage damage, const RECT * rects, unsigned count) noexcept
{
std::shared_ptr<Core> core;
{
CSRWSharedLock lock(m_lock);
core = m_active;
}
if (!core || !core->live)
return TexResult::OK;
return core->Run(src, format, NextSerial(), captureTime, postStart,
damage, rects, count);
}

View File

@@ -0,0 +1,71 @@
/**
* 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 "capture/CFrameGraph.h"
#include "capture/CFrameTex.h"
#include "transport/ITexStage.h"
#include <memory>
class CInteropResource;
class CTexHub;
struct CD3D11Device;
struct CD3D12Device;
// Transactional executor for texture-backed transport routes. Every product
// is materialized in executor-owned storage before the acquired IddCx surface
// is released. A graph containing only legacy routes remains fully dormant.
class CFrameExec final : public ITexStage
{
private:
struct Core;
mutable CSRWLock m_lock;
std::shared_ptr<CD3D11Device> m_d11;
std::shared_ptr<CD3D12Device> m_d12;
CTexHub * m_hub = nullptr;
std::shared_ptr<Core> m_active;
std::shared_ptr<Core> m_pending;
std::atomic<uint64_t> m_serial { 0 };
uint64_t NextSerial();
public:
CFrameExec() = default;
~CFrameExec();
CFrameExec(const CFrameExec&) = delete;
CFrameExec& operator=(const CFrameExec&) = delete;
bool Init(const std::shared_ptr<CD3D11Device>& d11,
const std::shared_ptr<CD3D12Device>& d12, CTexHub& hub);
void Reset();
CfgResult Prep(const CFrameGraph& graph) noexcept override;
void Commit() noexcept override;
void Abort() noexcept override;
TexResult Run(CInteropResource& src, const D12FrameFormat& format,
uint64_t captureTime, uint64_t postStart, FrameDamage damage,
const RECT * rects, unsigned count) noexcept;
};

View File

@@ -241,7 +241,7 @@ unsigned CFrameGraph::Checkpoint(const FrameProfile& requested)
}
bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
bool primary, const FrameCfg& cfg)
bool primary, bool tex, const FrameCfg& cfg)
{
if (!id || !epoch || !Can(cfg) ||
m_leafCount == TRANSPORT_MAX_INSTANCES)
@@ -266,11 +266,16 @@ bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
leaf.node = node;
leaf.required = required;
leaf.primary = primary;
leaf.tex = tex;
leaf.cfg = cfg;
for (unsigned current = node;
current != FRAME_GRAPH_ROOT; current = m_nodes[current].parent)
{
++m_nodes[current].refs;
if (tex)
++m_nodes[current].texRefs;
}
return true;
}
@@ -284,6 +289,9 @@ bool CFrameGraph::Seal()
if (!m_leaves[i].id || !m_leaves[i].epoch ||
!m_leaves[i].node || m_leaves[i].node >= m_nodeCount)
return false;
for (unsigned i = 0; i < m_nodeCount; ++i)
if (!m_nodes[i].refs || m_nodes[i].texRefs > m_nodes[i].refs)
return false;
m_sealed = true;
return true;
@@ -324,17 +332,19 @@ bool CFrameGraph::Want(FrameSignal signal) const
bool CFrameGraph::Shared(unsigned node) const
{
if (!m_sealed || !node || node >= m_nodeCount)
if (!m_sealed || !node || node >= m_nodeCount ||
!m_nodes[node].texRefs)
return false;
for (unsigned i = 0; i < m_leafCount; ++i)
if (m_leaves[i].node == node &&
if (m_leaves[i].tex &&
m_leaves[i].node == node &&
m_leaves[i].cfg.profile.storage == FrameStorage::D3D11_TEXTURE)
return true;
return false;
}
bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const
bool CFrameGraph::Desc(unsigned nodeIndex,
const FrameContentRef& content, FrameDesc& desc) const
{
if (!content)
return false;
@@ -343,59 +353,78 @@ bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
FrameProfile sourceProfile;
const bool validProfile = D12::Profile(source,
FrameStorage::D3D12_TEXTURE, sourceProfile);
const auto effectiveTransform =
D12::Transform(source.colorTransform);
if (!validProfile || !Frame::Same(sourceProfile, m_cfg.src))
return false;
if (!m_sealed ||
leaf >= m_leafCount ||
!content->serial ||
source.width != m_cfg.srcWidth ||
source.height != m_cfg.srcHeight ||
source.dataWidth != m_cfg.srcWidth ||
source.dataHeight != m_cfg.srcHeight ||
source.pitch != 0 ||
if (!m_sealed ||
!nodeIndex ||
nodeIndex >= m_nodeCount ||
!content->serial ||
source.width != m_cfg.srcWidth ||
source.height != m_cfg.srcHeight ||
source.dataWidth != m_cfg.srcWidth ||
source.dataHeight != m_cfg.srcHeight ||
source.pitch != 0 ||
source.desc.Dimension !=
D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
source.desc.Width != m_cfg.srcWidth ||
source.desc.Height != m_cfg.srcHeight ||
source.desc.DepthOrArraySize != 1 ||
source.desc.MipLevels != 1 ||
source.desc.SampleDesc.Count != 1 ||
D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
source.desc.Width != m_cfg.srcWidth ||
source.desc.Height != m_cfg.srcHeight ||
source.desc.DepthOrArraySize != 1 ||
source.desc.MipLevels != 1 ||
source.desc.SampleDesc.Count != 1 ||
effectiveTransform != m_cfg.transform ||
!Frame::Valid(content->damage, content->rects, content->count,
m_cfg.srcWidth, m_cfg.srcHeight))
return false;
const GraphLeaf& route = m_leaves[leaf];
const GraphNode& node = m_nodes[route.node];
desc.id = route.id;
desc.epoch = route.epoch;
desc.node = route.node;
desc.profile = route.cfg.profile;
desc.frame = FrameDesc {};
desc.frame.content = content;
desc.frame.damage = content->damage;
desc.frame.count = content->count;
const GraphNode& node = m_nodes[nodeIndex];
FrameDesc result;
result.content = content;
result.damage = content->damage;
result.count = content->count;
if (content->count)
memcpy(desc.frame.rects, content->rects,
memcpy(result.rects, content->rects,
content->count * sizeof(*content->rects));
desc.frame.format = content->format;
result.format = content->format;
// A scaled checkpoint has a different damage coordinate space. Until the
// executor owns exact edge transforms, preserve correctness by making any
// partial source damage a full node update.
if (desc.frame.damage == FrameDamage::RECTS &&
if (result.damage == FrameDamage::RECTS &&
(node.width != m_cfg.srcWidth || node.height != m_cfg.srcHeight))
{
desc.frame.damage = FrameDamage::FULL;
desc.frame.count = 0;
result.damage = FrameDamage::FULL;
result.count = 0;
}
D12FrameFormat& format = desc.frame.format;
if (!D12::Set(format, node.profile, node.width, node.height))
D12FrameFormat& output = result.format;
if (!D12::Set(output, node.profile, node.width, node.height))
return false;
// Calibration/LUT nodes have already consumed this transform.
format.colorTransform.reset();
output.colorTransform.reset();
desc = result;
return true;
}
bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const
{
if (!m_sealed || leaf >= m_leafCount)
return false;
const GraphLeaf& route = m_leaves[leaf];
LeafDesc result;
if (!Desc(route.node, content, result.frame))
return false;
result.id = route.id;
result.epoch = route.epoch;
result.node = route.node;
result.profile = route.cfg.profile;
desc = result;
return true;
}

View File

@@ -75,7 +75,9 @@ struct GraphNode
{
FrameOp op = FrameOp::SRC;
unsigned parent = FRAME_GRAPH_ROOT;
// refs covers every route; texRefs is the direct texture-route closure.
unsigned refs = 0;
unsigned texRefs = 0;
unsigned width = 0;
unsigned height = 0;
FrameProfile profile;
@@ -88,6 +90,7 @@ struct GraphLeaf
unsigned node = 0;
bool required = false;
bool primary = false;
bool tex = false;
FrameCfg cfg;
};
@@ -149,13 +152,15 @@ public:
bool Begin(const GraphCfg& cfg);
bool Can(const FrameCfg& cfg) const;
bool Add(BackendId id, uint32_t epoch, bool required, bool primary,
const FrameCfg& cfg);
bool tex, 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;
bool Shared(unsigned node) const;
bool Desc(unsigned node, const FrameContentRef& content,
FrameDesc& desc) const;
bool Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const;

View File

@@ -297,11 +297,13 @@ CFrameTex::CFrameTex(uint64_t graphValue, uint64_t poolValue,
const FrameProfile& profileValue,
const FrameDesc& frameValue, const FrameTime& timeValue,
const ComPtr<ID3D12Resource>& res,
const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue) :
const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue,
const std::shared_ptr<void>& hold) :
m_res(res),
m_sync(sync),
m_state(state),
m_shared(sharedValue),
m_hold(hold),
graph(graphValue),
pool(poolValue),
node(nodeValue),
@@ -378,7 +380,8 @@ ID3D12Resource * TexWrite::Get() const
}
bool TexWrite::Seal(const FrameDesc& frame, const FrameTime& time,
const D12Sync& sync, TexLease& lease)
const D12Sync& sync, TexLease& lease,
const std::shared_ptr<void>& hold)
{
if (!m_core || !sync.Valid())
return false;
@@ -404,7 +407,7 @@ bool TexWrite::Seal(const FrameDesc& frame, const FrameTime& time,
// pool descriptor, including producer-only UAV creation capability.
desc.format.desc = resource->GetDesc();
const unsigned index = m_index;
const unsigned index = m_index;
const uint64_t generation = m_version;
if (!core->Seal(index, generation, sync))
{
@@ -415,7 +418,7 @@ bool TexWrite::Seal(const FrameDesc& frame, const FrameTime& time,
CFrameTex * raw = new (std::nothrow) CFrameTex(core->graph, core->id,
core->node, m_index, m_version, core->profile, desc, time, resource,
sync, core->read, core->shared);
sync, core->read, core->shared, hold);
if (!raw)
{
core->Drop(index, generation);
@@ -445,6 +448,13 @@ bool TexWrite::Seal(const FrameDesc& frame, const FrameTime& time,
return true;
}
void TexWrite::Fail(const D12Sync& sync)
{
if (m_core)
m_core->Fail(m_index, m_version, sync);
Clear();
}
void TexWrite::Cancel()
{
if (m_core)

View File

@@ -59,13 +59,15 @@ private:
ComPtr<ID3D12Resource> m_res;
D12Sync m_sync;
D3D12_RESOURCE_STATES m_state;
bool m_shared;
bool m_shared;
std::shared_ptr<void> m_hold;
CFrameTex(uint64_t graph, uint64_t pool, unsigned node, unsigned slot,
uint64_t version, const FrameProfile& profile, const FrameDesc& frame,
const FrameTime& time, const ComPtr<ID3D12Resource>& res,
const D12Sync& sync,
D3D12_RESOURCE_STATES state, bool shared);
D3D12_RESOURCE_STATES state, bool shared,
const std::shared_ptr<void>& hold);
public:
CFrameTex(const CFrameTex&) = delete;
@@ -141,15 +143,17 @@ public:
// 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 FrameTime& time,
const D12Sync& sync, TexLease& lease);
const D12Sync& sync, TexLease& lease,
const std::shared_ptr<void>& hold = {});
void Fail(const D12Sync& sync);
void Cancel();
};
// One pool owns interchangeable textures for a single graph node. Reset stops
// acquisition and drains every sealed producer point. Existing writers and
// leases keep the retired core alive. Reset invalidates writers; a writer
// which already submitted must still Seal so its point is safely drained,
// but it will not publish a product from the retired generation.
// which already submitted must still Seal or Fail so its point is safely
// drained, but it will not publish a product from the retired generation.
// The configured state is the promised state presented to every consumer;
// it is not queried from D3D12. Consumers may read but never transition or
// write a shared product.

View File

@@ -161,6 +161,12 @@ bool CSwapChainProcessor::InitializePipeline()
return false;
m_resPool.Init(m_dx11Device, m_dx12Device);
if (!m_exec.Init(m_dx11Device, m_dx12Device,
m_devContext->GetTransport().Tex()))
{
DEBUG_ERROR("Failed to initialize the frame graph executor");
return false;
}
const bool enableEffects = !m_dx11Device->IsSoftware();
if (!enableEffects)
DEBUG_INFO("Software render adapter: post-processing disabled");
@@ -233,6 +239,7 @@ CSwapChainProcessor::~CSwapChainProcessor()
// Drain in-flight GPU work / completion callbacks before releasing the
// resources they reference. The swap chain was already released in the
// worker epilogue, so this does not hold an IddCx frame.
m_exec.Reset();
if (m_dx12Device)
{
m_dx12Device->WaitForIdle();
@@ -565,7 +572,7 @@ void CSwapChainProcessor::CfgGraph()
m_graphPending = false;
const CfgResult result =
transport.Cfg(m_graphCfg, m_graphRev, m_graph);
transport.Cfg(m_graphCfg, m_graphRev, m_graph, &m_exec);
if (result == CfgResult::ACCEPTED)
{
m_graphRetryAt = 0;
@@ -659,6 +666,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
D12FrameFormat srcFormat = {};
srcFormat.desc = srcDesc;
srcFormat.dataWidth = (unsigned)srcDesc.Width;
srcFormat.dataHeight = srcDesc.Height;
srcFormat.width = (unsigned)srcDesc.Width;
srcFormat.height = srcDesc.Height;
srcFormat.format = D12::Type(srcDesc.Format);
@@ -825,6 +834,22 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
m_transport.ForceFrame();
const FrameDamage texDamage = noImageUpdate ? FrameDamage::NONE :
(fullDamage || !resolvedDirtyRectCount ?
FrameDamage::FULL : FrameDamage::RECTS);
const unsigned texRectCount = texDamage == FrameDamage::RECTS ?
resolvedDirtyRectCount : 0;
// This call is a no-op for the current legacy-only LGMP graph. When a
// texture route is selected, every read of the borrowed source is submitted
// here before FinishedProcessingFrame releases the IddCx surface.
const TexResult texResult = m_exec.Run(*srcRes, srcFormat, captureTime,
postProcessStart, texDamage, dirtyRects, texRectCount);
if (texResult == TexResult::FAILED && m_haveGraphCfg)
{
m_graphPending = true;
m_graphRetryAt = CFrameScheduler::Nanotime() + GRAPH_RETRY_NS;
}
const FrameSubmission submission =
{
srcRes,

View File

@@ -25,6 +25,7 @@
#include "d3d/CD3D12Device.h"
#include "display/IddCxCompat.h"
#include "d3d/CInteropResourcePool.h"
#include "capture/CFrameExec.h"
#include "capture/CFrameGraph.h"
#include "capture/CFrameProcessor.h"
#include "postprocess/D12FrameFormat.h"
@@ -57,6 +58,7 @@ private:
HANDLE m_newFrameEvent;
CInteropResourcePool m_resPool;
CFrameExec m_exec;
CFrameGraph m_graph;
GraphCfg m_graphCfg;
bool m_haveGraphCfg = false;