From 43d918b5f3dcd621970f8d9387adb41211d6a0f1 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 13 Aug 2026 13:43:32 +1000 Subject: [PATCH] [idd] capture: build frame processing graph --- idd/LGIdd/LGIdd.vcxproj | 2 + idd/LGIdd/LGIdd.vcxproj.filters | 6 + idd/LGIdd/capture/CFrameGraph.cpp | 286 ++++++++++++++++++++++ idd/LGIdd/capture/CFrameGraph.h | 142 +++++++++++ idd/LGIdd/capture/CSwapChainProcessor.cpp | 75 ++++++ idd/LGIdd/capture/CSwapChainProcessor.h | 9 + idd/LGIdd/postprocess/CPostProcessor.cpp | 6 + idd/LGIdd/postprocess/CPostProcessor.h | 2 + idd/LGIdd/postprocess/D12FrameFormat.cpp | 90 +++++++ idd/LGIdd/postprocess/D12FrameFormat.h | 10 + idd/LGIdd/transport/CTransportManager.cpp | 185 ++++++++++++++ idd/LGIdd/transport/CTransportManager.h | 6 + idd/LGIdd/transport/FrameProfile.h | 2 + idd/LGIdd/transport/ITransport.h | 5 +- 14 files changed, 824 insertions(+), 2 deletions(-) create mode 100644 idd/LGIdd/capture/CFrameGraph.cpp create mode 100644 idd/LGIdd/capture/CFrameGraph.h 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;