diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj
index 7a52074a..ff0c48c7 100644
--- a/idd/LGIdd/LGIdd.vcxproj
+++ b/idd/LGIdd/LGIdd.vcxproj
@@ -43,6 +43,7 @@
+
@@ -95,6 +96,7 @@
+
diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters
index ea027eb0..8a651f56 100644
--- a/idd/LGIdd/LGIdd.vcxproj.filters
+++ b/idd/LGIdd/LGIdd.vcxproj.filters
@@ -97,6 +97,9 @@
Capture
+
+ Capture
+
Capture
@@ -273,6 +276,9 @@
Capture
+
+ Capture
+
Capture
diff --git a/idd/LGIdd/capture/CFrameGraph.cpp b/idd/LGIdd/capture/CFrameGraph.cpp
new file mode 100644
index 00000000..37265b5c
--- /dev/null
+++ b/idd/LGIdd/capture/CFrameGraph.cpp
@@ -0,0 +1,286 @@
+/**
+ * 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/CFrameGraph.h"
+
+bool Frame::Same(const GraphCfg& left, const GraphCfg& right)
+{
+ return
+ left.mode == right.mode &&
+ Frame::Same(left.adapter, right.adapter) &&
+ left.srcWidth == right.srcWidth &&
+ left.srcHeight == right.srcHeight &&
+ Frame::Same(left.src, right.src) &&
+ left.width == right.width &&
+ left.height == right.height &&
+ Frame::Same(left.checkpoint, right.checkpoint);
+}
+
+bool Frame::Valid(FrameDamage damage, const RECT * rects, unsigned count,
+ unsigned width, unsigned height)
+{
+ switch (damage)
+ {
+ case FrameDamage::NONE:
+ case FrameDamage::FULL:
+ return count == 0;
+
+ case FrameDamage::RECTS:
+ if (!rects || !count || count > FRAME_DAMAGE_MAX)
+ return false;
+ for (unsigned i = 0; i < count; ++i)
+ {
+ const RECT& rect = rects[i];
+ if (rect.left < 0 ||
+ rect.top < 0 ||
+ rect.left >= rect.right ||
+ rect.top >= rect.bottom ||
+ static_cast(rect.right) > width ||
+ static_cast(rect.bottom) > height)
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+void CFrameGraph::Reset()
+{
+ m_cfg = GraphCfg {};
+ m_nodeCount = 0;
+ m_leafCount = 0;
+ m_begun = false;
+ m_sealed = false;
+}
+
+bool CFrameGraph::Begin(const GraphCfg& cfg)
+{
+ Reset();
+ if (!cfg.srcWidth || !cfg.srcHeight || !cfg.width || !cfg.height ||
+ !Frame::Valid(cfg.mode) ||
+ cfg.src.storage != FrameStorage::D3D12_TEXTURE ||
+ cfg.checkpoint.storage != FrameStorage::D3D12_TEXTURE ||
+ !Frame::Valid(cfg.src) || !Frame::Valid(cfg.checkpoint) ||
+ !Frame::Can(cfg.src.signal, cfg.checkpoint.signal))
+ return false;
+
+ m_cfg = cfg;
+ GraphNode& source = m_nodes[m_nodeCount++];
+ source = GraphNode {};
+ source.op = FrameOp::SRC;
+ source.parent = FRAME_GRAPH_ROOT;
+ source.width = cfg.srcWidth;
+ source.height = cfg.srcHeight;
+ source.profile = cfg.src;
+ m_begun = true;
+ return true;
+}
+
+bool CFrameGraph::Can(const FrameCfg& cfg) const
+{
+ if (!m_begun || m_sealed || !cfg.width || !cfg.height ||
+ cfg.width != m_cfg.width || cfg.height != m_cfg.height ||
+ cfg.mode != m_cfg.mode ||
+ !Frame::Same(cfg.adapter, m_cfg.adapter) ||
+ !Frame::Valid(cfg.profile))
+ return false;
+ return Frame::Can(m_cfg.src.signal, cfg.profile.signal);
+}
+
+unsigned CFrameGraph::Find(FrameOp op, unsigned parent,
+ unsigned width, unsigned height,
+ const FrameProfile& profile) const
+{
+ for (unsigned i = 1; i < m_nodeCount; ++i)
+ if (m_nodes[i].op == op && m_nodes[i].parent == parent &&
+ m_nodes[i].width == width && m_nodes[i].height == height &&
+ Frame::Same(m_nodes[i].profile, profile))
+ return i;
+ return FRAME_GRAPH_ROOT;
+}
+
+unsigned CFrameGraph::AddNode(FrameOp op, unsigned parent,
+ unsigned width, unsigned height,
+ const FrameProfile& profile)
+{
+ const unsigned found = Find(op, parent, width, height, profile);
+ if (found != FRAME_GRAPH_ROOT)
+ return found;
+ if (m_nodeCount == FRAME_GRAPH_MAX_NODES)
+ return FRAME_GRAPH_ROOT;
+
+ const unsigned index = m_nodeCount++;
+ GraphNode& node = m_nodes[index];
+ node = GraphNode {};
+ node.op = op;
+ node.parent = parent;
+ node.width = width;
+ node.height = height;
+ node.profile = profile;
+ return index;
+}
+
+unsigned CFrameGraph::Checkpoint(const FrameProfile& requested)
+{
+ const FrameProfile profile =
+ Frame::Store(requested, FrameStorage::D3D12_TEXTURE);
+ unsigned parent = 0;
+ FrameOp op;
+ switch (profile.signal)
+ {
+ case FrameSignal::SRGB:
+ op = FrameOp::SDR;
+ parent = AddNode(FrameOp::SDR, 0, m_cfg.width, m_cfg.height,
+ m_cfg.checkpoint);
+ if (parent == FRAME_GRAPH_ROOT)
+ return FRAME_GRAPH_ROOT;
+ if (Frame::Same(profile, m_cfg.checkpoint))
+ return parent;
+ break;
+
+ case FrameSignal::SCRGB_LINEAR:
+ {
+ op = FrameOp::SCRGB;
+ FrameProfile scRGB;
+ scRGB.storage = FrameStorage::D3D12_TEXTURE;
+ scRGB.pixel = FramePixel::RGBA16F;
+ scRGB.signal = FrameSignal::SCRGB_LINEAR;
+ return AddNode(
+ op, 0, m_cfg.width, m_cfg.height, scRGB);
+ }
+
+ case FrameSignal::PQ_BT2020:
+ op = FrameOp::HDR10;
+ if (m_cfg.src.signal == FrameSignal::SCRGB_LINEAR)
+ {
+ FrameProfile scRGB;
+ scRGB.storage = FrameStorage::D3D12_TEXTURE;
+ scRGB.pixel = FramePixel::RGBA16F;
+ scRGB.signal = FrameSignal::SCRGB_LINEAR;
+ parent = AddNode(
+ FrameOp::SCRGB, 0, m_cfg.width, m_cfg.height, scRGB);
+ if (parent == FRAME_GRAPH_ROOT)
+ return FRAME_GRAPH_ROOT;
+ }
+ else
+ {
+ parent = AddNode(FrameOp::HDR10, 0,
+ m_cfg.width, m_cfg.height, m_cfg.checkpoint);
+ if (parent == FRAME_GRAPH_ROOT)
+ return FRAME_GRAPH_ROOT;
+ if (Frame::Same(profile, m_cfg.checkpoint))
+ return parent;
+ }
+ break;
+
+ default:
+ return FRAME_GRAPH_ROOT;
+ }
+ return AddNode(
+ op, parent, m_cfg.width, m_cfg.height, profile);
+}
+
+bool CFrameGraph::Add(BackendId id, uint32_t epoch, bool required,
+ bool primary, const FrameCfg& cfg)
+{
+ if (!id || !epoch || !Can(cfg) ||
+ m_leafCount == TRANSPORT_MAX_INSTANCES)
+ return false;
+
+ for (unsigned i = 0; i < m_leafCount; ++i)
+ if (m_leaves[i].id == id && m_leaves[i].epoch == epoch)
+ return false;
+
+ const CFrameGraph before = *this;
+ const unsigned node = Checkpoint(cfg.profile);
+ if (node == FRAME_GRAPH_ROOT)
+ {
+ *this = before;
+ return false;
+ }
+
+ GraphLeaf& leaf = m_leaves[m_leafCount++];
+ leaf = GraphLeaf {};
+ leaf.id = id;
+ leaf.epoch = epoch;
+ leaf.node = node;
+ leaf.required = required;
+ leaf.primary = primary;
+ leaf.cfg = cfg;
+
+ for (unsigned current = node;
+ current != FRAME_GRAPH_ROOT; current = m_nodes[current].parent)
+ ++m_nodes[current].refs;
+ return true;
+}
+
+bool CFrameGraph::Seal()
+{
+ if (!m_begun || m_sealed || !m_leafCount || !m_nodeCount ||
+ !m_nodes[0].refs)
+ return false;
+
+ for (unsigned i = 0; i < m_leafCount; ++i)
+ if (!m_leaves[i].id || !m_leaves[i].epoch ||
+ !m_leaves[i].node || m_leaves[i].node >= m_nodeCount)
+ return false;
+
+ m_sealed = true;
+ return true;
+}
+
+bool CFrameGraph::Same(const GraphCfg& cfg) const
+{
+ return m_sealed && Frame::Same(m_cfg, cfg);
+}
+
+bool CFrameGraph::Desc(unsigned leaf, const FrameDesc& frame,
+ LeafDesc& desc) const
+{
+ if (!m_sealed || leaf >= m_leafCount || !frame.serial ||
+ frame.format.width != m_cfg.srcWidth ||
+ frame.format.height != m_cfg.srcHeight ||
+ !Frame::Valid(frame.damage, frame.rects, frame.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 = frame;
+
+ D12FrameFormat& format = desc.frame.format;
+ return D12::Set(format, node.profile, node.width, node.height);
+}
+
+const GraphNode * CFrameGraph::Nodes(unsigned& count) const
+{
+ count = m_sealed ? m_nodeCount : 0;
+ return count ? m_nodes : nullptr;
+}
+
+const GraphLeaf * CFrameGraph::Leaves(unsigned& count) const
+{
+ count = m_sealed ? m_leafCount : 0;
+ return count ? m_leaves : nullptr;
+}
diff --git a/idd/LGIdd/capture/CFrameGraph.h b/idd/LGIdd/capture/CFrameGraph.h
new file mode 100644
index 00000000..58ce403f
--- /dev/null
+++ b/idd/LGIdd/capture/CFrameGraph.h
@@ -0,0 +1,142 @@
+/**
+ * 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 "postprocess/D12FrameFormat.h"
+#include "transport/FrameProfile.h"
+#include "transport/TransportConfig.h"
+
+#include
+#include
+#include
+
+static const unsigned FRAME_DAMAGE_MAX = 256;
+static const unsigned FRAME_GRAPH_ROOT = UINT_MAX;
+static const unsigned FRAME_GRAPH_MAX_NODES =
+ 1 + 2 * TRANSPORT_MAX_INSTANCES;
+
+enum class FrameOp : uint8_t
+{
+ SRC,
+ SDR,
+ SCRGB,
+ HDR10,
+};
+
+enum class FrameDamage : uint8_t
+{
+ NONE,
+ RECTS,
+ FULL,
+};
+
+struct GraphCfg
+{
+ GpuMode mode = GpuMode::HARDWARE;
+ LUID adapter = {};
+ unsigned srcWidth = 0;
+ unsigned srcHeight = 0;
+ FrameProfile src;
+ unsigned width = 0;
+ unsigned height = 0;
+ FrameProfile checkpoint;
+};
+
+namespace Frame
+{
+ bool Same(const GraphCfg& left, const GraphCfg& right);
+ bool Valid(FrameDamage damage, const RECT * rects, unsigned count,
+ unsigned width, unsigned height);
+}
+
+struct GraphNode
+{
+ FrameOp op = FrameOp::SRC;
+ unsigned parent = FRAME_GRAPH_ROOT;
+ unsigned refs = 0;
+ unsigned width = 0;
+ unsigned height = 0;
+ FrameProfile profile;
+};
+
+struct GraphLeaf
+{
+ BackendId id = 0;
+ uint32_t epoch = 0;
+ unsigned node = 0;
+ bool required = false;
+ bool primary = false;
+ FrameCfg cfg;
+};
+
+// FrameDesc is resource-free. It can cross graph nodes without retaining the
+// acquired IddCx texture and is paired with owned texture storage separately.
+struct FrameDesc
+{
+ uint64_t serial = 0;
+ uint64_t captureTime = 0;
+ FrameDamage damage = FrameDamage::NONE;
+ RECT rects[FRAME_DAMAGE_MAX] = {};
+ unsigned count = 0;
+ D12FrameFormat format = {};
+};
+
+struct LeafDesc
+{
+ BackendId id = 0;
+ uint32_t epoch = 0;
+ unsigned node = 0;
+ FrameProfile profile;
+ FrameDesc frame = {};
+};
+
+class CFrameGraph
+{
+private:
+ GraphCfg m_cfg;
+ GraphNode m_nodes[FRAME_GRAPH_MAX_NODES] = {};
+ GraphLeaf m_leaves[TRANSPORT_MAX_INSTANCES] = {};
+ unsigned m_nodeCount = 0;
+ unsigned m_leafCount = 0;
+ bool m_begun = false;
+ bool m_sealed = false;
+
+ unsigned Find(FrameOp op, unsigned parent, unsigned width, unsigned height,
+ const FrameProfile& profile) const;
+ unsigned AddNode(FrameOp op, unsigned parent, unsigned width,
+ unsigned height,
+ const FrameProfile& profile);
+ unsigned Checkpoint(const FrameProfile& profile);
+
+public:
+ void Reset();
+ 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 Seal();
+ bool Same(const GraphCfg& cfg) const;
+ bool Desc(unsigned leaf, const FrameDesc& frame, LeafDesc& desc) const;
+
+ const GraphCfg& Cfg() const { return m_cfg; }
+ const GraphNode * Nodes(unsigned& count) const;
+ const GraphLeaf * Leaves(unsigned& count) const;
+};
diff --git a/idd/LGIdd/capture/CSwapChainProcessor.cpp b/idd/LGIdd/capture/CSwapChainProcessor.cpp
index 40dc3e66..2aefdea2 100644
--- a/idd/LGIdd/capture/CSwapChainProcessor.cpp
+++ b/idd/LGIdd/capture/CSwapChainProcessor.cpp
@@ -40,6 +40,34 @@
static const uint32_t HDR_PQ_MIN_LUMINANCE = 50;
static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000;
+static const uint64_t GRAPH_RETRY_NS = 250000000ULL;
+
+static bool MakeGraphCfg(const D12FrameFormat& source,
+ const D12FrameFormat& checkpoint, bool software,
+ const LUID& adapter, GraphCfg& cfg)
+{
+ if (!source.width || !source.height ||
+ source.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
+ !checkpoint.width || !checkpoint.height ||
+ checkpoint.desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D)
+ return false;
+
+ FrameProfile src;
+ FrameProfile output;
+ if (!D12::Profile(source, FrameStorage::D3D12_TEXTURE, src) ||
+ !D12::Profile(checkpoint, FrameStorage::D3D12_TEXTURE, output))
+ return false;
+
+ cfg.mode = software ? GpuMode::SOFTWARE : GpuMode::HARDWARE;
+ cfg.adapter = adapter;
+ cfg.srcWidth = source.width;
+ cfg.srcHeight = source.height;
+ cfg.src = src;
+ cfg.width = checkpoint.width;
+ cfg.height = checkpoint.height;
+ cfg.checkpoint = output;
+ return true;
+}
CSwapChainProcessor::CSwapChainProcessor(CMonitorContext * monitorContext,
UINT64 assignmentGeneration, IDDCX_MONITOR monitor,
@@ -329,6 +357,8 @@ void CSwapChainProcessor::SwapChainThreadCore()
if (WaitForSingleObject(m_terminateEvent.Get(), 0) == WAIT_OBJECT_0)
break;
+ CfgGraph();
+
UINT frameNumber = 0;
UINT dirtyRectCount = 0;
UINT moveRegionCount = 0;
@@ -413,6 +443,7 @@ void CSwapChainProcessor::SwapChainThreadCore()
// Every acquired frame must be finished before the next acquire, even if
// its presentation number was a duplicate and no work was submitted.
hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain);
+ surface.Reset();
if (FAILED(hr))
{
// A lost path is normal (mode change/topology rebuild); Windows
@@ -493,6 +524,46 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const
#endif
}
+void CSwapChainProcessor::QueueGraph(const D12FrameFormat& source,
+ const D12FrameFormat& checkpoint)
+{
+ GraphCfg cfg;
+ if (!MakeGraphCfg(
+ source, checkpoint, m_dx11Device->IsSoftware(),
+ m_renderAdapter, cfg))
+ return;
+ if (m_haveGraphCfg && Frame::Same(m_graphCfg, cfg))
+ return;
+
+ m_graphCfg = cfg;
+ m_haveGraphCfg = true;
+ m_graphPending = true;
+ m_graphRetryAt = 0;
+}
+
+void CSwapChainProcessor::CfgGraph()
+{
+ if (!m_graphPending)
+ return;
+
+ const uint64_t now = CFrameScheduler::Nanotime();
+ if (now < m_graphRetryAt)
+ return;
+
+ m_graphPending = false;
+ const CfgResult result =
+ m_devContext->GetTransport().Cfg(m_graphCfg, m_graph);
+ if (result == CfgResult::ACCEPTED)
+ {
+ m_graphRetryAt = 0;
+ }
+ else if (result == CfgResult::RETRY)
+ {
+ m_graphPending = true;
+ m_graphRetryAt = now + GRAPH_RETRY_NS;
+ }
+}
+
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer,
unsigned dirtyRectCount, unsigned moveRegionCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
@@ -732,6 +803,10 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer
m_postProcessors[0].GetTimingToken(
&timingEffectIndex, &timingToken);
+
+ const D12FrameFormat& graphFormat =
+ m_postProcessors[0].GetTextureFormat();
+ QueueGraph(srcFormat, graphFormat);
}
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
diff --git a/idd/LGIdd/capture/CSwapChainProcessor.h b/idd/LGIdd/capture/CSwapChainProcessor.h
index d33ce418..153a65e1 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/CFrameGraph.h"
#include "capture/CFrameProcessor.h"
#include "postprocess/D12FrameFormat.h"
#include "postprocess/CPostProcessor.h"
@@ -56,6 +57,11 @@ private:
HANDLE m_newFrameEvent;
CInteropResourcePool m_resPool;
+ CFrameGraph m_graph;
+ GraphCfg m_graphCfg;
+ bool m_haveGraphCfg = false;
+ bool m_graphPending = false;
+ uint64_t m_graphRetryAt = 0;
CPostProcessor m_postProcessors[CAPTURE_PIPELINE_SLOTS];
std::unique_ptr m_frameProcessor;
// Reconfiguration is exclusive while per-candidate recording is shared.
@@ -93,6 +99,9 @@ private:
void UpdateHDRMetadata(const IDDCX_METADATA2& metadata);
#endif
bool GetContentHDRMetadata(D12FrameFormat& format) const;
+ void QueueGraph(const D12FrameFormat& source,
+ const D12FrameFormat& checkpoint);
+ void CfgGraph();
bool SwapChainNewFrame(ComPtr acquiredBuffer,
unsigned dirtyRectCount, unsigned moveRegionCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
diff --git a/idd/LGIdd/postprocess/CPostProcessor.cpp b/idd/LGIdd/postprocess/CPostProcessor.cpp
index bcfe67b5..efcd30db 100644
--- a/idd/LGIdd/postprocess/CPostProcessor.cpp
+++ b/idd/LGIdd/postprocess/CPostProcessor.cpp
@@ -91,6 +91,7 @@ void CPostProcessor::Reset()
m_device.Reset();
m_srcFormat = {};
m_dstFormat = {};
+ m_texFormat = {};
m_copyLayout = {};
m_copyEffect = nullptr;
m_frameSize = 0;
@@ -173,6 +174,7 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat,
// Propagate it without recreating resources or post-processing state.
D12::CopyHdr(m_srcFormat, srcFormat);
D12::CopyHdr(m_dstFormat, srcFormat);
+ D12::CopyHdr(m_texFormat, srcFormat);
return true;
}
@@ -181,6 +183,7 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat,
D12FrameFormat oldDst = m_dstFormat;
D12FrameFormat cur = srcFormat;
+ D12FrameFormat tex = srcFormat;
CPostProcessEffect * outputEffect = nullptr;
bool effectsActive = false;
@@ -193,6 +196,8 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat,
effect->Enabled = true;
effectsActive = true;
cur = dst;
+ if (dst.desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D)
+ tex = dst;
outputEffect = effect.get();
break;
@@ -249,6 +254,7 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat,
m_srcFormat = srcFormat;
m_dstFormat = cur;
+ m_texFormat = tex;
m_copyLayout = copyLayout;
m_copyEffect = copyEffect;
m_frameSize = frameSize;
diff --git a/idd/LGIdd/postprocess/CPostProcessor.h b/idd/LGIdd/postprocess/CPostProcessor.h
index 8296c0f7..a8c83c0c 100644
--- a/idd/LGIdd/postprocess/CPostProcessor.h
+++ b/idd/LGIdd/postprocess/CPostProcessor.h
@@ -107,6 +107,7 @@ private:
std::vector> m_effects;
D12FrameFormat m_srcFormat = {};
D12FrameFormat m_dstFormat = {};
+ D12FrameFormat m_texFormat = {};
D3D12_PLACED_SUBRESOURCE_FOOTPRINT m_copyLayout = {};
CPostProcessEffect * m_copyEffect = nullptr;
size_t m_frameSize = 0;
@@ -135,6 +136,7 @@ public:
unsigned * nbDirtyRects);
const D12FrameFormat& GetOutputFormat() const { return m_dstFormat; }
+ const D12FrameFormat& GetTextureFormat() const { return m_texFormat; }
bool HasActiveEffects() const { return m_effectsActive; }
void GetTimingToken(unsigned * effectIndex, uint64_t * token) const;
void RecordTiming(unsigned effectIndex, uint64_t token,
diff --git a/idd/LGIdd/postprocess/D12FrameFormat.cpp b/idd/LGIdd/postprocess/D12FrameFormat.cpp
index ca3616df..28695242 100644
--- a/idd/LGIdd/postprocess/D12FrameFormat.cpp
+++ b/idd/LGIdd/postprocess/D12FrameFormat.cpp
@@ -71,6 +71,27 @@ std::shared_ptr D12::Transform(
return transform;
}
+DXGI_FORMAT D12::Dxgi(FramePixel pixel)
+{
+ switch (pixel)
+ {
+ case FramePixel::BGRA8:
+ return DXGI_FORMAT_B8G8R8A8_UNORM;
+ case FramePixel::RGBA8:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ case FramePixel::RGB10A2:
+ return DXGI_FORMAT_R10G10B10A2_UNORM;
+ case FramePixel::RGBA16F:
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ }
+ return DXGI_FORMAT_UNKNOWN;
+}
+
+FrameType D12::Type(FramePixel pixel)
+{
+ return Type(Dxgi(pixel));
+}
+
FrameType D12::Type(DXGI_FORMAT format)
{
switch (format)
@@ -88,6 +109,75 @@ FrameType D12::Type(DXGI_FORMAT format)
}
}
+bool D12::Profile(const D12FrameFormat& format, FrameStorage storage,
+ FrameProfile& profile)
+{
+ if ((format.hdrPQ && !format.hdr) || !Frame::Valid(storage))
+ return false;
+
+ FrameProfile result;
+ result.storage = storage;
+ if (!format.hdr)
+ {
+ result.signal = FrameSignal::SRGB;
+ if (format.format == FRAME_TYPE_BGRA)
+ result.pixel = FramePixel::BGRA8;
+ else if (format.format == FRAME_TYPE_RGBA)
+ result.pixel = FramePixel::RGBA8;
+ else
+ return false;
+ }
+ else if (format.hdrPQ)
+ {
+ if (format.format != FRAME_TYPE_RGBA10)
+ return false;
+ result.pixel = FramePixel::RGB10A2;
+ result.signal = FrameSignal::PQ_BT2020;
+ }
+ else
+ {
+ if (format.format != FRAME_TYPE_RGBA16F)
+ return false;
+ result.pixel = FramePixel::RGBA16F;
+ result.signal = FrameSignal::SCRGB_LINEAR;
+ }
+
+ if (format.desc.Format != Dxgi(result.pixel))
+ return false;
+
+ profile = result;
+ return true;
+}
+
+bool D12::Set(D12FrameFormat& format, const FrameProfile& profile,
+ unsigned width, unsigned height, D3D12_RESOURCE_FLAGS flags)
+{
+ if (!width || !height || profile.storage != FrameStorage::D3D12_TEXTURE ||
+ !Frame::Valid(profile))
+ return false;
+
+ format.desc = {};
+ format.desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+ format.desc.Width = width;
+ format.desc.Height = height;
+ format.desc.DepthOrArraySize = 1;
+ format.desc.MipLevels = 1;
+ format.desc.Format = Dxgi(profile.pixel);
+ format.desc.SampleDesc.Count = 1;
+ format.desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+ format.desc.Flags = flags;
+ format.dataWidth = width;
+ format.dataHeight = height;
+ format.pitch = 0;
+ format.width = width;
+ format.height = height;
+ format.format = Type(profile.pixel);
+ format.hdr = profile.signal != FrameSignal::SRGB;
+ format.hdrPQ = profile.signal == FrameSignal::PQ_BT2020;
+ return format.desc.Format != DXGI_FORMAT_UNKNOWN &&
+ format.format != FRAME_TYPE_INVALID;
+}
+
void D12::CopyHdr(D12FrameFormat& dst, const D12FrameFormat& src)
{
dst.hdrMetadata = src.hdrMetadata;
diff --git a/idd/LGIdd/postprocess/D12FrameFormat.h b/idd/LGIdd/postprocess/D12FrameFormat.h
index 0b8694e2..4e7c2303 100644
--- a/idd/LGIdd/postprocess/D12FrameFormat.h
+++ b/idd/LGIdd/postprocess/D12FrameFormat.h
@@ -20,6 +20,8 @@
#pragma once
+#include "transport/FrameProfile.h"
+
#include
#include
#include
@@ -85,7 +87,15 @@ namespace D12
NO_FLAGS,
};
+ DXGI_FORMAT Dxgi(FramePixel pixel);
FrameType Type(DXGI_FORMAT format);
+ FrameType Type(FramePixel pixel);
+
+ bool Profile(const D12FrameFormat& format, FrameStorage storage,
+ FrameProfile& profile);
+ bool Set(D12FrameFormat& format, const FrameProfile& profile,
+ unsigned width, unsigned height,
+ D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE);
void CopyHdr(D12FrameFormat& dst, const D12FrameFormat& src);
std::shared_ptr Transform(
const std::shared_ptr& transform);
diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp
index 104b161c..5c87f6e1 100644
--- a/idd/LGIdd/transport/CTransportManager.cpp
+++ b/idd/LGIdd/transport/CTransportManager.cpp
@@ -20,6 +20,7 @@
#include "transport/CTransportManager.h"
+#include "capture/CFrameGraph.h"
#include "CDebug.h"
#include "Seq.h"
@@ -993,6 +994,190 @@ bool CTransportManager::Setup(size_t alignment)
return success;
}
+CfgResult CTransportManager::Cfg(
+ const GraphCfg& cfg, CFrameGraph& graph)
+{
+ CFrameGraph next;
+ if (!next.Begin(cfg))
+ return CfgResult::REJECTED;
+ if (!BeginPhase(Phase::CFG, true))
+ return CfgResult::RETRY;
+
+ struct Route
+ {
+ Entry * entry = nullptr;
+ std::shared_ptr transport;
+ BackendId id = 0;
+ uint32_t epoch = 0;
+ bool required = false;
+ bool primary = false;
+ bool prepared = false;
+ bool eligible = false;
+ FrameProfile profiles[FRAME_PROFILE_MAX] = {};
+ unsigned profileCount = 0;
+ };
+
+ Route routes[FRAME_MAX_SINKS];
+ unsigned routeCount = 0;
+ CfgResult result = CfgResult::ACCEPTED;
+
+ Entry * entries[FRAME_MAX_SINKS] = {};
+ const unsigned count = Entries(entries);
+ for (unsigned i = 0; i < count; ++i)
+ {
+ Entry& entry = *entries[i];
+ bool frameService = false;
+ bool required = false;
+ {
+ CSRWSharedLock entryLock(entry.lock);
+ frameService =
+ (entry.config.services & TRANSPORT_SERVICE_FRAME) != 0;
+ required = entry.required;
+ }
+ if (!frameService)
+ continue;
+
+ if (!BeginCall(entry, Call::CFG, true))
+ {
+ if (required)
+ {
+ result = CfgResult::RETRY;
+ break;
+ }
+ continue;
+ }
+
+ Route& route = routes[routeCount++];
+ route.entry = &entry;
+ State state;
+ bool frameAbsent = false;
+ {
+ CSRWSharedLock entryLock(entry.lock);
+ route.transport = entry.transport;
+ route.id = entry.id;
+ route.epoch = entry.epoch;
+ route.required = entry.required;
+ route.primary = entry.primary;
+ state = entry.state;
+ frameAbsent = entry.frameAbsent;
+ }
+
+ route.eligible = route.transport && !frameAbsent &&
+ (state == State::INITIALIZED || state == State::READY);
+ if (!route.eligible)
+ {
+ if (!route.required &&
+ (frameAbsent || state == State::FAILED))
+ continue;
+ result = frameAbsent ? CfgResult::REJECTED :
+ (state == State::FAILED ? CfgResult::FAILED : CfgResult::RETRY);
+ break;
+ }
+
+ unsigned profileCount = 0;
+ const FrameProfile * profiles =
+ route.transport->Profiles(profileCount);
+ if (profileCount > FRAME_PROFILE_MAX ||
+ (profileCount && !profiles))
+ {
+ route.eligible = false;
+ if (route.required)
+ {
+ result = CfgResult::FAILED;
+ break;
+ }
+ continue;
+ }
+ route.profileCount = profileCount;
+ for (unsigned profile = 0; profile < profileCount; ++profile)
+ route.profiles[profile] = profiles[profile];
+ }
+
+ if (result == CfgResult::ACCEPTED)
+ for (unsigned i = 0; i < routeCount; ++i)
+ {
+ Route& route = routes[i];
+ if (!route.eligible)
+ continue;
+
+ CfgResult routeResult = CfgResult::NEXT;
+ for (unsigned profileIndex = 0;
+ profileIndex < route.profileCount; ++profileIndex)
+ {
+ const FrameProfile& profile = route.profiles[profileIndex];
+ FrameCfg candidate;
+ candidate.mode = cfg.mode;
+ candidate.adapter = cfg.adapter;
+ candidate.width = cfg.width;
+ candidate.height = cfg.height;
+ candidate.profile = profile;
+ if (!next.Can(candidate))
+ continue;
+
+ routeResult = route.transport->Probe(candidate);
+ if (routeResult == CfgResult::NEXT)
+ continue;
+ if (routeResult != CfgResult::ACCEPTED)
+ break;
+
+ routeResult = route.transport->Prepare(candidate);
+ if (routeResult == CfgResult::NEXT)
+ {
+ route.transport->Abort();
+ continue;
+ }
+ if (routeResult != CfgResult::ACCEPTED)
+ break;
+
+ if (!next.Add(route.id, route.epoch, route.required,
+ route.primary, candidate))
+ {
+ route.transport->Abort();
+ routeResult = CfgResult::FAILED;
+ break;
+ }
+ route.prepared = true;
+ break;
+ }
+
+ if (route.prepared)
+ continue;
+
+ route.transport->Abort();
+ if (routeResult == CfgResult::RETRY)
+ {
+ result = CfgResult::RETRY;
+ break;
+ }
+ if (route.required)
+ {
+ result = routeResult == CfgResult::NEXT ?
+ CfgResult::REJECTED : routeResult;
+ break;
+ }
+ }
+
+ if (result == CfgResult::ACCEPTED && !next.Seal())
+ result = CfgResult::FAILED;
+
+ if (result == CfgResult::ACCEPTED)
+ {
+ for (unsigned i = 0; i < routeCount; ++i)
+ if (routes[i].prepared)
+ routes[i].transport->Commit();
+ graph = next;
+ }
+ else
+ for (unsigned i = routeCount; i > 0; --i)
+ if (routes[i - 1].prepared)
+ routes[i - 1].transport->Abort();
+
+ for (unsigned i = routeCount; i > 0; --i)
+ EndCall(*routes[i - 1].entry, routes[i - 1].transport);
+ EndPhase();
+ return result;
+}
+
ITransport::ProcessResult CTransportManager::Process(
ITransportActions& actions)
{
diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h
index 6596364c..61b62820 100644
--- a/idd/LGIdd/transport/CTransportManager.h
+++ b/idd/LGIdd/transport/CTransportManager.h
@@ -33,6 +33,9 @@
static_assert(TRANSPORT_MAX_INSTANCES == FRAME_MAX_SINKS,
"The transport and frame limits must match");
+class CFrameGraph;
+struct GraphCfg;
+
class CTransportManager final : public FrameCaps
{
public:
@@ -51,6 +54,7 @@ private:
OPEN,
INITIALIZE,
SETUP,
+ CFG,
PROCESS,
ACCESS,
STOP,
@@ -71,6 +75,7 @@ private:
{
IDLE,
LIFECYCLE,
+ CFG,
PROCESS,
RECOVERY,
ACCESS,
@@ -168,6 +173,7 @@ public:
OpenResult Open();
bool Initialize();
bool Setup(size_t alignment);
+ CfgResult Cfg(const GraphCfg& cfg, CFrameGraph& graph);
ProcessResult Process(ITransportActions& actions);
void Stop();
void SyncRecovery();
diff --git a/idd/LGIdd/transport/FrameProfile.h b/idd/LGIdd/transport/FrameProfile.h
index 6f3ef406..75f8332b 100644
--- a/idd/LGIdd/transport/FrameProfile.h
+++ b/idd/LGIdd/transport/FrameProfile.h
@@ -23,6 +23,8 @@
#include
#include
+static const unsigned FRAME_PROFILE_MAX = 16;
+
enum class FrameStorage : uint8_t
{
D3D12_TEXTURE,
diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h
index c2ae1766..ec2a13a9 100644
--- a/idd/LGIdd/transport/ITransport.h
+++ b/idd/LGIdd/transport/ITransport.h
@@ -148,8 +148,9 @@ public:
virtual void RecoveryStatus(const SourceKey&, uint64_t, uint32_t,
bool, Recovery, uint32_t) {}
- // Profiles is an immutable, ordered preference list whose pointer remains
- // valid for the lifetime of this instance.
+ // Profiles is an immutable, ordered preference list of at most
+ // FRAME_PROFILE_MAX entries whose pointer remains valid for the lifetime
+ // of this instance.
// Probe has no side effects. Prepare changes pending state only; Commit
// promotes it without failure, while Abort preserves the active route.
virtual const FrameProfile * Profiles(unsigned& count) const = 0;