/** * 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 ValidTime(const FrameTime& time) { if (!time.postStart || (!time.gpuValid && (time.gpuStart || time.gpuEnd)) || (!time.readyValid && time.readyAt)) return false; if (time.gpuValid && (!time.gpuStart || time.gpuStart < time.postStart || time.gpuEnd < time.gpuStart)) return false; if (time.readyValid && (!time.readyAt || time.readyAt < (time.gpuValid ? time.gpuEnd : time.postStart))) return false; return true; } 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; bool shared = false; 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 FrameTime& timeValue, const ComPtr& res, const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue, const std::shared_ptr& hold) : m_res(res), m_sync(sync), m_state(state), m_shared(sharedValue), m_hold(hold), graph(graphValue), pool(poolValue), node(nodeValue), slot(slotValue), version(versionValue), profile(profileValue), frame(frameValue), time(timeValue) { } 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 FrameTime& time, const D12Sync& sync, TexLease& lease, const std::shared_ptr& hold) { if (!m_core || !sync.Valid()) return false; const std::shared_ptr core = m_core; if (!ValidTime(time)) { core->Fail(m_index, m_version, sync); Clear(); return false; } 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, time, resource, sync, core->read, core->shared, hold); 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::Fail(const D12Sync& sync) { if (m_core) m_core->Fail(m_index, m_version, sync); Clear(); } 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, bool shared) { 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; core->shared = shared; 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 D3D12_HEAP_FLAGS flags = shared ? static_cast(D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) : D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; const HRESULT hr = device->CreateCommittedResource(&heap, flags, &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; }