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;