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();
+};