From ba7a037f2348adf326f616d8b4b975590f8b3a84 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 13 Aug 2026 15:42:24 +1000 Subject: [PATCH] [idd] d3d: add leased D3D11 texture interop --- idd/LGIdd/LGIdd.vcxproj | 2 + idd/LGIdd/LGIdd.vcxproj.filters | 6 + idd/LGIdd/capture/CFrameGraph.cpp | 11 + idd/LGIdd/capture/CFrameGraph.h | 1 + idd/LGIdd/capture/CFrameTex.cpp | 19 +- idd/LGIdd/capture/CFrameTex.h | 6 +- idd/LGIdd/d3d/CD3D11Device.cpp | 4 +- idd/LGIdd/d3d/CD3D11Device.h | 12 +- idd/LGIdd/d3d/CD3D12CommandQueue.cpp | 6 +- idd/LGIdd/d3d/CD3D12CommandQueue.h | 2 +- idd/LGIdd/d3d/CD3D12Device.cpp | 4 +- idd/LGIdd/d3d/CD3D12Device.h | 1 + idd/LGIdd/d3d/CInteropPool.cpp | 407 +++++++++++++++++++++++++++ idd/LGIdd/d3d/CInteropPool.h | 102 +++++++ 14 files changed, 566 insertions(+), 17 deletions(-) create mode 100644 idd/LGIdd/d3d/CInteropPool.cpp create mode 100644 idd/LGIdd/d3d/CInteropPool.h diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 76eba21a..7a2e73af 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -51,6 +51,7 @@ + @@ -106,6 +107,7 @@ + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index 29f7ab2e..c5ef57c4 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -124,6 +124,9 @@ Capture + + D3D + D3D @@ -303,6 +306,9 @@ Capture + + D3D + D3D diff --git a/idd/LGIdd/capture/CFrameGraph.cpp b/idd/LGIdd/capture/CFrameGraph.cpp index 8dda16a1..a6ab2922 100644 --- a/idd/LGIdd/capture/CFrameGraph.cpp +++ b/idd/LGIdd/capture/CFrameGraph.cpp @@ -313,6 +313,17 @@ bool CFrameGraph::Want(FrameSignal signal) const return false; } +bool CFrameGraph::Shared(unsigned node) const +{ + if (!m_sealed || !node || node >= m_nodeCount) + return false; + for (unsigned i = 0; i < m_leafCount; ++i) + if (m_leaves[i].node == node && + m_leaves[i].cfg.profile.storage == FrameStorage::D3D11_TEXTURE) + return true; + return false; +} + bool CFrameGraph::Desc(unsigned leaf, const FrameContentRef& content, LeafDesc& desc) const { diff --git a/idd/LGIdd/capture/CFrameGraph.h b/idd/LGIdd/capture/CFrameGraph.h index 1f4f78e7..21fedd18 100644 --- a/idd/LGIdd/capture/CFrameGraph.h +++ b/idd/LGIdd/capture/CFrameGraph.h @@ -153,6 +153,7 @@ public: bool Same(const GraphCfg& cfg) const; bool Want(FrameSignal signal) const; bool Need(FrameOp op) const; + bool Shared(unsigned node) const; bool Desc(unsigned leaf, const FrameContentRef& content, LeafDesc& desc) const; diff --git a/idd/LGIdd/capture/CFrameTex.cpp b/idd/LGIdd/capture/CFrameTex.cpp index c3dcbb5f..62909b52 100644 --- a/idd/LGIdd/capture/CFrameTex.cpp +++ b/idd/LGIdd/capture/CFrameTex.cpp @@ -95,8 +95,9 @@ struct CTexCore unsigned node = 0; unsigned count = 0; FrameProfile profile; - D3D12_RESOURCE_DESC desc = {}; - D3D12_RESOURCE_STATES read = D3D12_RESOURCE_STATE_COMMON; + 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) @@ -279,10 +280,11 @@ 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) : + const D12Sync& sync, D3D12_RESOURCE_STATES state, bool sharedValue) : m_res(res), m_sync(sync), m_state(state), + m_shared(sharedValue), graph(graphValue), pool(poolValue), node(nodeValue), @@ -389,7 +391,7 @@ bool TexWrite::Seal(const FrameDesc& frame, const D12Sync& sync, CFrameTex * raw = new (std::nothrow) CFrameTex(core->graph, core->id, core->node, m_index, m_version, core->profile, desc, resource, sync, - core->read); + core->read, core->shared); if (!raw) { core->Drop(index, generation); @@ -429,7 +431,7 @@ void TexWrite::Cancel() TexResult CTexPool::Init(ID3D12Device3 * device, uint64_t graph, unsigned node, const FrameProfile& profile, const D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES state, - unsigned slots) + unsigned slots, bool shared) { if (!device || !graph || !node || !slots || slots > MAX_SLOTS || profile.storage != FrameStorage::D3D12_TEXTURE || @@ -468,6 +470,7 @@ TexResult CTexPool::Init(ID3D12Device3 * device, uint64_t graph, core->profile = profile; core->desc = desc; core->read = state; + core->shared = shared; D3D12_HEAP_PROPERTIES heap = {}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; @@ -478,8 +481,12 @@ TexResult CTexPool::Init(ID3D12Device3 * device, uint64_t graph, 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, - D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, &desc, state, nullptr, + flags, &desc, state, nullptr, IID_PPV_ARGS(&core->slots[i].res)); if (FAILED(hr)) { diff --git a/idd/LGIdd/capture/CFrameTex.h b/idd/LGIdd/capture/CFrameTex.h index 7a6dee31..73bc8793 100644 --- a/idd/LGIdd/capture/CFrameTex.h +++ b/idd/LGIdd/capture/CFrameTex.h @@ -47,11 +47,12 @@ private: ComPtr m_res; D12Sync m_sync; D3D12_RESOURCE_STATES m_state; + bool m_shared; 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); + D3D12_RESOURCE_STATES state, bool shared); public: CFrameTex(const CFrameTex&) = delete; @@ -68,6 +69,7 @@ public: ID3D12Resource * Get() const { return m_res.Get(); } const D12Sync& Sync() const { return m_sync; } D3D12_RESOURCE_STATES State() const { return m_state; } + bool Shared() const { return m_shared; } bool Wait(CD3D12CommandSlot& cmd) const; D12SyncState Status() const { return m_sync.State(); } bool Ready() const { return m_sync.Done(); } @@ -152,7 +154,7 @@ public: TexResult Init(ID3D12Device3 * device, uint64_t graph, unsigned node, const FrameProfile& profile, const D3D12_RESOURCE_DESC& desc, - D3D12_RESOURCE_STATES state, unsigned slots); + D3D12_RESOURCE_STATES state, unsigned slots, bool shared = false); void Reset(); TexResult Try(TexWrite& write); }; diff --git a/idd/LGIdd/d3d/CD3D11Device.cpp b/idd/LGIdd/d3d/CD3D11Device.cpp index 5d10f167..600fb497 100644 --- a/idd/LGIdd/d3d/CD3D11Device.cpp +++ b/idd/LGIdd/d3d/CD3D11Device.cpp @@ -47,11 +47,13 @@ HRESULT CD3D11Device::Init() ComPtr device; ComPtr context; + const UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | + (m_video ? D3D11_CREATE_DEVICE_VIDEO_SUPPORT : 0); hr = D3D11CreateDevice( m_adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, + flags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, diff --git a/idd/LGIdd/d3d/CD3D11Device.h b/idd/LGIdd/d3d/CD3D11Device.h index 163f8a0e..8c668479 100644 --- a/idd/LGIdd/d3d/CD3D11Device.h +++ b/idd/LGIdd/d3d/CD3D11Device.h @@ -36,11 +36,14 @@ private: ComPtr m_adapter; ComPtr m_device; ComPtr m_context; - bool m_isSoftware = false; + bool m_isSoftware = false; + bool m_video = false; public: - CD3D11Device(LUID adapterLuid) : - m_adapterLuid(adapterLuid) {}; + CD3D11Device(LUID adapterLuid, bool video = false) : + m_adapterLuid(adapterLuid), + m_video(video) + {} CD3D11Device() { @@ -53,5 +56,8 @@ public: ComPtr GetContext() { return m_context; } + LUID GetAdapterLuid() const { return m_adapterLuid; } + bool IsSoftware() { return m_isSoftware; } + bool IsVideo() const { return m_video; } }; diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp index fd7f3a16..edc28778 100644 --- a/idd/LGIdd/d3d/CD3D12CommandQueue.cpp +++ b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp @@ -432,7 +432,7 @@ bool CD3D12CommandQueue::InitTiming(ID3D12Device3 * device, UINT slotCount) bool CD3D12CommandQueue::Init(ID3D12Device3 * device, D3D12_COMMAND_LIST_TYPE type, const WCHAR * name, CD3D12CommandSlot::CallbackMode callbackMode, UINT slotCount, - bool enableTiming) + bool enableTiming, bool sharedFence) { if (!slotCount || slotCount > MAX_SLOTS) { @@ -455,7 +455,9 @@ bool CD3D12CommandQueue::Init(ID3D12Device3 * device, } m_queue->SetName(name); - hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, + const D3D12_FENCE_FLAGS fenceFlags = sharedFence ? + D3D12_FENCE_FLAG_SHARED : D3D12_FENCE_FLAG_NONE; + hr = device->CreateFence(0, fenceFlags, IID_PPV_ARGS(&m_fence)); if (FAILED(hr)) { diff --git a/idd/LGIdd/d3d/CD3D12CommandQueue.h b/idd/LGIdd/d3d/CD3D12CommandQueue.h index 7ed20f0b..a240ce88 100644 --- a/idd/LGIdd/d3d/CD3D12CommandQueue.h +++ b/idd/LGIdd/d3d/CD3D12CommandQueue.h @@ -198,7 +198,7 @@ class CD3D12CommandQueue bool Init(ID3D12Device3 * device, D3D12_COMMAND_LIST_TYPE type, const WCHAR * name, CD3D12CommandSlot::CallbackMode callbackMode, - UINT slotCount, bool enableTiming = false); + UINT slotCount, bool enableTiming = false, bool sharedFence = false); void DeInit(); CD3D12CommandSlot * Acquire(UINT slotIndex); diff --git a/idd/LGIdd/d3d/CD3D12Device.cpp b/idd/LGIdd/d3d/CD3D12Device.cpp index 02f9e828..42595201 100644 --- a/idd/LGIdd/d3d/CD3D12Device.cpp +++ b/idd/LGIdd/d3d/CD3D12Device.cpp @@ -137,12 +137,12 @@ CD3D12Device::InitResult CD3D12Device::Init( if (!m_copyQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COPY, L"Copy", m_indirectCopy ? CD3D12CommandSlot::NORMAL : - CD3D12CommandSlot::FAST, 2, true)) + CD3D12CommandSlot::FAST, 2, true, true)) return InitResult::FAILURE; if (m_computeEnabled && !m_computeQueue.Init(m_device.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE, - L"Compute", CD3D12CommandSlot::FAST, 2)) + L"Compute", CD3D12CommandSlot::FAST, 2, false, true)) return InitResult::FAILURE; DEBUG_INFO("Created CD3D12Device"); diff --git a/idd/LGIdd/d3d/CD3D12Device.h b/idd/LGIdd/d3d/CD3D12Device.h index e23a936d..cbf33942 100644 --- a/idd/LGIdd/d3d/CD3D12Device.h +++ b/idd/LGIdd/d3d/CD3D12Device.h @@ -79,6 +79,7 @@ struct CD3D12Device ComPtr GetDevice() { return m_device; } ComPtr GetTransportHeap() { return m_transportHeap; } + LUID GetAdapterLuid() const { return m_adapterLuid; } bool IsIndirectCopy() const { return m_indirectCopy; } CD3D12CommandSlot * GetCopySlot (); diff --git a/idd/LGIdd/d3d/CInteropPool.cpp b/idd/LGIdd/d3d/CInteropPool.cpp new file mode 100644 index 00000000..e5c9f1da --- /dev/null +++ b/idd/LGIdd/d3d/CInteropPool.cpp @@ -0,0 +1,407 @@ +/** + * 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 "d3d/CInteropPool.h" + +#include "CDebug.h" + +#include + +using Microsoft::WRL::Wrappers::HandleT; +using Microsoft::WRL::Wrappers::HandleTraits::HANDLENullTraits; + +static bool SameObject(IUnknown * left, IUnknown * right) +{ + if (!left || !right) + return false; + ComPtr leftId; + ComPtr rightId; + return + SUCCEEDED(left->QueryInterface(IID_PPV_ARGS(&leftId))) && + SUCCEEDED(right->QueryInterface(IID_PPV_ARGS(&rightId))) && + leftId.Get() == rightId.Get(); +} + +static bool SameDesc(const D3D12_RESOURCE_DESC& d12, + const D3D11_TEXTURE2D_DESC& d11) +{ + return + d12.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && + d12.Width == d11.Width && + d12.Height == d11.Height && + d12.DepthOrArraySize == d11.ArraySize && + d12.MipLevels == d11.MipLevels && + d12.Format == d11.Format && + d12.SampleDesc.Count == d11.SampleDesc.Count && + d12.SampleDesc.Quality == d11.SampleDesc.Quality && + d11.Usage == D3D11_USAGE_DEFAULT && + d11.CPUAccessFlags == 0; +} + +D12SyncState D11Sync::State() const +{ + if (ready) + return D12SyncState::READY; + if (!Valid()) + return D12SyncState::FAILED; + const UINT64 completed = fence->GetCompletedValue(); + if (completed == UINT64_MAX) + return D12SyncState::FAILED; + return completed >= value ? + D12SyncState::READY : D12SyncState::PENDING; +} + +struct CD3D11Core +{ + static const unsigned MAX_FENCES = 4; + + struct View + { + uint64_t pool = 0; + ComPtr src; + ComPtr tex; + }; + + struct Fence + { + ComPtr src; + ComPtr fence; + }; + + mutable CSRWLock lock; + bool open = true; + bool failed = false; + uint64_t pool = 0; + UINT bind = 0; + std::shared_ptr d11; + std::shared_ptr d12; + ComPtr dev11; + ComPtr dev12; + View views[CTexPool::MAX_SLOTS]; + Fence fences[MAX_FENCES]; + + void Stop() + { + CSRWExclusiveLock guard(lock); + open = false; + } + + void Fail() + { + CSRWExclusiveLock guard(lock); + failed = true; + } + + TexResult Error(HRESULT hr) + { + const HRESULT removed = dev12->GetDeviceRemovedReason(); + const HRESULT removed11 = dev11->GetDeviceRemovedReason(); + if (removed != S_OK || removed11 != S_OK || hr == E_OUTOFMEMORY) + { + const HRESULT reason = removed != S_OK ? removed : + (removed11 != S_OK ? removed11 : hr); + DEBUG_ERROR_HR(reason, "D3D11 texture interop failed"); + failed = true; + return TexResult::FAILED; + } + DEBUG_WARN_HR(hr, "D3D11 texture interop is unavailable"); + return TexResult::REJECTED; + } + + TexResult OpenView(const CFrameTex& frame, + ComPtr& texture) + { + if (frame.slot >= CTexPool::MAX_SLOTS) + return TexResult::REJECTED; + + View& view = views[frame.slot]; + if (view.tex) + { + if (view.pool != frame.pool || + !SameObject(view.src.Get(), frame.Get())) + { + failed = true; + return TexResult::FAILED; + } + texture = view.tex; + return TexResult::OK; + } + + HANDLE raw = nullptr; + const HRESULT create = dev12->CreateSharedHandle(frame.Get(), nullptr, + GENERIC_ALL, nullptr, &raw); + if (FAILED(create)) + return Error(create); + HandleT handle; + handle.Attach(raw); + + ComPtr opened; + const HRESULT hr = dev11->OpenSharedResource1(handle.Get(), + IID_PPV_ARGS(&opened)); + handle.Close(); + if (FAILED(hr)) + return Error(hr); + + D3D11_TEXTURE2D_DESC desc11 = {}; + opened->GetDesc(&desc11); + if (!SameDesc(frame.Get()->GetDesc(), desc11) || + (desc11.BindFlags & bind) != bind) + return TexResult::REJECTED; + + view.pool = frame.pool; + view.src = frame.Get(); + view.tex = opened; + texture = opened; + return TexResult::OK; + } + + TexResult OpenFence(const D12Sync& sync, D11Sync& result) + { + ComPtr owner; + if (!sync.Valid() || + FAILED(sync.fence->GetDevice(IID_PPV_ARGS(&owner))) || + !SameObject(owner.Get(), dev12.Get())) + return TexResult::REJECTED; + + const D12SyncState state = sync.State(); + if (state == D12SyncState::FAILED) + { + failed = true; + return TexResult::FAILED; + } + if (state == D12SyncState::READY) + { + result.ready = true; + return TexResult::OK; + } + + for (unsigned i = 0; i < MAX_FENCES; ++i) + if (fences[i].fence && + SameObject(fences[i].src.Get(), sync.fence.Get())) + { + result.fence = fences[i].fence; + result.value = sync.value; + return TexResult::OK; + } + + unsigned free = MAX_FENCES; + for (unsigned i = 0; i < MAX_FENCES; ++i) + if (!fences[i].fence) + { + free = i; + break; + } + if (free == MAX_FENCES) + return TexResult::BUSY; + + HANDLE raw = nullptr; + const HRESULT create = dev12->CreateSharedHandle(sync.fence.Get(), + nullptr, GENERIC_ALL, nullptr, &raw); + if (FAILED(create)) + return Error(create); + HandleT handle; + handle.Attach(raw); + + ComPtr opened; + const HRESULT hr = dev11->OpenSharedFence(handle.Get(), + IID_PPV_ARGS(&opened)); + handle.Close(); + if (FAILED(hr)) + return Error(hr); + + fences[free].src = sync.fence; + fences[free].fence = opened; + result.fence = opened; + result.value = sync.value; + return TexResult::OK; + } + + TexResult Get(const TexLease& src, D11Lease& lease) + { + const CFrameTex * frame = src.Get(); + if (!frame || + frame->profile.storage != FrameStorage::D3D12_TEXTURE || + !frame->Shared() || + frame->State() != D3D12_RESOURCE_STATE_COMMON || + !frame->Get() || + !frame->Sync().Valid()) + return TexResult::REJECTED; + + ComPtr owner; + if (FAILED(frame->Get()->GetDevice(IID_PPV_ARGS(&owner))) || + !SameObject(owner.Get(), dev12.Get())) + return TexResult::REJECTED; + + CSRWExclusiveLock guard(lock); + if (!open || failed) + return TexResult::FAILED; + if (frame->Status() == D12SyncState::FAILED) + { + failed = true; + return TexResult::FAILED; + } + if (!pool) + pool = frame->pool; + else if (pool != frame->pool) + return TexResult::REJECTED; + + ComPtr texture; + TexResult result = OpenView(*frame, texture); + if (result != TexResult::OK) + return result; + + D11Sync sync; + result = OpenFence(frame->Sync(), sync); + if (result != TexResult::OK) + return result; + + const FrameProfile profile = + Frame::Store(frame->profile, FrameStorage::D3D11_TEXTURE); + lease = D11Lease(shared_from_this(), src, texture, sync, profile); + return TexResult::OK; + } + +private: + // Filled by Init after construction; avoids exposing ownership callbacks + // from cached COM objects. + std::weak_ptr self; + + std::shared_ptr shared_from_this() + { + return self.lock(); + } + + friend class CInteropPool; +}; + +D11Lease::D11Lease(const std::shared_ptr& core, + const TexLease& src, const ComPtr& tex, + const D11Sync& sync, const FrameProfile& profile) : + m_core(core), + m_src(src), + m_tex(tex), + m_sync(sync), + m_profile(profile) +{ +} + +TexResult D11Lease::Wait(ID3D11DeviceContext4 * context) const +{ + if (!m_core || !m_src || !m_tex || !context || !m_sync.Valid()) + return TexResult::REJECTED; + + ComPtr owner; + context->GetDevice(owner.GetAddressOf()); + if (!SameObject(owner.Get(), m_core->dev11.Get())) + return TexResult::REJECTED; + + const D12SyncState state = m_sync.State(); + if (state == D12SyncState::FAILED) + { + m_core->Fail(); + return TexResult::FAILED; + } + if (state == D12SyncState::READY) + return TexResult::OK; + + const HRESULT hr = context->Wait(m_sync.fence.Get(), m_sync.value); + if (FAILED(hr)) + { + DEBUG_ERROR_HR(hr, "Failed to queue the D3D11 producer wait"); + m_core->Fail(); + return TexResult::FAILED; + } + return TexResult::OK; +} + +void D11Lease::Reset() +{ + m_sync = D11Sync {}; + m_tex.Reset(); + m_src.Reset(); + m_core.reset(); + m_profile = FrameProfile {}; +} + +TexResult CInteropPool::Init( + const std::shared_ptr& d11, + const std::shared_ptr& d12, UINT bind) +{ + if (!d11 || !d12 || !Frame::Same(d11->GetAdapterLuid(), + d12->GetAdapterLuid())) + return TexResult::REJECTED; + + ComPtr dev11 = d11->GetDevice(); + ComPtr dev12 = d12->GetDevice(); + if (!dev11 || !dev12) + return TexResult::REJECTED; + + std::shared_ptr core; + try + { + core = std::shared_ptr(new (std::nothrow) CD3D11Core); + } + catch (const std::bad_alloc&) + { + return TexResult::FAILED; + } + if (!core) + return TexResult::FAILED; + core->d11 = d11; + core->d12 = d12; + core->dev11 = dev11; + core->dev12 = dev12; + core->bind = bind; + core->self = core; + + std::shared_ptr old; + { + CSRWExclusiveLock guard(m_lock); + old = std::move(m_core); + if (old) + old->Stop(); + m_core = core; + } + return TexResult::OK; +} + +TexResult CInteropPool::Get(const TexLease& src, D11Lease& lease) +{ + if (lease) + return TexResult::REJECTED; + + std::shared_ptr core; + { + CSRWSharedLock guard(m_lock); + core = m_core; + } + return core ? core->Get(src, lease) : TexResult::FAILED; +} + +void CInteropPool::Reset() +{ + std::shared_ptr core; + { + CSRWExclusiveLock guard(m_lock); + core = std::move(m_core); + if (core) + core->Stop(); + } +} diff --git a/idd/LGIdd/d3d/CInteropPool.h b/idd/LGIdd/d3d/CInteropPool.h new file mode 100644 index 00000000..0b341bf4 --- /dev/null +++ b/idd/LGIdd/d3d/CInteropPool.h @@ -0,0 +1,102 @@ +/** + * 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/CFrameTex.h" +#include "d3d/CD3D11Device.h" +#include "d3d/CD3D12Device.h" + +#include + +struct CD3D11Core; + +struct D11Sync +{ + ComPtr fence; + UINT64 value = 0; + bool ready = false; + + bool Valid() const { return ready || (fence && value); } + D12SyncState State() const; +}; + +// A native D3D11 view of one immutable graph product. Keep the lease until +// the encoder's GPU completion, not merely until its submission returns. +class D11Lease +{ + friend class CInteropPool; + friend struct CD3D11Core; + +private: + std::shared_ptr m_core; + TexLease m_src; + ComPtr m_tex; + D11Sync m_sync; + FrameProfile m_profile; + + D11Lease(const std::shared_ptr& core, const TexLease& src, + const ComPtr& tex, const D11Sync& sync, + const FrameProfile& profile); + +public: + D11Lease() = default; + D11Lease(const D11Lease&) = default; + D11Lease& operator=(const D11Lease&) = default; + D11Lease(D11Lease&&) noexcept = default; + D11Lease& operator=(D11Lease&&) noexcept = default; + + explicit operator bool() const { return !!m_src && !!m_tex; } + ID3D11Texture2D * Get() const { return m_tex.Get(); } + const CFrameTex * Frame() const { return m_src.Get(); } + const FrameProfile& Profile() const { return m_profile; } + const D11Sync& Sync() const { return m_sync; } + D12SyncState Status() const { return m_sync.State(); } + + // Orders only work subsequently submitted through this exact context. + // The context owner remains responsible for its thread serialization and + // for ordering any private encoder queue. + TexResult Wait(ID3D11DeviceContext4 * context) const; + void Reset(); +}; + +// Opens cached native D3D11 views of shareable D3D12 graph products. The +// pool never copies, transitions, or writes a product. Failures remain local +// to this interop route and cannot invalidate the source or sibling routes. +class CInteropPool +{ +private: + mutable CSRWLock m_lock; + std::shared_ptr m_core; + +public: + CInteropPool() = default; + CInteropPool(const CInteropPool&) = delete; + CInteropPool& operator=(const CInteropPool&) = delete; + ~CInteropPool() { Reset(); } + + // bind is the minimum native D3D11 bind capability required by the future + // consumer. Unsupported capabilities reject this route without a copy. + TexResult Init(const std::shared_ptr& d11, + const std::shared_ptr& d12, UINT bind = 0); + TexResult Get(const TexLease& src, D11Lease& lease); + void Reset(); +};