[idd] capture: define immutable texture products

This commit is contained in:
Geoffrey McRae
2026-08-13 15:12:41 +10:00
parent a8eb49fb17
commit 3dd5abb05e
8 changed files with 816 additions and 16 deletions

View File

@@ -44,6 +44,7 @@
<ClCompile Include="capture\CFrameBufferPool.cpp" />
<ClCompile Include="capture\CFrameBufferResource.cpp" />
<ClCompile Include="capture\CFrameGraph.cpp" />
<ClCompile Include="capture\CFrameTex.cpp" />
<ClCompile Include="capture\CFrameProcessor.cpp" />
<ClCompile Include="capture\CFrameProcessorUtil.cpp" />
<ClCompile Include="capture\CFrameScheduler.cpp" />
@@ -97,6 +98,7 @@
<ClInclude Include="capture\CFrameBufferPool.h" />
<ClInclude Include="capture\CFrameBufferResource.h" />
<ClInclude Include="capture\CFrameGraph.h" />
<ClInclude Include="capture\CFrameTex.h" />
<ClInclude Include="capture\CFrameProcessor.h" />
<ClInclude Include="capture\CFrameProcessorUtil.h" />
<ClInclude Include="capture\CFrameScheduler.h" />

View File

@@ -100,6 +100,9 @@
<ClInclude Include="capture\CFrameGraph.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="capture\CFrameTex.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="capture\CFrameProcessor.h">
<Filter>Capture</Filter>
</ClInclude>
@@ -279,6 +282,9 @@
<ClCompile Include="capture\CFrameGraph.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="capture\CFrameTex.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="capture\CFrameProcessor.cpp">
<Filter>Capture</Filter>
</ClCompile>

View File

@@ -20,6 +20,8 @@
#include "capture/CFrameGraph.h"
#include <cstring>
bool Frame::Same(const GraphCfg& left, const GraphCfg& right)
{
return
@@ -311,13 +313,35 @@ bool CFrameGraph::Want(FrameSignal signal) const
return false;
}
bool CFrameGraph::Desc(unsigned leaf, const FrameDesc& frame,
bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const
{
if (!m_sealed || leaf >= m_leafCount || !frame.serial ||
frame.format.width != m_cfg.srcWidth ||
frame.format.height != m_cfg.srcHeight ||
!Frame::Valid(frame.damage, frame.rects, frame.count,
if (!content)
return false;
const D12FrameFormat& source = content->format;
FrameProfile sourceProfile;
const bool validProfile = D12::Profile(source,
FrameStorage::D3D12_TEXTURE, sourceProfile);
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 ||
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 ||
!Frame::Valid(content->damage, content->rects, content->count,
m_cfg.srcWidth, m_cfg.srcHeight))
return false;
@@ -327,10 +351,32 @@ bool CFrameGraph::Desc(unsigned leaf, const FrameDesc& frame,
desc.epoch = route.epoch;
desc.node = route.node;
desc.profile = route.cfg.profile;
desc.frame = frame;
desc.frame = FrameDesc {};
desc.frame.content = content;
desc.frame.damage = content->damage;
desc.frame.count = content->count;
if (content->count)
memcpy(desc.frame.rects, content->rects,
content->count * sizeof(*content->rects));
desc.frame.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 &&
(node.width != m_cfg.srcWidth || node.height != m_cfg.srcHeight))
{
desc.frame.damage = FrameDamage::FULL;
desc.frame.count = 0;
}
D12FrameFormat& format = desc.frame.format;
return D12::Set(format, node.profile, node.width, node.height);
if (!D12::Set(format, node.profile, node.width, node.height))
return false;
// Calibration/LUT nodes have already consumed this transform.
format.colorTransform.reset();
return true;
}
const GraphNode * CFrameGraph::Nodes(unsigned& count) const

View File

@@ -91,9 +91,9 @@ struct GraphLeaf
FrameCfg cfg;
};
// FrameDesc is resource-free. It can cross graph nodes without retaining the
// acquired IddCx texture and is paired with owned texture storage separately.
struct FrameDesc
// All products derived from one capture retain the same immutable content.
// It contains no acquired IddCx or Direct3D resource.
struct FrameContent
{
uint64_t serial = 0;
uint64_t captureTime = 0;
@@ -103,6 +103,19 @@ struct FrameDesc
D12FrameFormat format = {};
};
using FrameContentRef = std::shared_ptr<const FrameContent>;
// FrameDesc adds one graph node's representation and damage to the shared
// content identity. It remains resource-free.
struct FrameDesc
{
FrameContentRef content;
FrameDamage damage = FrameDamage::NONE;
RECT rects[FRAME_DAMAGE_MAX] = {};
unsigned count = 0;
D12FrameFormat format = {};
};
struct LeafDesc
{
BackendId id = 0;
@@ -140,7 +153,8 @@ public:
bool Same(const GraphCfg& cfg) const;
bool Want(FrameSignal signal) const;
bool Need(FrameOp op) const;
bool Desc(unsigned leaf, const FrameDesc& frame, LeafDesc& desc) const;
bool Desc(unsigned leaf, const FrameContentRef& content,
LeafDesc& desc) const;
const GraphCfg& Cfg() const { return m_cfg; }
const GraphNode * Nodes(unsigned& count) const;

View File

@@ -0,0 +1,537 @@
/**
* 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/CFrameTex.h"
#include "Atomic.h"
#include "CDebug.h"
#include "CSRWLock.h"
#include "Seq.h"
#include <new>
enum class TexState : uint8_t
{
FREE,
WRITE,
LEASED,
WAIT,
DEAD,
};
static void WaitSync(const D12Sync& sync)
{
while (sync.State() == D12SyncState::PENDING)
Sleep(1);
}
static uint64_t NextPool()
{
static std::atomic<uint64_t> next { 0 };
return Atomic::Next(next);
}
static bool ValidFrame(const FrameProfile& profile,
const D3D12_RESOURCE_DESC& resource, const FrameDesc& frame)
{
const D12FrameFormat& format = frame.format;
FrameProfile formatProfile;
const bool validProfile = D12::Profile(format,
FrameStorage::D3D12_TEXTURE, formatProfile);
if (!validProfile || !Frame::Same(formatProfile, profile))
return false;
const bool validDamage = Frame::Valid(frame.damage, frame.rects,
frame.count, format.width, format.height);
return
frame.content &&
frame.content->serial &&
validDamage &&
resource.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D &&
resource.Width == format.width &&
resource.Height == format.height &&
resource.DepthOrArraySize == 1 &&
resource.MipLevels == 1 &&
resource.SampleDesc.Count == 1 &&
D12::Same(resource, format.desc, D12::DescCmp::NO_FLAGS) &&
format.pitch == 0 &&
format.dataWidth == resource.Width &&
format.dataHeight == resource.Height;
}
struct CTexCore
{
struct Slot
{
ComPtr<ID3D12Resource> res;
D12Sync sync;
TexState state = TexState::FREE;
uint64_t gen = 0;
};
mutable CSRWLock lock;
bool open = true;
bool closed = false;
bool failed = false;
uint64_t graph = 0;
uint64_t id = 0;
unsigned node = 0;
unsigned count = 0;
FrameProfile profile;
D3D12_RESOURCE_DESC desc = {};
D3D12_RESOURCE_STATES read = D3D12_RESOURCE_STATE_COMMON;
Slot slots[CTexPool::MAX_SLOTS];
TexResult Acquire(unsigned& index, uint64_t& generation)
{
CSRWExclusiveLock guard(lock);
if (!open || failed)
return TexResult::FAILED;
for (unsigned i = 0; i < count; ++i)
if (slots[i].state == TexState::WAIT)
{
const D12SyncState state = slots[i].sync.State();
if (state == D12SyncState::FAILED)
{
slots[i].state = TexState::DEAD;
failed = true;
return TexResult::FAILED;
}
else if (state == D12SyncState::READY)
{
slots[i].sync = D12Sync {};
slots[i].state = TexState::FREE;
}
}
for (unsigned i = 0; i < count; ++i)
if (slots[i].state == TexState::FREE)
{
Seq::Inc(slots[i].gen);
slots[i].state = TexState::WRITE;
index = i;
generation = slots[i].gen;
return TexResult::OK;
}
for (unsigned i = 0; i < count; ++i)
if (slots[i].state != TexState::DEAD)
return TexResult::BUSY;
return TexResult::FAILED;
}
bool Info(unsigned index, uint64_t generation,
ComPtr<ID3D12Resource>& resource) const
{
CSRWSharedLock guard(lock);
if (!open || failed || index >= count ||
slots[index].gen != generation ||
slots[index].state != TexState::WRITE)
return false;
resource = slots[index].res;
return true;
}
bool Seal(unsigned index, uint64_t generation, const D12Sync& sync)
{
CSRWExclusiveLock guard(lock);
if (!open || failed || index >= count ||
slots[index].gen != generation ||
slots[index].state != TexState::WRITE || !sync.Valid())
return false;
slots[index].sync = sync;
slots[index].state = TexState::LEASED;
return true;
}
void Fail(unsigned index, uint64_t generation, const D12Sync& sync)
{
D12Sync retired;
{
CSRWExclusiveLock guard(lock);
if (index >= count || slots[index].gen != generation ||
slots[index].state != TexState::WRITE)
return;
slots[index].sync = sync;
if (open)
slots[index].state = TexState::WAIT;
else
{
retired = sync;
slots[index].state = TexState::DEAD;
}
}
if (retired.Valid())
WaitSync(retired);
}
void Drop(unsigned index, uint64_t generation)
{
D12Sync retired;
{
CSRWExclusiveLock guard(lock);
if (index >= count || slots[index].gen != generation)
return;
Slot& slot = slots[index];
if (slot.state == TexState::WRITE)
{
slot.state = TexState::DEAD;
return;
}
if (slot.state != TexState::LEASED)
return;
const D12SyncState state = slot.sync.State();
if (!open)
{
retired = slot.sync;
slot.state = TexState::DEAD;
}
else if (state == D12SyncState::FAILED)
{
slot.state = TexState::DEAD;
failed = true;
}
else if (state == D12SyncState::READY)
{
slot.sync = D12Sync {};
slot.state = TexState::FREE;
}
else
slot.state = TexState::WAIT;
}
if (retired.Valid())
WaitSync(retired);
}
void Cancel(unsigned index, uint64_t generation)
{
CSRWExclusiveLock guard(lock);
if (index >= count || slots[index].gen != generation ||
slots[index].state != TexState::WRITE)
return;
slots[index].state = open ? TexState::FREE : TexState::DEAD;
}
void Retire(unsigned index, uint64_t generation)
{
CSRWExclusiveLock guard(lock);
if (index < count && slots[index].gen == generation &&
slots[index].state == TexState::WRITE)
slots[index].state = TexState::DEAD;
}
void Close()
{
D12Sync waits[CTexPool::MAX_SLOTS];
unsigned waitCount = 0;
{
CSRWExclusiveLock guard(lock);
if (closed)
return;
open = false;
closed = true;
for (unsigned i = 0; i < count; ++i)
{
if ((slots[i].state == TexState::LEASED ||
slots[i].state == TexState::WAIT) &&
slots[i].sync.Valid())
waits[waitCount++] = slots[i].sync;
if (slots[i].state != TexState::LEASED &&
slots[i].state != TexState::WRITE)
slots[i].state = TexState::DEAD;
}
}
// Reset is infrequent and cannot release a texture still referenced by
// producer commands. Outstanding consumers keep their own core alive.
for (unsigned i = 0; i < waitCount; ++i)
WaitSync(waits[i]);
}
void Stop()
{
CSRWExclusiveLock guard(lock);
open = false;
}
};
CFrameTex::CFrameTex(uint64_t graphValue, uint64_t poolValue,
unsigned nodeValue, unsigned slotValue, uint64_t versionValue,
const FrameProfile& profileValue,
const FrameDesc& frameValue, const ComPtr<ID3D12Resource>& res,
const D12Sync& sync, D3D12_RESOURCE_STATES state) :
m_res(res),
m_sync(sync),
m_state(state),
graph(graphValue),
pool(poolValue),
node(nodeValue),
slot(slotValue),
version(versionValue),
profile(profileValue),
frame(frameValue)
{
}
bool CFrameTex::Wait(CD3D12CommandSlot& cmd) const
{
if (!m_sync.Valid())
return false;
const UINT64 completed = m_sync.fence->GetCompletedValue();
if (completed == UINT64_MAX)
return false;
if (completed >= m_sync.value)
return true;
return cmd.WaitFor(m_sync.fence.Get(), m_sync.value);
}
TexWrite::TexWrite(const std::shared_ptr<CTexCore>& core,
unsigned index, uint64_t version) :
m_core(core), m_index(index), m_version(version)
{
}
TexWrite::TexWrite(TexWrite&& other) noexcept :
m_core(std::move(other.m_core)),
m_index(other.m_index),
m_version(other.m_version)
{
other.Clear();
}
TexWrite& TexWrite::operator=(TexWrite&& other) noexcept
{
if (this == &other)
return *this;
Retire();
m_core = std::move(other.m_core);
m_index = other.m_index;
m_version = other.m_version;
other.Clear();
return *this;
}
TexWrite::~TexWrite()
{
Retire();
}
void TexWrite::Retire()
{
if (m_core)
m_core->Retire(m_index, m_version);
Clear();
}
void TexWrite::Clear()
{
m_core.reset();
m_index = 0;
m_version = 0;
}
ID3D12Resource * TexWrite::Get() const
{
ComPtr<ID3D12Resource> resource;
return m_core && m_core->Info(m_index, m_version, resource) ?
resource.Get() : nullptr;
}
bool TexWrite::Seal(const FrameDesc& frame, const D12Sync& sync,
TexLease& lease)
{
if (!m_core || !sync.Valid())
return false;
const std::shared_ptr<CTexCore> core = m_core;
ComPtr<ID3D12Resource> resource;
FrameDesc desc = frame;
if (!core->Info(m_index, m_version, resource) ||
!ValidFrame(core->profile, resource->GetDesc(), desc))
{
core->Fail(m_index, m_version, sync);
Clear();
return false;
}
// The graph descriptor is resource-free and canonical. Publish the exact
// pool descriptor, including producer-only UAV creation capability.
desc.format.desc = resource->GetDesc();
const unsigned index = m_index;
const uint64_t generation = m_version;
if (!core->Seal(index, generation, sync))
{
core->Fail(index, generation, sync);
Clear();
return false;
}
CFrameTex * raw = new (std::nothrow) CFrameTex(core->graph, core->id,
core->node, m_index, m_version, core->profile, desc, resource, sync,
core->read);
if (!raw)
{
core->Drop(index, generation);
Clear();
return false;
}
std::shared_ptr<const CFrameTex> product;
try
{
product = std::shared_ptr<const CFrameTex>(raw,
[core, index, generation](const CFrameTex * texture)
{
delete texture;
core->Drop(index, generation);
});
}
catch (const std::bad_alloc&)
{
// The shared_ptr constructor invokes the supplied deleter on failure.
Clear();
return false;
}
Clear();
lease = TexLease(product);
return true;
}
void TexWrite::Cancel()
{
if (m_core)
m_core->Cancel(m_index, m_version);
Clear();
}
TexResult CTexPool::Init(ID3D12Device3 * device, uint64_t graph,
unsigned node, const FrameProfile& profile,
const D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES state,
unsigned slots)
{
if (!device || !graph || !node || !slots || slots > MAX_SLOTS ||
profile.storage != FrameStorage::D3D12_TEXTURE ||
!Frame::Valid(profile) ||
desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
!desc.Width || !desc.Height || desc.Width > UINT_MAX ||
desc.Format != D12::Dxgi(profile.pixel) ||
desc.DepthOrArraySize != 1 ||
desc.MipLevels != 1 ||
desc.SampleDesc.Count != 1 ||
desc.SampleDesc.Quality != 0 ||
desc.Alignment != 0 ||
desc.Layout != D3D12_TEXTURE_LAYOUT_UNKNOWN ||
(static_cast<UINT>(desc.Flags) &
~static_cast<UINT>(D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS |
D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS)) != 0 ||
node >= FRAME_GRAPH_MAX_NODES ||
state != D3D12_RESOURCE_STATE_COMMON)
return TexResult::REJECTED;
std::shared_ptr<CTexCore> core;
try
{
core = std::shared_ptr<CTexCore>(new (std::nothrow) CTexCore);
}
catch (const std::bad_alloc&)
{
return TexResult::FAILED;
}
if (!core)
return TexResult::FAILED;
core->graph = graph;
core->id = NextPool();
core->node = node;
core->count = slots;
core->profile = profile;
core->desc = desc;
core->read = state;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
heap.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap.CreationNodeMask = 1;
heap.VisibleNodeMask = 1;
for (unsigned i = 0; i < slots; ++i)
{
const HRESULT hr = device->CreateCommittedResource(&heap,
D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, &desc, state, nullptr,
IID_PPV_ARGS(&core->slots[i].res));
if (FAILED(hr))
{
DEBUG_ERROR_HR(hr, "Failed to create frame texture");
core->Close();
return TexResult::FAILED;
}
core->slots[i].res->SetName(L"Frame Texture");
}
std::shared_ptr<CTexCore> old;
{
CSRWExclusiveLock guard(m_lock);
old = std::move(m_core);
if (old)
old->Stop();
m_core = core;
}
if (old)
old->Close();
return TexResult::OK;
}
void CTexPool::Reset()
{
std::shared_ptr<CTexCore> core;
{
CSRWExclusiveLock guard(m_lock);
core = std::move(m_core);
if (core)
core->Stop();
}
if (core)
core->Close();
}
TexResult CTexPool::Try(TexWrite& write)
{
if (write)
return TexResult::REJECTED;
std::shared_ptr<CTexCore> core;
{
CSRWSharedLock guard(m_lock);
core = m_core;
}
if (!core)
return TexResult::FAILED;
unsigned index;
uint64_t generation;
const TexResult result = core->Acquire(index, generation);
if (result == TexResult::OK)
write = TexWrite(core, index, generation);
return result;
}

View File

@@ -0,0 +1,158 @@
/**
* 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 "CSRWLock.h"
#include "capture/CFrameGraph.h"
#include "d3d/CD3D12CommandQueue.h"
#include <memory>
struct CTexCore;
class TexWrite;
enum class TexResult : uint8_t
{
OK,
BUSY,
REJECTED,
FAILED,
};
// An immutable, pool-owned D3D12 graph product. Node zero is deliberately
// excluded because it aliases the acquired IddCx surface.
class CFrameTex final
{
friend class TexWrite;
private:
ComPtr<ID3D12Resource> m_res;
D12Sync m_sync;
D3D12_RESOURCE_STATES m_state;
CFrameTex(uint64_t graph, uint64_t pool, unsigned node, unsigned slot,
uint64_t version, const FrameProfile& profile, const FrameDesc& frame,
const ComPtr<ID3D12Resource>& res, const D12Sync& sync,
D3D12_RESOURCE_STATES state);
public:
CFrameTex(const CFrameTex&) = delete;
CFrameTex& operator=(const CFrameTex&) = delete;
const uint64_t graph;
const uint64_t pool;
const unsigned node;
const unsigned slot;
const uint64_t version;
const FrameProfile profile;
const FrameDesc frame;
ID3D12Resource * Get() const { return m_res.Get(); }
const D12Sync& Sync() const { return m_sync; }
D3D12_RESOURCE_STATES State() const { return m_state; }
bool Wait(CD3D12CommandSlot& cmd) const;
D12SyncState Status() const { return m_sync.State(); }
bool Ready() const { return m_sync.Done(); }
};
// Consumers retain a lease until their own GPU use of the texture has
// completed. Dropping the CPU call's local copy is not sufficient.
class TexLease
{
friend class TexWrite;
private:
std::shared_ptr<const CFrameTex> m_tex;
explicit TexLease(std::shared_ptr<const CFrameTex> tex) : m_tex(tex) {}
public:
TexLease() = default;
TexLease(const TexLease&) = default;
TexLease& operator=(const TexLease&) = default;
TexLease(TexLease&&) noexcept = default;
TexLease& operator=(TexLease&&) noexcept = default;
explicit operator bool() const { return !!m_tex; }
const CFrameTex * Get() const { return m_tex.get(); }
const CFrameTex * operator->() const { return m_tex.get(); }
void Reset() { m_tex.reset(); }
};
// A move-only producer reservation. Abandoning a writer retires its slot;
// Cancel may be used only before any command referencing the texture submits.
class TexWrite
{
friend class CTexPool;
private:
std::shared_ptr<CTexCore> m_core;
unsigned m_index = 0;
uint64_t m_version = 0;
TexWrite(const std::shared_ptr<CTexCore>& core,
unsigned index, uint64_t version);
void Clear();
void Retire();
public:
TexWrite() = default;
TexWrite(const TexWrite&) = delete;
TexWrite& operator=(const TexWrite&) = delete;
TexWrite(TexWrite&& other) noexcept;
TexWrite& operator=(TexWrite&& other) noexcept;
~TexWrite();
explicit operator bool() const { return !!m_core; }
ID3D12Resource * Get() const;
// 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 D12Sync& sync, TexLease& lease);
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.
// 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.
class CTexPool
{
private:
mutable CSRWLock m_lock;
std::shared_ptr<CTexCore> m_core;
public:
static const unsigned MAX_SLOTS = 4;
CTexPool() = default;
CTexPool(const CTexPool&) = delete;
CTexPool& operator=(const CTexPool&) = delete;
~CTexPool() { Reset(); }
TexResult Init(ID3D12Device3 * device, uint64_t graph, unsigned node,
const FrameProfile& profile, const D3D12_RESOURCE_DESC& desc,
D3D12_RESOURCE_STATES state, unsigned slots);
void Reset();
TexResult Try(TexWrite& write);
};

View File

@@ -236,8 +236,11 @@ void CD3D12CommandSlot::Cancel()
SetEvent(m_availableEvent.Get());
}
bool CD3D12CommandSlot::Execute()
bool CD3D12CommandSlot::Execute(D12Sync * sync)
{
if (sync)
*sync = D12Sync {};
State expected = STATE_RECORDING;
if (!Atomic::CAS(m_state, expected, STATE_SUBMITTED,
std::memory_order_acq_rel))
@@ -256,7 +259,7 @@ bool CD3D12CommandSlot::Execute()
return false;
}
if (m_queue->Submit(*this))
if (m_queue->Submit(*this, sync))
return true;
if (!Atomic::Load(m_submitted, std::memory_order_acquire))
@@ -572,7 +575,7 @@ void CD3D12CommandQueue::WaitForIdle()
}
}
bool CD3D12CommandQueue::Submit(CD3D12CommandSlot& slot)
bool CD3D12CommandQueue::Submit(CD3D12CommandSlot& slot, D12Sync * sync)
{
bool result = false;
CSRWExclusiveLock lock(m_submitLock);
@@ -612,6 +615,14 @@ bool CD3D12CommandQueue::Submit(CD3D12CommandSlot& slot)
break;
}
// Capture the immutable point while submission is serialized. The slot
// may complete and be acquired again as soon as we leave this scope.
if (sync)
{
sync->fence = m_fence;
sync->value = fenceTarget;
}
hr = m_fence->SetEventOnCompletion(fenceTarget, slot.m_event.Get());
if (FAILED(hr))
{

View File

@@ -35,6 +35,32 @@ using namespace Microsoft::WRL::Wrappers::HandleTraits;
class CD3D12CommandQueue;
enum class D12SyncState : uint8_t
{
PENDING,
READY,
FAILED,
};
struct D12Sync
{
ComPtr<ID3D12Fence> fence;
UINT64 value = 0;
bool Valid() const { return fence && value; }
D12SyncState State() const
{
if (!Valid())
return D12SyncState::FAILED;
const UINT64 completed = fence->GetCompletedValue();
if (completed == UINT64_MAX)
return D12SyncState::FAILED;
return completed >= value ?
D12SyncState::READY : D12SyncState::PENDING;
}
bool Done() const { return State() == D12SyncState::READY; }
};
class CD3D12CommandSlot
{
friend class CD3D12CommandQueue;
@@ -104,7 +130,7 @@ class CD3D12CommandSlot
bool Acquire();
void Cancel();
bool Execute();
bool Execute(D12Sync * sync = nullptr);
bool BeginTiming();
void EndTiming();
@@ -162,7 +188,7 @@ class CD3D12CommandQueue
bool m_timingSupported = false;
bool InitTiming(ID3D12Device3 * device, UINT slotCount);
bool Submit(CD3D12CommandSlot& slot);
bool Submit(CD3D12CommandSlot& slot, D12Sync * sync);
bool SnapshotTiming(CD3D12CommandSlot& slot) const;
bool GetGPUTimes(const CD3D12CommandSlot& slot,
uint64_t& start, uint64_t& end) const;