diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index ff0c48c7..76eba21a 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -44,6 +44,7 @@ + @@ -97,6 +98,7 @@ + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index 8a651f56..29f7ab2e 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -100,6 +100,9 @@ Capture + + Capture + Capture @@ -279,6 +282,9 @@ Capture + + Capture + Capture diff --git a/idd/LGIdd/capture/CFrameGraph.cpp b/idd/LGIdd/capture/CFrameGraph.cpp index 2ec5f70a..8dda16a1 100644 --- a/idd/LGIdd/capture/CFrameGraph.cpp +++ b/idd/LGIdd/capture/CFrameGraph.cpp @@ -20,6 +20,8 @@ #include "capture/CFrameGraph.h" +#include + 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 diff --git a/idd/LGIdd/capture/CFrameGraph.h b/idd/LGIdd/capture/CFrameGraph.h index 9e2ba60a..1f4f78e7 100644 --- a/idd/LGIdd/capture/CFrameGraph.h +++ b/idd/LGIdd/capture/CFrameGraph.h @@ -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; + +// 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; diff --git a/idd/LGIdd/capture/CFrameTex.cpp b/idd/LGIdd/capture/CFrameTex.cpp new file mode 100644 index 00000000..c3dcbb5f --- /dev/null +++ b/idd/LGIdd/capture/CFrameTex.cpp @@ -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 + +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 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 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& 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& 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& 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 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 core = m_core; + ComPtr 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 product; + try + { + product = std::shared_ptr(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(desc.Flags) & + ~static_cast(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 core; + try + { + core = std::shared_ptr(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 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 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 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; +} diff --git a/idd/LGIdd/capture/CFrameTex.h b/idd/LGIdd/capture/CFrameTex.h new file mode 100644 index 00000000..7a6dee31 --- /dev/null +++ b/idd/LGIdd/capture/CFrameTex.h @@ -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 + +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 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& 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 m_tex; + explicit TexLease(std::shared_ptr 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 m_core; + unsigned m_index = 0; + uint64_t m_version = 0; + + TexWrite(const std::shared_ptr& 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 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); +}; diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp index 0e205db7..fd7f3a16 100644 --- a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp +++ b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp @@ -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)) { diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.h b/idd/LGIdd/d3d/CD3D12CommandQueue.h index 24f9bb0d..7ed20f0b 100644 --- a/idd/LGIdd/d3d/CD3D12CommandQueue.h +++ b/idd/LGIdd/d3d/CD3D12CommandQueue.h @@ -35,6 +35,32 @@ using namespace Microsoft::WRL::Wrappers::HandleTraits; class CD3D12CommandQueue; +enum class D12SyncState : uint8_t +{ + PENDING, + READY, + FAILED, +}; + +struct D12Sync +{ + ComPtr 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;