diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj
index 1ec8e7c7..31c90a7e 100644
--- a/idd/LGIdd/LGIdd.vcxproj
+++ b/idd/LGIdd/LGIdd.vcxproj
@@ -43,6 +43,7 @@
+
@@ -62,6 +63,7 @@
+
@@ -99,6 +101,7 @@
+
@@ -119,6 +122,7 @@
+
diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters
index d3c4edde..de7ab098 100644
--- a/idd/LGIdd/LGIdd.vcxproj.filters
+++ b/idd/LGIdd/LGIdd.vcxproj.filters
@@ -97,6 +97,9 @@
Capture
+
+ Capture
+
Capture
@@ -157,6 +160,9 @@
Post-processing\Effects
+
+ Post-processing\Effects
+
Post-processing\Effects
@@ -294,6 +300,9 @@
Capture
+
+ Capture
+
Capture
@@ -351,6 +360,9 @@
Post-processing\Effects
+
+ Post-processing\Effects
+
Post-processing\Effects
diff --git a/idd/LGIdd/capture/CFrameExec.cpp b/idd/LGIdd/capture/CFrameExec.cpp
new file mode 100644
index 00000000..7ee77f3f
--- /dev/null
+++ b/idd/LGIdd/capture/CFrameExec.cpp
@@ -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
+#include
+#include
+
+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& 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
+{
+ struct Fx
+ {
+ ExecOp op = ExecOp::COPY;
+ ComPtr scratch;
+ std::unique_ptr color;
+ std::unique_ptr scale;
+ std::unique_ptr hdr;
+ std::unique_ptr convert;
+
+ bool Init(const ComPtr& 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& device,
+ const ComPtr& list,
+ ID3D12Resource * src, ID3D12Resource * dst)
+ {
+ if (!src || !dst)
+ return false;
+ if (op == ExecOp::COPY)
+ return Copy(list.Get(), src, dst);
+
+ ComPtr 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 d11;
+ std::shared_ptr d12;
+ ComPtr 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 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& d11Device,
+ const std::shared_ptr& 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 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 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& d11,
+ const std::shared_ptr& 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 active;
+ std::shared_ptr 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 d11;
+ std::shared_ptr 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 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 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 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;
+ {
+ 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);
+}
diff --git a/idd/LGIdd/capture/CFrameExec.h b/idd/LGIdd/capture/CFrameExec.h
new file mode 100644
index 00000000..677c932f
--- /dev/null
+++ b/idd/LGIdd/capture/CFrameExec.h
@@ -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
+
+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 m_d11;
+ std::shared_ptr m_d12;
+ CTexHub * m_hub = nullptr;
+ std::shared_ptr m_active;
+ std::shared_ptr m_pending;
+ std::atomic 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& d11,
+ const std::shared_ptr& 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;
+};
diff --git a/idd/LGIdd/capture/CFrameGraph.cpp b/idd/LGIdd/capture/CFrameGraph.cpp
index f5f7ad1d..0e06f0de 100644
--- a/idd/LGIdd/capture/CFrameGraph.cpp
+++ b/idd/LGIdd/capture/CFrameGraph.cpp
@@ -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;
}
diff --git a/idd/LGIdd/capture/CFrameGraph.h b/idd/LGIdd/capture/CFrameGraph.h
index b513d3de..7adb2567 100644
--- a/idd/LGIdd/capture/CFrameGraph.h
+++ b/idd/LGIdd/capture/CFrameGraph.h
@@ -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;
diff --git a/idd/LGIdd/capture/CFrameTex.cpp b/idd/LGIdd/capture/CFrameTex.cpp
index c01472c2..b8e4a699 100644
--- a/idd/LGIdd/capture/CFrameTex.cpp
+++ b/idd/LGIdd/capture/CFrameTex.cpp
@@ -297,11 +297,13 @@ CFrameTex::CFrameTex(uint64_t graphValue, uint64_t poolValue,
const FrameProfile& profileValue,
const FrameDesc& frameValue, const FrameTime& timeValue,
const ComPtr& res,
- const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue) :
+ const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue,
+ const std::shared_ptr& 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& 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)
diff --git a/idd/LGIdd/capture/CFrameTex.h b/idd/LGIdd/capture/CFrameTex.h
index 465991b7..77761dfa 100644
--- a/idd/LGIdd/capture/CFrameTex.h
+++ b/idd/LGIdd/capture/CFrameTex.h
@@ -59,13 +59,15 @@ private:
ComPtr m_res;
D12Sync m_sync;
D3D12_RESOURCE_STATES m_state;
- bool m_shared;
+ bool m_shared;
+ std::shared_ptr 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& res,
const D12Sync& sync,
- D3D12_RESOURCE_STATES state, bool shared);
+ D3D12_RESOURCE_STATES state, bool shared,
+ const std::shared_ptr& 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& 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.
diff --git a/idd/LGIdd/capture/CSwapChainProcessor.cpp b/idd/LGIdd/capture/CSwapChainProcessor.cpp
index fdfaea1f..ec7acdee 100644
--- a/idd/LGIdd/capture/CSwapChainProcessor.cpp
+++ b/idd/LGIdd/capture/CSwapChainProcessor.cpp
@@ -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 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 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,
diff --git a/idd/LGIdd/capture/CSwapChainProcessor.h b/idd/LGIdd/capture/CSwapChainProcessor.h
index 81c2c615..966bd850 100644
--- a/idd/LGIdd/capture/CSwapChainProcessor.h
+++ b/idd/LGIdd/capture/CSwapChainProcessor.h
@@ -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;
diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp
index edc28778..251541de 100644
--- a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp
+++ b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp
@@ -507,6 +507,21 @@ void CD3D12CommandQueue::DeInit()
m_qpcFrequency = 0;
}
+CD3D12CommandSlot * CD3D12CommandQueue::Try(UINT slotIndex)
+{
+ if (slotIndex >= m_slotCount)
+ return nullptr;
+
+ CD3D12CommandSlot& slot = m_slots[slotIndex];
+ if (slot.Acquire())
+ return &slot;
+
+ // The GPU may already be finished while its thread-pool callback is
+ // still pending. Complete it here without waiting for callback dispatch.
+ slot.OnCompletion(false);
+ return slot.Acquire() ? &slot : nullptr;
+}
+
CD3D12CommandSlot * CD3D12CommandQueue::Acquire(UINT slotIndex)
{
if (slotIndex >= m_slotCount)
@@ -515,14 +530,8 @@ CD3D12CommandSlot * CD3D12CommandQueue::Acquire(UINT slotIndex)
const ULONGLONG deadline = GetTickCount64() + 100;
for (;;)
{
- if (m_slots[slotIndex].Acquire())
- return &m_slots[slotIndex];
-
- // The GPU may already be finished while its thread-pool callback is
- // still pending. Complete it here before sleeping on callback dispatch.
- m_slots[slotIndex].OnCompletion(false);
- if (m_slots[slotIndex].Acquire())
- return &m_slots[slotIndex];
+ if (CD3D12CommandSlot * slot = Try(slotIndex))
+ return slot;
if (Atomic::Load(m_failed, std::memory_order_acquire))
break;
@@ -549,16 +558,7 @@ CD3D12CommandSlot * CD3D12CommandQueue::Acquire()
// The unindexed path is used for immediate software publication. Keep at
// most one copy in flight so a newer frame is dropped instead of queued
// behind bandwidth-bound work which is already stale.
- CD3D12CommandSlot& slot = m_slots[0];
- if (slot.Acquire())
- return &slot;
-
- // Complete already-fenced work without waiting for callback dispatch.
- slot.OnCompletion(false);
- if (slot.Acquire())
- return &slot;
-
- return nullptr;
+ return Try(0);
}
void CD3D12CommandQueue::WaitForIdle()
diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.h b/idd/LGIdd/d3d/CD3D12CommandQueue.h
index a240ce88..2f1c69d8 100644
--- a/idd/LGIdd/d3d/CD3D12CommandQueue.h
+++ b/idd/LGIdd/d3d/CD3D12CommandQueue.h
@@ -201,7 +201,12 @@ class CD3D12CommandQueue
UINT slotCount, bool enableTiming = false, bool sharedFence = false);
void DeInit();
+ CD3D12CommandSlot * Try(UINT slotIndex);
CD3D12CommandSlot * Acquire(UINT slotIndex);
CD3D12CommandSlot * Acquire();
+ bool Failed() const
+ {
+ return Atomic::Load(m_failed, std::memory_order_acquire);
+ }
void WaitForIdle();
};
diff --git a/idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp b/idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp
index 5d86916c..3aa47b42 100644
--- a/idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp
+++ b/idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp
@@ -165,6 +165,20 @@ bool CColorTransformEffect::Init(const ComPtr& device)
PostProcessStatus CColorTransformEffect::SetFormat(
const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, true);
+}
+
+PostProcessStatus CColorTransformEffect::Cfg(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, false);
+}
+
+PostProcessStatus CColorTransformEffect::Set(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{
const auto transform = D12::Transform(src.colorTransform);
if (!transform)
@@ -205,10 +219,15 @@ PostProcessStatus CColorTransformEffect::SetFormat(
D3D12_RESOURCE_DESC desc = src.desc;
desc.Format = dstFormat;
- desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
- if (!m_dst || m_dst->GetDesc().Width != desc.Width ||
- m_dst->GetDesc().Height != desc.Height ||
- m_dst->GetDesc().Format != desc.Format)
+ desc.Flags = own ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS :
+ dst.desc.Flags;
+ if (!(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
+ return PostProcessStatus::FAILED;
+ if (own &&
+ (!m_dst ||
+ m_dst->GetDesc().Width != desc.Width ||
+ m_dst->GetDesc().Height != desc.Height ||
+ m_dst->GetDesc().Format != desc.Format))
{
if (!CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED;
@@ -216,9 +235,9 @@ PostProcessStatus CColorTransformEffect::SetFormat(
std::memcpy(m_consts.matrix, transform->matrix,
sizeof(m_consts.matrix));
- m_consts.scalar = transform->scalar;
- m_consts.matrixEnabled = matrixEnabled;
- m_consts.lutEnabled = lutEnabled;
+ m_consts.scalar = transform->scalar;
+ m_consts.matrixEnabled = matrixEnabled;
+ m_consts.lutEnabled = lutEnabled;
const UINT inputTransfer = src.hdrPQ ? TRANSFER_PQ :
(src.hdr ? TRANSFER_LINEAR : TRANSFER_SRGB);
m_consts.inputTransfer = inputTransfer;
@@ -230,6 +249,7 @@ PostProcessStatus CColorTransformEffect::SetFormat(
m_srcFormat = src.desc.Format;
m_dstFormat = dstFormat;
+ m_outDesc = desc;
m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height);
@@ -245,6 +265,30 @@ ComPtr CColorTransformEffect::Run(
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects)
+{
+ if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
+ dirtyRects, nbDirtyRects))
+ return nullptr;
+ return m_dst;
+}
+
+bool CColorTransformEffect::Run(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
+{
+ if (!device || !commandList || !src || src.Get() == dst || !IsDst(dst))
+ return false;
+ return Draw(device, commandList, src, dst,
+ dirtyRects, nbDirtyRects);
+}
+
+bool CColorTransformEffect::Draw(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
{
UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects);
@@ -261,7 +305,7 @@ ComPtr CColorTransformEffect::Run(
m_uploadPending = false;
}
- TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
@@ -275,10 +319,10 @@ ComPtr CColorTransformEffect::Run(
device->CreateShaderResourceView(
m_lutBuffer.Get(), &lutDesc, Handle(device, 2));
- UAV(device, 3, m_dst.Get(), m_dstFormat);
+ UAV(device, 3, dst, m_dstFormat);
Dispatch(commandList);
- TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON);
- return m_dst;
+ return true;
}
diff --git a/idd/LGIdd/postprocess/effect/CColorTransformEffect.h b/idd/LGIdd/postprocess/effect/CColorTransformEffect.h
index 5ada0e55..035a2ccf 100644
--- a/idd/LGIdd/postprocess/effect/CColorTransformEffect.h
+++ b/idd/LGIdd/postprocess/effect/CColorTransformEffect.h
@@ -51,6 +51,13 @@ private:
CalPart m_part;
bool m_keepSignal;
+ PostProcessStatus Set(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own);
+ bool Draw(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
+
public:
explicit CColorTransformEffect(CalPart part = CalPart::ALL,
bool keepSignal = false) :
@@ -62,9 +69,17 @@ public:
PostProcessStatus SetFormat(const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst) override;
+ // Configures shader state without allocating the legacy-owned output.
+ PostProcessStatus Cfg(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst);
ComPtr Run(const ComPtr& device,
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override;
+ // dst must match Cfg's output and be in COMMON; Run restores COMMON.
+ bool Run(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
};
diff --git a/idd/LGIdd/postprocess/effect/CComputeEffect.cpp b/idd/LGIdd/postprocess/effect/CComputeEffect.cpp
index 1aa30b89..e1d44073 100644
--- a/idd/LGIdd/postprocess/effect/CComputeEffect.cpp
+++ b/idd/LGIdd/postprocess/effect/CComputeEffect.cpp
@@ -280,11 +280,27 @@ void CComputeEffect::UAV(const ComPtr& device, UINT index,
void CComputeEffect::TransitionDst(
const ComPtr& commandList,
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
+{
+ TransitionDst(commandList, m_dst.Get(), before, after);
+}
+
+bool CComputeEffect::IsDst(ID3D12Resource * dst) const
+{
+ if (!dst)
+ return false;
+
+ return D12::Same(dst->GetDesc(), m_outDesc);
+}
+
+void CComputeEffect::TransitionDst(
+ const ComPtr& commandList,
+ ID3D12Resource * dst, D3D12_RESOURCE_STATES before,
+ D3D12_RESOURCE_STATES after)
{
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
- barrier.Transition.pResource = m_dst.Get();
+ barrier.Transition.pResource = dst;
barrier.Transition.StateBefore = before;
barrier.Transition.StateAfter = after;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
diff --git a/idd/LGIdd/postprocess/effect/CComputeEffect.h b/idd/LGIdd/postprocess/effect/CComputeEffect.h
index 9f2c8367..ee3d0819 100644
--- a/idd/LGIdd/postprocess/effect/CComputeEffect.h
+++ b/idd/LGIdd/postprocess/effect/CComputeEffect.h
@@ -61,6 +61,7 @@ protected:
ComPtr m_pso;
ComPtr m_descHeap;
ComPtr m_dst;
+ D3D12_RESOURCE_DESC m_outDesc = {};
unsigned m_threadsX = 0;
unsigned m_threadsY = 0;
@@ -81,6 +82,11 @@ protected:
void UAV(const ComPtr& device, UINT index,
ID3D12Resource * resource, DXGI_FORMAT format) const;
+ bool IsDst(ID3D12Resource * dst) const;
+
void TransitionDst(const ComPtr& commandList,
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after);
+ void TransitionDst(const ComPtr& commandList,
+ ID3D12Resource * dst, D3D12_RESOURCE_STATES before,
+ D3D12_RESOURCE_STATES after);
};
diff --git a/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp b/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp
index c68267d6..ede8fdb6 100644
--- a/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp
+++ b/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp
@@ -156,6 +156,20 @@ bool CDownsampleEffect::Init(const ComPtr& device, bool report)
PostProcessStatus CDownsampleEffect::SetFormat(
const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, true);
+}
+
+PostProcessStatus CDownsampleEffect::Cfg(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, false);
+}
+
+PostProcessStatus CDownsampleEffect::Set(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{
unsigned targetX = m_targetX;
unsigned targetY = m_targetY;
@@ -174,9 +188,12 @@ PostProcessStatus CDownsampleEffect::SetFormat(
D3D12_RESOURCE_DESC desc = src.desc;
desc.Width = targetX;
desc.Height = targetY;
- desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
+ desc.Flags = own ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS :
+ dst.desc.Flags;
+ if (!(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
+ return PostProcessStatus::FAILED;
- if (!CreateDefaultTexture(device, desc, m_dst))
+ if (own && !CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED;
m_consts.width = (float)targetX;
@@ -190,6 +207,7 @@ PostProcessStatus CDownsampleEffect::SetFormat(
}
m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height);
+ m_outDesc = desc;
m_format = src.desc.Format;
m_scaleX = (double)desc.Width / src.desc.Width;
m_scaleY = (double)desc.Height / src.desc.Height;
@@ -225,19 +243,43 @@ ComPtr CDownsampleEffect::Run(
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects)
+{
+ if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
+ dirtyRects, nbDirtyRects))
+ return nullptr;
+ return m_dst;
+}
+
+bool CDownsampleEffect::Run(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
+{
+ if (!device || !commandList || !src || src.Get() == dst || !IsDst(dst))
+ return false;
+ return Draw(device, commandList, src, dst,
+ dirtyRects, nbDirtyRects);
+}
+
+bool CDownsampleEffect::Draw(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
{
UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects);
- TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
SRV(device, 1, src.Get(), m_format);
- UAV(device, 2, m_dst.Get(), m_format);
+ UAV(device, 2, dst, m_format);
Dispatch(commandList);
- TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON);
- return m_dst;
+ return true;
}
diff --git a/idd/LGIdd/postprocess/effect/CDownsampleEffect.h b/idd/LGIdd/postprocess/effect/CDownsampleEffect.h
index 898122ea..bf9e4f66 100644
--- a/idd/LGIdd/postprocess/effect/CDownsampleEffect.h
+++ b/idd/LGIdd/postprocess/effect/CDownsampleEffect.h
@@ -55,6 +55,12 @@ private:
bool ParseRules(const std::wstring& value, bool report);
const Rule * MatchRule(unsigned width, unsigned height) const;
+ PostProcessStatus Set(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own);
+ bool Draw(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
public:
CDownsampleEffect() = default;
@@ -67,6 +73,9 @@ public:
PostProcessStatus SetFormat(const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst) override;
+ // Configures shader state without allocating the legacy-owned output.
+ PostProcessStatus Cfg(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst);
void AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) override;
@@ -74,4 +83,9 @@ public:
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override;
+ // dst must match Cfg's output and be in COMMON; Run restores COMMON.
+ bool Run(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
};
diff --git a/idd/LGIdd/postprocess/effect/CFormatEffect.cpp b/idd/LGIdd/postprocess/effect/CFormatEffect.cpp
new file mode 100644
index 00000000..971ee3d8
--- /dev/null
+++ b/idd/LGIdd/postprocess/effect/CFormatEffect.cpp
@@ -0,0 +1,164 @@
+/**
+ * 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 "CFormatEffect.h"
+
+#include "CDebug.h"
+
+using namespace PostProcessUtil;
+
+namespace
+{
+ bool IsSDR8(const D12FrameFormat& format)
+ {
+ if (format.hdr || format.hdrPQ)
+ return false;
+
+ switch (format.format)
+ {
+ case FRAME_TYPE_BGRA:
+ return format.desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM;
+ case FRAME_TYPE_RGBA:
+ return format.desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM;
+ default:
+ return false;
+ }
+ }
+}
+
+bool CFormatEffect::Init(const ComPtr& device)
+{
+ D3D12_DESCRIPTOR_RANGE ranges[] =
+ {
+ Range(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0),
+ Range(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0),
+ };
+
+ const char * shader =
+ "Texture2D src : register(t0);\n"
+ "RWTexture2D dst : register(u0);\n"
+ "[numthreads(" POST_PROCESS_THREADS_STR ", "
+ POST_PROCESS_THREADS_STR ", 1)]\n"
+ "void main(uint3 dt : SV_DispatchThreadID)\n"
+ "{\n"
+ " dst[dt.xy] = src[dt.xy];\n"
+ "}\n";
+
+ return InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader);
+}
+
+PostProcessStatus CFormatEffect::SetFormat(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Cfg(device, src, dst);
+}
+
+PostProcessStatus CFormatEffect::Cfg(
+ const ComPtr& device,
+ const D12FrameFormat& src, const D12FrameFormat& dst)
+{
+ UNREFERENCED_PARAMETER(device);
+
+ if (src.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
+ dst.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
+ !src.desc.Width ||
+ !src.desc.Height ||
+ src.desc.Width != dst.desc.Width ||
+ src.desc.Height != dst.desc.Height ||
+ !IsSDR8(src) ||
+ !IsSDR8(dst))
+ {
+ DEBUG_ERROR("Unsupported texture format conversion");
+ return PostProcessStatus::FAILED;
+ }
+
+ if (src.desc.Format == dst.desc.Format)
+ return PostProcessStatus::BYPASS_EFFECT;
+
+ if (!(dst.desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
+ {
+ DEBUG_ERROR("Format conversion destination does not allow UAV access");
+ return PostProcessStatus::FAILED;
+ }
+
+ m_srcDesc = src.desc;
+ m_outDesc = dst.desc;
+ m_srcFormat = src.desc.Format;
+ m_dstFormat = dst.desc.Format;
+ m_threadsX = Groups((unsigned)dst.desc.Width);
+ m_threadsY = Groups(dst.desc.Height);
+ return PostProcessStatus::SUCCESS;
+}
+
+ComPtr CFormatEffect::Run(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, RECT dirtyRects[],
+ unsigned * nbDirtyRects)
+{
+ UNREFERENCED_PARAMETER(device);
+ UNREFERENCED_PARAMETER(commandList);
+ UNREFERENCED_PARAMETER(src);
+ UNREFERENCED_PARAMETER(dirtyRects);
+ UNREFERENCED_PARAMETER(nbDirtyRects);
+ return nullptr;
+}
+
+bool CFormatEffect::IsSrc(ID3D12Resource * src) const
+{
+ if (!src)
+ return false;
+
+ return D12::Same(src->GetDesc(), m_srcDesc, D12::DescCmp::VIEW);
+}
+
+bool CFormatEffect::Run(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
+{
+ if (!device || !commandList || !IsSrc(src.Get()) ||
+ src.Get() == dst || !IsDst(dst))
+ return false;
+ return Draw(device, commandList, src, dst, dirtyRects, nbDirtyRects);
+}
+
+bool CFormatEffect::Draw(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
+{
+ UNREFERENCED_PARAMETER(dirtyRects);
+ UNREFERENCED_PARAMETER(nbDirtyRects);
+
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
+ D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+
+ SRV(device, 0, src.Get(), m_srcFormat);
+ UAV(device, 1, dst, m_dstFormat);
+ Dispatch(commandList);
+
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
+ D3D12_RESOURCE_STATE_COMMON);
+ return true;
+}
diff --git a/idd/LGIdd/postprocess/effect/CFormatEffect.h b/idd/LGIdd/postprocess/effect/CFormatEffect.h
new file mode 100644
index 00000000..48587a80
--- /dev/null
+++ b/idd/LGIdd/postprocess/effect/CFormatEffect.h
@@ -0,0 +1,57 @@
+/**
+ * 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 "CComputeEffect.h"
+
+class CFormatEffect : public CComputeEffect
+{
+private:
+ D3D12_RESOURCE_DESC m_srcDesc = {};
+ DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN;
+ DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN;
+
+ bool IsSrc(ID3D12Resource * src) const;
+ bool Draw(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
+
+public:
+ const char * GetName() const override { return "Format"; }
+
+ bool Init(const ComPtr& device);
+
+ PostProcessStatus SetFormat(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst) override;
+ PostProcessStatus Cfg(const ComPtr& device,
+ const D12FrameFormat& src, const D12FrameFormat& dst);
+
+ ComPtr Run(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, RECT dirtyRects[],
+ unsigned * nbDirtyRects) override;
+ // dst must match Cfg's output and be in COMMON; Run restores COMMON.
+ bool Run(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
+};
diff --git a/idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp
index dcc838eb..92f0d5f0 100644
--- a/idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp
+++ b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp
@@ -87,17 +87,35 @@ bool CHDR16to10Effect::Init(const ComPtr& device)
PostProcessStatus CHDR16to10Effect::SetFormat(
const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, true);
+}
+
+PostProcessStatus CHDR16to10Effect::Cfg(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst)
+{
+ return Set(device, src, dst, false);
+}
+
+PostProcessStatus CHDR16to10Effect::Set(
+ const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{
if (src.desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT || !src.hdr)
return PostProcessStatus::BYPASS_EFFECT;
D3D12_RESOURCE_DESC desc = src.desc;
desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
- desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
-
- if (!CreateDefaultTexture(device, desc, m_dst))
+ desc.Flags = own ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS :
+ dst.desc.Flags;
+ if (!(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
return PostProcessStatus::FAILED;
+ if (own && !CreateDefaultTexture(device, desc, m_dst))
+ return PostProcessStatus::FAILED;
+
+ m_outDesc = desc;
m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height);
@@ -118,19 +136,43 @@ ComPtr CHDR16to10Effect::Run(
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects)
+{
+ if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
+ dirtyRects, nbDirtyRects))
+ return nullptr;
+ return m_dst;
+}
+
+bool CHDR16to10Effect::Run(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
+{
+ if (!device || !commandList || !src || src.Get() == dst || !IsDst(dst))
+ return false;
+ return Draw(device, commandList, src, dst,
+ dirtyRects, nbDirtyRects);
+}
+
+bool CHDR16to10Effect::Draw(
+ const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects)
{
UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects);
- TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
SRV(device, 1, src.Get(), DXGI_FORMAT_R16G16B16A16_FLOAT);
- UAV(device, 2, m_dst.Get(), DXGI_FORMAT_R10G10B10A2_UNORM);
+ UAV(device, 2, dst, DXGI_FORMAT_R10G10B10A2_UNORM);
Dispatch(commandList);
- TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
+ TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON);
- return m_dst;
+ return true;
}
diff --git a/idd/LGIdd/postprocess/effect/CHDR16to10Effect.h b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.h
index c60f4f98..c34d97a6 100644
--- a/idd/LGIdd/postprocess/effect/CHDR16to10Effect.h
+++ b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.h
@@ -31,6 +31,13 @@ private:
} m_consts = { 80.0f };
ComPtr m_constBuffer;
+ PostProcessStatus Set(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst, bool own);
+ bool Draw(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
+
public:
const char * GetName() const override { return "HDR16to10"; }
@@ -38,9 +45,17 @@ public:
PostProcessStatus SetFormat(const ComPtr& device,
const D12FrameFormat& src, D12FrameFormat& dst) override;
+ // Configures shader state without allocating the legacy-owned output.
+ PostProcessStatus Cfg(const ComPtr& device,
+ const D12FrameFormat& src, D12FrameFormat& dst);
ComPtr Run(const ComPtr& device,
const ComPtr& commandList,
const ComPtr& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override;
+ // dst must match Cfg's output and be in COMMON; Run restores COMMON.
+ bool Run(const ComPtr& device,
+ const ComPtr& commandList,
+ const ComPtr& src, ID3D12Resource * dst,
+ RECT dirtyRects[], unsigned * nbDirtyRects);
};
diff --git a/idd/LGIdd/transport/CTexHub.cpp b/idd/LGIdd/transport/CTexHub.cpp
index 35e79c05..68c8de45 100644
--- a/idd/LGIdd/transport/CTexHub.cpp
+++ b/idd/LGIdd/transport/CTexHub.cpp
@@ -490,6 +490,24 @@ void CTexHub::Abort(CTexStage& stage) noexcept
drop.reset();
}
+void CTexHub::Fault(
+ const FrameIn& frame, PushResult result) noexcept
+{
+ if (result != PushResult::REJECTED && result != PushResult::FAILED)
+ return;
+
+ std::shared_ptr set;
+ unsigned index;
+ ITexSink * sink = nullptr;
+ if (!Enter(frame, true, set, index, sink))
+ {
+ if (set)
+ Leave(set, TRANSPORT_MAX_INSTANCES);
+ return;
+ }
+ Finish(set, index, result);
+}
+
PushResult CTexHub::Push(FrameIn frame, TexLease lease) noexcept
{
std::shared_ptr set;
diff --git a/idd/LGIdd/transport/CTexHub.h b/idd/LGIdd/transport/CTexHub.h
index 01f4b1b2..9f2735f8 100644
--- a/idd/LGIdd/transport/CTexHub.h
+++ b/idd/LGIdd/transport/CTexHub.h
@@ -90,19 +90,18 @@ private:
std::shared_ptr m_active;
std::shared_ptr m_pending;
std::atomic & m_rev;
- HANDLE m_idle = nullptr;
- unsigned m_calls = 0;
- bool m_open = true;
- bool m_changing = false;
- bool m_stopped = false;
- FaultRec m_faults[MAX_FAULTS] = {};
- unsigned m_faultCount = 0;
- Failure m_failures[TRANSPORT_MAX_INSTANCES] = {};
- unsigned m_failureCount = 0;
+ HANDLE m_idle = nullptr;
+ unsigned m_calls = 0;
+ bool m_open = true;
+ bool m_changing = false;
+ bool m_stopped = false;
+ FaultRec m_faults[MAX_FAULTS] = {};
+ unsigned m_faultCount = 0;
+ Failure m_failures[TRANSPORT_MAX_INSTANCES] = {};
+ unsigned m_failureCount = 0;
static bool Match(const FaultRec& fault, BackendId id, uint32_t epoch,
const FrameCfg& cfg, bool anyCfg = false);
-
bool Enter(const FrameIn& frame, bool lease,
std::shared_ptr& set, unsigned& route, ITexSink *& sink);
void Finish(const std::shared_ptr& set, unsigned route,
@@ -135,6 +134,7 @@ private:
void Stop();
public:
+ void Fault(const FrameIn& frame, PushResult result) noexcept;
PushResult Push(FrameIn frame, TexLease lease) noexcept;
PushResult Push(FrameIn frame, D11Lease lease) noexcept;
};
diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp
index 1e9d4844..45df40fc 100644
--- a/idd/LGIdd/transport/CTransportManager.cpp
+++ b/idd/LGIdd/transport/CTransportManager.cpp
@@ -809,15 +809,15 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now,
const bool frameService =
(entry.config.services & TRANSPORT_SERVICE_FRAME) != 0;
entry.transport.reset();
- entry.directMemory = DirectFrameBufferMemory {};
+ entry.directMemory = DirectFrameBufferMemory {};
entry.directMemoryValid = false;
- entry.setupDone = false;
- entry.controlFailed = false;
- entry.controlAbsent = false;
- entry.inputFailed = false;
- entry.inputAbsent = false;
- entry.frameAbsent = false;
- entry.serviceRetryAt = 0;
+ entry.setupDone = false;
+ entry.controlFailed = false;
+ entry.controlAbsent = false;
+ entry.inputFailed = false;
+ entry.inputAbsent = false;
+ entry.frameAbsent = false;
+ entry.serviceRetryAt = 0;
Seq::Inc(entry.epoch);
if (frameService)
BumpFrameRev();
@@ -1254,7 +1254,7 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
break;
if (!next.Add(route.id, route.epoch, route.required,
- route.primary, candidate))
+ route.primary, route.texSink != nullptr, candidate))
{
route.transport->Abort();
routeResult = CfgResult::FAILED;