[idd] capture: execute texture frame graphs

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

View File

@@ -43,6 +43,7 @@
<ClCompile Include="display\CMonitorContext.cpp" /> <ClCompile Include="display\CMonitorContext.cpp" />
<ClCompile Include="capture\CFrameBufferPool.cpp" /> <ClCompile Include="capture\CFrameBufferPool.cpp" />
<ClCompile Include="capture\CFrameBufferResource.cpp" /> <ClCompile Include="capture\CFrameBufferResource.cpp" />
<ClCompile Include="capture\CFrameExec.cpp" />
<ClCompile Include="capture\CFrameGraph.cpp" /> <ClCompile Include="capture\CFrameGraph.cpp" />
<ClCompile Include="capture\CFrameTex.cpp" /> <ClCompile Include="capture\CFrameTex.cpp" />
<ClCompile Include="capture\CFrameProcessor.cpp" /> <ClCompile Include="capture\CFrameProcessor.cpp" />
@@ -62,6 +63,7 @@
<ClCompile Include="postprocess\effect\CColorTransformEffect.cpp" /> <ClCompile Include="postprocess\effect\CColorTransformEffect.cpp" />
<ClCompile Include="postprocess\effect\CComputeEffect.cpp" /> <ClCompile Include="postprocess\effect\CComputeEffect.cpp" />
<ClCompile Include="postprocess\effect\CDownsampleEffect.cpp" /> <ClCompile Include="postprocess\effect\CDownsampleEffect.cpp" />
<ClCompile Include="postprocess\effect\CFormatEffect.cpp" />
<ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp" /> <ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp" />
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp" /> <ClCompile Include="postprocess\effect\CRGB24Effect.cpp" />
<ClCompile Include="transport\CControlHub.cpp" /> <ClCompile Include="transport\CControlHub.cpp" />
@@ -99,6 +101,7 @@
<ClInclude Include="display\CMonitorContext.h" /> <ClInclude Include="display\CMonitorContext.h" />
<ClInclude Include="capture\CFrameBufferPool.h" /> <ClInclude Include="capture\CFrameBufferPool.h" />
<ClInclude Include="capture\CFrameBufferResource.h" /> <ClInclude Include="capture\CFrameBufferResource.h" />
<ClInclude Include="capture\CFrameExec.h" />
<ClInclude Include="capture\CFrameGraph.h" /> <ClInclude Include="capture\CFrameGraph.h" />
<ClInclude Include="capture\CFrameTex.h" /> <ClInclude Include="capture\CFrameTex.h" />
<ClInclude Include="capture\CFrameProcessor.h" /> <ClInclude Include="capture\CFrameProcessor.h" />
@@ -119,6 +122,7 @@
<ClInclude Include="postprocess\effect\CColorTransformEffect.h" /> <ClInclude Include="postprocess\effect\CColorTransformEffect.h" />
<ClInclude Include="postprocess\effect\CComputeEffect.h" /> <ClInclude Include="postprocess\effect\CComputeEffect.h" />
<ClInclude Include="postprocess\effect\CDownsampleEffect.h" /> <ClInclude Include="postprocess\effect\CDownsampleEffect.h" />
<ClInclude Include="postprocess\effect\CFormatEffect.h" />
<ClInclude Include="postprocess\effect\CHDR16to10Effect.h" /> <ClInclude Include="postprocess\effect\CHDR16to10Effect.h" />
<ClInclude Include="postprocess\effect\CRGB24Effect.h" /> <ClInclude Include="postprocess\effect\CRGB24Effect.h" />
<ClInclude Include="transport\CControlHub.h" /> <ClInclude Include="transport\CControlHub.h" />

View File

@@ -97,6 +97,9 @@
<ClInclude Include="capture\CFrameBufferResource.h"> <ClInclude Include="capture\CFrameBufferResource.h">
<Filter>Capture</Filter> <Filter>Capture</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="capture\CFrameExec.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="capture\CFrameGraph.h"> <ClInclude Include="capture\CFrameGraph.h">
<Filter>Capture</Filter> <Filter>Capture</Filter>
</ClInclude> </ClInclude>
@@ -157,6 +160,9 @@
<ClInclude Include="postprocess\effect\CDownsampleEffect.h"> <ClInclude Include="postprocess\effect\CDownsampleEffect.h">
<Filter>Post-processing\Effects</Filter> <Filter>Post-processing\Effects</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="postprocess\effect\CFormatEffect.h">
<Filter>Post-processing\Effects</Filter>
</ClInclude>
<ClInclude Include="postprocess\effect\CHDR16to10Effect.h"> <ClInclude Include="postprocess\effect\CHDR16to10Effect.h">
<Filter>Post-processing\Effects</Filter> <Filter>Post-processing\Effects</Filter>
</ClInclude> </ClInclude>
@@ -294,6 +300,9 @@
<ClCompile Include="capture\CFrameBufferResource.cpp"> <ClCompile Include="capture\CFrameBufferResource.cpp">
<Filter>Capture</Filter> <Filter>Capture</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="capture\CFrameExec.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="capture\CFrameGraph.cpp"> <ClCompile Include="capture\CFrameGraph.cpp">
<Filter>Capture</Filter> <Filter>Capture</Filter>
</ClCompile> </ClCompile>
@@ -351,6 +360,9 @@
<ClCompile Include="postprocess\effect\CDownsampleEffect.cpp"> <ClCompile Include="postprocess\effect\CDownsampleEffect.cpp">
<Filter>Post-processing\Effects</Filter> <Filter>Post-processing\Effects</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="postprocess\effect\CFormatEffect.cpp">
<Filter>Post-processing\Effects</Filter>
</ClCompile>
<ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp"> <ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp">
<Filter>Post-processing\Effects</Filter> <Filter>Post-processing\Effects</Filter>
</ClCompile> </ClCompile>

View File

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

View File

@@ -0,0 +1,71 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "Atomic.h"
#include "CSRWLock.h"
#include "capture/CFrameGraph.h"
#include "capture/CFrameTex.h"
#include "transport/ITexStage.h"
#include <memory>
class CInteropResource;
class CTexHub;
struct CD3D11Device;
struct CD3D12Device;
// Transactional executor for texture-backed transport routes. Every product
// is materialized in executor-owned storage before the acquired IddCx surface
// is released. A graph containing only legacy routes remains fully dormant.
class CFrameExec final : public ITexStage
{
private:
struct Core;
mutable CSRWLock m_lock;
std::shared_ptr<CD3D11Device> m_d11;
std::shared_ptr<CD3D12Device> m_d12;
CTexHub * m_hub = nullptr;
std::shared_ptr<Core> m_active;
std::shared_ptr<Core> m_pending;
std::atomic<uint64_t> m_serial { 0 };
uint64_t NextSerial();
public:
CFrameExec() = default;
~CFrameExec();
CFrameExec(const CFrameExec&) = delete;
CFrameExec& operator=(const CFrameExec&) = delete;
bool Init(const std::shared_ptr<CD3D11Device>& d11,
const std::shared_ptr<CD3D12Device>& d12, CTexHub& hub);
void Reset();
CfgResult Prep(const CFrameGraph& graph) noexcept override;
void Commit() noexcept override;
void Abort() noexcept override;
TexResult Run(CInteropResource& src, const D12FrameFormat& format,
uint64_t captureTime, uint64_t postStart, FrameDamage damage,
const RECT * rects, unsigned count) noexcept;
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -161,6 +161,12 @@ bool CSwapChainProcessor::InitializePipeline()
return false; return false;
m_resPool.Init(m_dx11Device, m_dx12Device); 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(); const bool enableEffects = !m_dx11Device->IsSoftware();
if (!enableEffects) if (!enableEffects)
DEBUG_INFO("Software render adapter: post-processing disabled"); DEBUG_INFO("Software render adapter: post-processing disabled");
@@ -233,6 +239,7 @@ CSwapChainProcessor::~CSwapChainProcessor()
// Drain in-flight GPU work / completion callbacks before releasing the // Drain in-flight GPU work / completion callbacks before releasing the
// resources they reference. The swap chain was already released in the // resources they reference. The swap chain was already released in the
// worker epilogue, so this does not hold an IddCx frame. // worker epilogue, so this does not hold an IddCx frame.
m_exec.Reset();
if (m_dx12Device) if (m_dx12Device)
{ {
m_dx12Device->WaitForIdle(); m_dx12Device->WaitForIdle();
@@ -565,7 +572,7 @@ void CSwapChainProcessor::CfgGraph()
m_graphPending = false; m_graphPending = false;
const CfgResult result = 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) if (result == CfgResult::ACCEPTED)
{ {
m_graphRetryAt = 0; m_graphRetryAt = 0;
@@ -659,6 +666,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
D12FrameFormat srcFormat = {}; D12FrameFormat srcFormat = {};
srcFormat.desc = srcDesc; srcFormat.desc = srcDesc;
srcFormat.dataWidth = (unsigned)srcDesc.Width;
srcFormat.dataHeight = srcDesc.Height;
srcFormat.width = (unsigned)srcDesc.Width; srcFormat.width = (unsigned)srcDesc.Width;
srcFormat.height = srcDesc.Height; srcFormat.height = srcDesc.Height;
srcFormat.format = D12::Type(srcDesc.Format); srcFormat.format = D12::Type(srcDesc.Format);
@@ -825,6 +834,22 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged)
m_transport.ForceFrame(); 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 = const FrameSubmission submission =
{ {
srcRes, srcRes,

View File

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

View File

@@ -507,6 +507,21 @@ void CD3D12CommandQueue::DeInit()
m_qpcFrequency = 0; 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) CD3D12CommandSlot * CD3D12CommandQueue::Acquire(UINT slotIndex)
{ {
if (slotIndex >= m_slotCount) if (slotIndex >= m_slotCount)
@@ -515,14 +530,8 @@ CD3D12CommandSlot * CD3D12CommandQueue::Acquire(UINT slotIndex)
const ULONGLONG deadline = GetTickCount64() + 100; const ULONGLONG deadline = GetTickCount64() + 100;
for (;;) for (;;)
{ {
if (m_slots[slotIndex].Acquire()) if (CD3D12CommandSlot * slot = Try(slotIndex))
return &m_slots[slotIndex]; return slot;
// 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 (Atomic::Load(m_failed, std::memory_order_acquire)) if (Atomic::Load(m_failed, std::memory_order_acquire))
break; break;
@@ -549,16 +558,7 @@ CD3D12CommandSlot * CD3D12CommandQueue::Acquire()
// The unindexed path is used for immediate software publication. Keep at // 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 // most one copy in flight so a newer frame is dropped instead of queued
// behind bandwidth-bound work which is already stale. // behind bandwidth-bound work which is already stale.
CD3D12CommandSlot& slot = m_slots[0]; return Try(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;
} }
void CD3D12CommandQueue::WaitForIdle() void CD3D12CommandQueue::WaitForIdle()

View File

@@ -201,7 +201,12 @@ class CD3D12CommandQueue
UINT slotCount, bool enableTiming = false, bool sharedFence = false); UINT slotCount, bool enableTiming = false, bool sharedFence = false);
void DeInit(); void DeInit();
CD3D12CommandSlot * Try(UINT slotIndex);
CD3D12CommandSlot * Acquire(UINT slotIndex); CD3D12CommandSlot * Acquire(UINT slotIndex);
CD3D12CommandSlot * Acquire(); CD3D12CommandSlot * Acquire();
bool Failed() const
{
return Atomic::Load(m_failed, std::memory_order_acquire);
}
void WaitForIdle(); void WaitForIdle();
}; };

View File

@@ -165,6 +165,20 @@ bool CColorTransformEffect::Init(const ComPtr<ID3D12Device3>& device)
PostProcessStatus CColorTransformEffect::SetFormat( PostProcessStatus CColorTransformEffect::SetFormat(
const ComPtr<ID3D12Device3>& device, const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, true);
}
PostProcessStatus CColorTransformEffect::Cfg(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, false);
}
PostProcessStatus CColorTransformEffect::Set(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{ {
const auto transform = D12::Transform(src.colorTransform); const auto transform = D12::Transform(src.colorTransform);
if (!transform) if (!transform)
@@ -205,10 +219,15 @@ PostProcessStatus CColorTransformEffect::SetFormat(
D3D12_RESOURCE_DESC desc = src.desc; D3D12_RESOURCE_DESC desc = src.desc;
desc.Format = dstFormat; desc.Format = dstFormat;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; desc.Flags = own ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS :
if (!m_dst || m_dst->GetDesc().Width != desc.Width || 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().Height != desc.Height ||
m_dst->GetDesc().Format != desc.Format) m_dst->GetDesc().Format != desc.Format))
{ {
if (!CreateDefaultTexture(device, desc, m_dst)) if (!CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED; return PostProcessStatus::FAILED;
@@ -230,6 +249,7 @@ PostProcessStatus CColorTransformEffect::SetFormat(
m_srcFormat = src.desc.Format; m_srcFormat = src.desc.Format;
m_dstFormat = dstFormat; m_dstFormat = dstFormat;
m_outDesc = desc;
m_threadsX = Groups((unsigned)desc.Width); m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height); m_threadsY = Groups(desc.Height);
@@ -245,6 +265,30 @@ ComPtr<ID3D12Resource> CColorTransformEffect::Run(
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) unsigned * nbDirtyRects)
{
if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
dirtyRects, nbDirtyRects))
return nullptr;
return m_dst;
}
bool CColorTransformEffect::Run(
const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects)
{ {
UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects);
@@ -261,7 +305,7 @@ ComPtr<ID3D12Resource> CColorTransformEffect::Run(
m_uploadPending = false; m_uploadPending = false;
} }
TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts)); CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
@@ -275,10 +319,10 @@ ComPtr<ID3D12Resource> CColorTransformEffect::Run(
device->CreateShaderResourceView( device->CreateShaderResourceView(
m_lutBuffer.Get(), &lutDesc, Handle(device, 2)); m_lutBuffer.Get(), &lutDesc, Handle(device, 2));
UAV(device, 3, m_dst.Get(), m_dstFormat); UAV(device, 3, dst, m_dstFormat);
Dispatch(commandList); Dispatch(commandList);
TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON); D3D12_RESOURCE_STATE_COMMON);
return m_dst; return true;
} }

View File

@@ -51,6 +51,13 @@ private:
CalPart m_part; CalPart m_part;
bool m_keepSignal; bool m_keepSignal;
PostProcessStatus Set(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own);
bool Draw(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
public: public:
explicit CColorTransformEffect(CalPart part = CalPart::ALL, explicit CColorTransformEffect(CalPart part = CalPart::ALL,
bool keepSignal = false) : bool keepSignal = false) :
@@ -62,9 +69,17 @@ public:
PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device, PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) override; const D12FrameFormat& src, D12FrameFormat& dst) override;
// Configures shader state without allocating the legacy-owned output.
PostProcessStatus Cfg(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst);
ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device, ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override; unsigned * nbDirtyRects) override;
// dst must match Cfg's output and be in COMMON; Run restores COMMON.
bool Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
}; };

View File

@@ -280,11 +280,27 @@ void CComputeEffect::UAV(const ComPtr<ID3D12Device3>& device, UINT index,
void CComputeEffect::TransitionDst( void CComputeEffect::TransitionDst(
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) 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<ID3D12GraphicsCommandList>& commandList,
ID3D12Resource * dst, D3D12_RESOURCE_STATES before,
D3D12_RESOURCE_STATES after)
{ {
D3D12_RESOURCE_BARRIER barrier = {}; D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = m_dst.Get(); barrier.Transition.pResource = dst;
barrier.Transition.StateBefore = before; barrier.Transition.StateBefore = before;
barrier.Transition.StateAfter = after; barrier.Transition.StateAfter = after;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

View File

@@ -61,6 +61,7 @@ protected:
ComPtr<ID3D12PipelineState> m_pso; ComPtr<ID3D12PipelineState> m_pso;
ComPtr<ID3D12DescriptorHeap> m_descHeap; ComPtr<ID3D12DescriptorHeap> m_descHeap;
ComPtr<ID3D12Resource> m_dst; ComPtr<ID3D12Resource> m_dst;
D3D12_RESOURCE_DESC m_outDesc = {};
unsigned m_threadsX = 0; unsigned m_threadsX = 0;
unsigned m_threadsY = 0; unsigned m_threadsY = 0;
@@ -81,6 +82,11 @@ protected:
void UAV(const ComPtr<ID3D12Device3>& device, UINT index, void UAV(const ComPtr<ID3D12Device3>& device, UINT index,
ID3D12Resource * resource, DXGI_FORMAT format) const; ID3D12Resource * resource, DXGI_FORMAT format) const;
bool IsDst(ID3D12Resource * dst) const;
void TransitionDst(const ComPtr<ID3D12GraphicsCommandList>& commandList, void TransitionDst(const ComPtr<ID3D12GraphicsCommandList>& commandList,
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after); D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after);
void TransitionDst(const ComPtr<ID3D12GraphicsCommandList>& commandList,
ID3D12Resource * dst, D3D12_RESOURCE_STATES before,
D3D12_RESOURCE_STATES after);
}; };

View File

@@ -156,6 +156,20 @@ bool CDownsampleEffect::Init(const ComPtr<ID3D12Device3>& device, bool report)
PostProcessStatus CDownsampleEffect::SetFormat( PostProcessStatus CDownsampleEffect::SetFormat(
const ComPtr<ID3D12Device3>& device, const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, true);
}
PostProcessStatus CDownsampleEffect::Cfg(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, false);
}
PostProcessStatus CDownsampleEffect::Set(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{ {
unsigned targetX = m_targetX; unsigned targetX = m_targetX;
unsigned targetY = m_targetY; unsigned targetY = m_targetY;
@@ -174,9 +188,12 @@ PostProcessStatus CDownsampleEffect::SetFormat(
D3D12_RESOURCE_DESC desc = src.desc; D3D12_RESOURCE_DESC desc = src.desc;
desc.Width = targetX; desc.Width = targetX;
desc.Height = targetY; 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; return PostProcessStatus::FAILED;
m_consts.width = (float)targetX; m_consts.width = (float)targetX;
@@ -190,6 +207,7 @@ PostProcessStatus CDownsampleEffect::SetFormat(
} }
m_threadsX = Groups((unsigned)desc.Width); m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height); m_threadsY = Groups(desc.Height);
m_outDesc = desc;
m_format = src.desc.Format; m_format = src.desc.Format;
m_scaleX = (double)desc.Width / src.desc.Width; m_scaleX = (double)desc.Width / src.desc.Width;
m_scaleY = (double)desc.Height / src.desc.Height; m_scaleY = (double)desc.Height / src.desc.Height;
@@ -225,19 +243,43 @@ ComPtr<ID3D12Resource> CDownsampleEffect::Run(
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) unsigned * nbDirtyRects)
{
if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
dirtyRects, nbDirtyRects))
return nullptr;
return m_dst;
}
bool CDownsampleEffect::Run(
const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects)
{ {
UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects);
TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts)); CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
SRV(device, 1, src.Get(), m_format); SRV(device, 1, src.Get(), m_format);
UAV(device, 2, m_dst.Get(), m_format); UAV(device, 2, dst, m_format);
Dispatch(commandList); Dispatch(commandList);
TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON); D3D12_RESOURCE_STATE_COMMON);
return m_dst; return true;
} }

View File

@@ -55,6 +55,12 @@ private:
bool ParseRules(const std::wstring& value, bool report); bool ParseRules(const std::wstring& value, bool report);
const Rule * MatchRule(unsigned width, unsigned height) const; const Rule * MatchRule(unsigned width, unsigned height) const;
PostProcessStatus Set(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own);
bool Draw(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
public: public:
CDownsampleEffect() = default; CDownsampleEffect() = default;
@@ -67,6 +73,9 @@ public:
PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device, PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) override; const D12FrameFormat& src, D12FrameFormat& dst) override;
// Configures shader state without allocating the legacy-owned output.
PostProcessStatus Cfg(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst);
void AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) override; void AdjustDamage(RECT dirtyRects[], unsigned * nbDirtyRects) override;
@@ -74,4 +83,9 @@ public:
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override; unsigned * nbDirtyRects) override;
// dst must match Cfg's output and be in COMMON; Run restores COMMON.
bool Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
}; };

View File

@@ -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<ID3D12Device3>& device)
{
D3D12_DESCRIPTOR_RANGE ranges[] =
{
Range(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0),
Range(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0),
};
const char * shader =
"Texture2D<float4> src : register(t0);\n"
"RWTexture2D<float4> 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<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst)
{
return Cfg(device, src, dst);
}
PostProcessStatus CFormatEffect::Cfg(
const ComPtr<ID3D12Device3>& 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<ID3D12Resource> CFormatEffect::Run(
const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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;
}

View File

@@ -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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
public:
const char * GetName() const override { return "Format"; }
bool Init(const ComPtr<ID3D12Device3>& device);
PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) override;
PostProcessStatus Cfg(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, const D12FrameFormat& dst);
ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override;
// dst must match Cfg's output and be in COMMON; Run restores COMMON.
bool Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
};

View File

@@ -87,17 +87,35 @@ bool CHDR16to10Effect::Init(const ComPtr<ID3D12Device3>& device)
PostProcessStatus CHDR16to10Effect::SetFormat( PostProcessStatus CHDR16to10Effect::SetFormat(
const ComPtr<ID3D12Device3>& device, const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, true);
}
PostProcessStatus CHDR16to10Effect::Cfg(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst)
{
return Set(device, src, dst, false);
}
PostProcessStatus CHDR16to10Effect::Set(
const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own)
{ {
if (src.desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT || !src.hdr) if (src.desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT || !src.hdr)
return PostProcessStatus::BYPASS_EFFECT; return PostProcessStatus::BYPASS_EFFECT;
D3D12_RESOURCE_DESC desc = src.desc; D3D12_RESOURCE_DESC desc = src.desc;
desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; desc.Flags = own ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS :
dst.desc.Flags;
if (!CreateDefaultTexture(device, desc, m_dst)) if (!(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
return PostProcessStatus::FAILED; return PostProcessStatus::FAILED;
if (own && !CreateDefaultTexture(device, desc, m_dst))
return PostProcessStatus::FAILED;
m_outDesc = desc;
m_threadsX = Groups((unsigned)desc.Width); m_threadsX = Groups((unsigned)desc.Width);
m_threadsY = Groups(desc.Height); m_threadsY = Groups(desc.Height);
@@ -118,19 +136,43 @@ ComPtr<ID3D12Resource> CHDR16to10Effect::Run(
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) unsigned * nbDirtyRects)
{
if (!m_dst || !Draw(device, commandList, src, m_dst.Get(),
dirtyRects, nbDirtyRects))
return nullptr;
return m_dst;
}
bool CHDR16to10Effect::Run(
const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& 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<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects)
{ {
UNREFERENCED_PARAMETER(dirtyRects); UNREFERENCED_PARAMETER(dirtyRects);
UNREFERENCED_PARAMETER(nbDirtyRects); UNREFERENCED_PARAMETER(nbDirtyRects);
TransitionDst(commandList, D3D12_RESOURCE_STATE_COMMON, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS); D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts)); CBV(device, 0, m_constBuffer.Get(), sizeof(m_consts));
SRV(device, 1, src.Get(), DXGI_FORMAT_R16G16B16A16_FLOAT); 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); Dispatch(commandList);
TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, TransitionDst(commandList, dst, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON); D3D12_RESOURCE_STATE_COMMON);
return m_dst; return true;
} }

View File

@@ -31,6 +31,13 @@ private:
} m_consts = { 80.0f }; } m_consts = { 80.0f };
ComPtr<ID3D12Resource> m_constBuffer; ComPtr<ID3D12Resource> m_constBuffer;
PostProcessStatus Set(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst, bool own);
bool Draw(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
public: public:
const char * GetName() const override { return "HDR16to10"; } const char * GetName() const override { return "HDR16to10"; }
@@ -38,9 +45,17 @@ public:
PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device, PostProcessStatus SetFormat(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst) override; const D12FrameFormat& src, D12FrameFormat& dst) override;
// Configures shader state without allocating the legacy-owned output.
PostProcessStatus Cfg(const ComPtr<ID3D12Device3>& device,
const D12FrameFormat& src, D12FrameFormat& dst);
ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device, ComPtr<ID3D12Resource> Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList, const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, RECT dirtyRects[], const ComPtr<ID3D12Resource>& src, RECT dirtyRects[],
unsigned * nbDirtyRects) override; unsigned * nbDirtyRects) override;
// dst must match Cfg's output and be in COMMON; Run restores COMMON.
bool Run(const ComPtr<ID3D12Device3>& device,
const ComPtr<ID3D12GraphicsCommandList>& commandList,
const ComPtr<ID3D12Resource>& src, ID3D12Resource * dst,
RECT dirtyRects[], unsigned * nbDirtyRects);
}; };

View File

@@ -490,6 +490,24 @@ void CTexHub::Abort(CTexStage& stage) noexcept
drop.reset(); drop.reset();
} }
void CTexHub::Fault(
const FrameIn& frame, PushResult result) noexcept
{
if (result != PushResult::REJECTED && result != PushResult::FAILED)
return;
std::shared_ptr<CTexSet> 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 PushResult CTexHub::Push(FrameIn frame, TexLease lease) noexcept
{ {
std::shared_ptr<CTexSet> set; std::shared_ptr<CTexSet> set;

View File

@@ -102,7 +102,6 @@ private:
static bool Match(const FaultRec& fault, BackendId id, uint32_t epoch, static bool Match(const FaultRec& fault, BackendId id, uint32_t epoch,
const FrameCfg& cfg, bool anyCfg = false); const FrameCfg& cfg, bool anyCfg = false);
bool Enter(const FrameIn& frame, bool lease, bool Enter(const FrameIn& frame, bool lease,
std::shared_ptr<CTexSet>& set, unsigned& route, ITexSink *& sink); std::shared_ptr<CTexSet>& set, unsigned& route, ITexSink *& sink);
void Finish(const std::shared_ptr<CTexSet>& set, unsigned route, void Finish(const std::shared_ptr<CTexSet>& set, unsigned route,
@@ -135,6 +134,7 @@ private:
void Stop(); void Stop();
public: public:
void Fault(const FrameIn& frame, PushResult result) noexcept;
PushResult Push(FrameIn frame, TexLease lease) noexcept; PushResult Push(FrameIn frame, TexLease lease) noexcept;
PushResult Push(FrameIn frame, D11Lease lease) noexcept; PushResult Push(FrameIn frame, D11Lease lease) noexcept;
}; };

View File

@@ -1254,7 +1254,7 @@ CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
break; break;
if (!next.Add(route.id, route.epoch, route.required, if (!next.Add(route.id, route.epoch, route.required,
route.primary, candidate)) route.primary, route.texSink != nullptr, candidate))
{ {
route.transport->Abort(); route.transport->Abort();
routeResult = CfgResult::FAILED; routeResult = CfgResult::FAILED;