Revision control
++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qwindowsd3d11rhitextureimporter_p.h"
++
++ #include <QtMultimedia/private/qvideotexturehelper_p.h>
++
++ #include <QtGui/rhi/qrhi.h>
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qscopeguard.h>
++ #include <QtCore/qvarlengtharray.h>
++ #include <QtCore/private/qsystemerror_p.h>
++ #include <QtCore/private/qexpected_p.h>
++
++ #include <d3d11on12.h>
++ #include <d3d12.h>
++
++ #include <algorithm>
++
++ #if QT_CONFIG(opengl)
++ # include <QtMultimedia/private/qmultimedia_gl_support_p.h>
++ # include <QtMultimedia/private/qrhivaluemapper_p.h>
++ # include <QtGui/qopenglcontext.h>
++ # include <QtGui/qopenglextrafunctions.h>
++ # include <QtGui/qopenglfunctions.h>
++ # include <QtGui/qopenglext.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QtMultimediaPrivate {
++
++ using namespace Qt::Literals;
++
++ namespace {
++
++ Q_LOGGING_CATEGORY(lcD3D11RhiTextureImporter, "qt.multimedia.windows.d3d11rhitextureimporter");
++
++ class D3D11TextureHandles : public QVideoFrameTexturesHandles
++ {
++ public:
++ explicit D3D11TextureHandles(ComPtr<ID3D11Texture2D> tex) : m_tex(std::move(tex)) { }
++
++ quint64 textureHandle(QRhi &rhi, int /*plane*/) override
++ {
++ if (rhi.backend() != QRhi::D3D11)
++ return 0;
++ return quint64(m_tex.Get());
++ }
++
++ private:
++ const ComPtr<ID3D11Texture2D> m_tex;
++ };
++
++ const QRhiD3D11NativeHandles *resolveD3D11NativeHandles(QRhi &rhi)
++ {
++ Q_ASSERT(rhi.backend() == QRhi::D3D11);
++ return static_cast<const QRhiD3D11NativeHandles *>(rhi.nativeHandles());
++ }
++
++ q23::expected<ComPtr<ID3D11Device1>, HRESULT>
++ resolveD3D11Device(const QRhiD3D11NativeHandles *nativeHandles)
++ {
++ ComPtr<ID3D11Device> rhiDevice = static_cast<ID3D11Device *>(nativeHandles->dev);
++ ComPtr<ID3D11Device1> rhiDevice1;
++ if (const HRESULT hr = rhiDevice.As(&rhiDevice1); FAILED(hr))
++ return q23::unexpected{ hr };
++ return rhiDevice1;
++ }
++
++ // Exposes the D3D12 view of a D3D11 texture, unwrapped via D3D11On12
++ class D3D12TextureHandles : public QVideoFrameTexturesHandles
++ {
++ public:
++ D3D12TextureHandles(ComPtr<ID3D12Resource> resource, ComPtr<ID3D11On12Device2> on12Device,
++ ComPtr<ID3D11Texture2D> wrappedResource,
++ std::shared_ptr<std::atomic_bool> wrappedResourceInUse)
++ : m_resource(std::move(resource)),
++ m_on12Device(std::move(on12Device)),
++ m_wrappedResource(std::move(wrappedResource)),
++ m_wrappedResourceInUse(std::move(wrappedResourceInUse))
++ {
++ }
++
++ ~D3D12TextureHandles() override
++ {
++ m_resource.Reset();
++ if (m_on12Device && m_wrappedResource)
++ m_on12Device->ReturnUnderlyingResource(m_wrappedResource.Get(), 0, nullptr, nullptr);
++ // Only now may the texture be reused for another frame.
++ if (m_wrappedResourceInUse)
++ m_wrappedResourceInUse->store(false);
++ }
++
++ quint64 textureHandle(QRhi &rhi, int /*plane*/) override
++ {
++ switch (rhi.backend()) {
++ case QRhi::D3D12:
++ return quint64(m_resource.Get());
++ default:
++ return 0;
++ }
++ }
++
++ private:
++ ComPtr<ID3D12Resource> m_resource;
++ const ComPtr<ID3D11On12Device2> m_on12Device;
++ const ComPtr<ID3D11Texture2D> m_wrappedResource;
++ const std::shared_ptr<std::atomic_bool> m_wrappedResourceInUse;
++ };
++
++ #if QT_CONFIG(opengl)
++
++ // WGL_NV_DX_interop2 function types
++ using PFNWGLDXOPENDEVICENVPROC = HANDLE(WINAPI *)(void *);
++ using PFNWGLDXCLOSEDEVICENVPROC = BOOL(WINAPI *)(HANDLE);
++ using PFNWGLDXREGISTEROBJECTNVPROC = HANDLE(WINAPI *)(HANDLE, void *, GLuint, GLenum, GLenum);
++ using PFNWGLDXUNREGISTEROBJECTNVPROC = BOOL(WINAPI *)(HANDLE, HANDLE);
++ using PFNWGLDXLOCKOBJECTSNVPROC = BOOL(WINAPI *)(HANDLE, GLint, HANDLE *);
++ using PFNWGLDXUNLOCKOBJECTSNVPROC = BOOL(WINAPI *)(HANDLE, GLint, HANDLE *);
++
++ # ifndef WGL_ACCESS_READ_ONLY_NV
++ # define WGL_ACCESS_READ_ONLY_NV 0x00000000
++ # endif
++
++ struct WglDxInterop
++ {
++ PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV = nullptr;
++ PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV = nullptr;
++ PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV = nullptr;
++ PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV = nullptr;
++ PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV = nullptr;
++ PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV = nullptr;
++
++ static WglDxInterop resolve(QOpenGLContext *ctx)
++ {
++ using namespace QtMultimediaPrivate;
++ WglDxInterop f;
++ if (!ctx)
++ return f;
++ f.wglDXOpenDeviceNV = resolveGlFunction<PFNWGLDXOPENDEVICENVPROC>(ctx, "wglDXOpenDeviceNV");
++ f.wglDXCloseDeviceNV =
++ resolveGlFunction<PFNWGLDXCLOSEDEVICENVPROC>(ctx, "wglDXCloseDeviceNV");
++ f.wglDXRegisterObjectNV =
++ resolveGlFunction<PFNWGLDXREGISTEROBJECTNVPROC>(ctx, "wglDXRegisterObjectNV");
++ f.wglDXUnregisterObjectNV =
++ resolveGlFunction<PFNWGLDXUNREGISTEROBJECTNVPROC>(ctx, "wglDXUnregisterObjectNV");
++ f.wglDXLockObjectsNV =
++ resolveGlFunction<PFNWGLDXLOCKOBJECTSNVPROC>(ctx, "wglDXLockObjectsNV");
++ f.wglDXUnlockObjectsNV =
++ resolveGlFunction<PFNWGLDXUNLOCKOBJECTSNVPROC>(ctx, "wglDXUnlockObjectsNV");
++ return f;
++ }
++
++ bool valid() const
++ {
++ return wglDXOpenDeviceNV && wglDXCloseDeviceNV && wglDXRegisterObjectNV
++ && wglDXUnregisterObjectNV && wglDXLockObjectsNV && wglDXUnlockObjectsNV;
++ }
++ };
++
++ QRhiValueMapper<WglDxInterop> g_wglDxInteropMapper;
++
++ bool supportsGlCopy(QOpenGLContext &glCtx)
++ {
++ return (glCtx.format().version() >= std::pair(4, 3)
++ || glCtx.hasExtension("GL_ARB_copy_image"_ba));
++ }
++
++ GLenum dxFormatToGlTextureFormat(DXGI_FORMAT d3dFormat)
++ {
++ switch (d3dFormat) {
++ case DXGI_FORMAT_R8G8B8A8_UNORM:
++ return GL_RGBA8;
++ case DXGI_FORMAT_R8_UNORM:
++ return GL_R8;
++ case DXGI_FORMAT_R8G8_UNORM:
++ return GL_RG8;
++ case DXGI_FORMAT_R16_UNORM:
++ return GL_R16;
++ case DXGI_FORMAT_R16G16_UNORM:
++ return GL_RG16;
++ default:
++ return GLenum(0);
++ }
++ }
++
++ // Unlike the D3D11/D3D12 handles, which keep their shared source texture alive, the GL result
++ // must survive the transient WGL registration (see importTextureOpenGL): makes a GPU-side copy
++ // of 'source' into a standalone texture with its own storage. Leaves the GL_TEXTURE_2D binding
++ // as found, since QRhi tracks GL state.
++ GlTextureHandle copyToOwnedGlTexture(QOpenGLContext *glCtx, GLuint source, DXGI_FORMAT sourceFormat,
++ GLsizei width, GLsizei height)
++ {
++ Q_ASSERT(supportsGlCopy(*glCtx));
++
++ const GLenum internalFormat = dxFormatToGlTextureFormat(sourceFormat);
++ if (!internalFormat) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "Cannot import D3D11 texture to OpenGL: unsupported DXGI format" << sourceFormat;
++ return {};
++ }
++
++ QOpenGLExtraFunctions *f = glCtx->extraFunctions();
++ clearGlErrors(f);
++ const auto checkGlError = [&](const char *call) {
++ const GLError error = GLError(f->glGetError());
++ if (error == GLError::NoError)
++ return true;
++ qCWarning(lcD3D11RhiTextureImporter) << call << "failed with GL error" << error;
++ return false;
++ };
++
++ GLint previousBinding = 0;
++ f->glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousBinding);
++ if (!checkGlError("glGetIntegerv(GL_TEXTURE_BINDING_2D)"))
++ return {};
++ auto restoreBinding = qScopeGuard([&] {
++ f->glBindTexture(GL_TEXTURE_2D, GLuint(previousBinding));
++ });
++
++ GLuint texId = 0;
++ f->glGenTextures(1, &texId);
++ if (!checkGlError("glGenTextures") || !texId)
++ return {};
++ GlTextureHandle result{
++ texId,
++ GlTextureDeleter{ glCtx },
++ };
++
++ f->glBindTexture(GL_TEXTURE_2D, texId);
++ if (!checkGlError("glBindTexture"))
++ return {};
++
++ f->glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, width, height);
++ if (!checkGlError("glTexStorage2D"))
++ return {};
++
++ f->glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, texId, GL_TEXTURE_2D, 0, 0, 0, 0,
++ width, height, 1);
++ if (!checkGlError("glCopyImageSubData"))
++ return {};
++
++ return result;
++ }
++
++ class OpenGLTextureHandles : public QVideoFrameTexturesHandles
++ {
++ public:
++ explicit OpenGLTextureHandles(GlTextureHandle tex) : m_tex(std::move(tex)) { }
++
++ quint64 textureHandle(QRhi &rhi, int /*plane*/) override
++ {
++ if (rhi.backend() != QRhi::OpenGLES2)
++ return 0;
++ return quint64(m_tex.get());
++ }
++
++ private:
++ const GlTextureHandle m_tex;
++ };
++
++ #endif // QT_CONFIG(opengl)
++
++ // At most 2 planes: NV12 and P010 are luma plus a single chroma plane.
++ class MultiPlaneTextureHandles : public QVideoFrameTexturesHandles
++ {
++ public:
++ explicit MultiPlaneTextureHandles(QVarLengthArray<QVideoFrameTexturesHandlesUPtr, 2> planes)
++ : m_planes(std::move(planes))
++ {
++ }
++
++ quint64 textureHandle(QRhi &rhi, int plane) override
++ {
++ if (plane < 0 || plane >= m_planes.size() || !m_planes[plane])
++ return 0;
++ return m_planes[plane]->textureHandle(rhi, 0);
++ }
++
++ private:
++ const QVarLengthArray<QVideoFrameTexturesHandlesUPtr, 2> m_planes;
++ };
++
++ } // namespace
++
++ QWindowsD3D11RhiTextureImporter::QWindowsD3D11RhiTextureImporter() = default;
++ QWindowsD3D11RhiTextureImporter::~QWindowsD3D11RhiTextureImporter() = default;
++
++ QWindowsD3D11RhiTextureImporter::D3D12OutputTexturePool::Texture
++ QWindowsD3D11RhiTextureImporter::D3D12OutputTexturePool::acquire(ID3D11Device *device,
++ const D3D11_TEXTURE2D_DESC &desc)
++ {
++ const auto matches = [&](const Texture &entry) {
++ ComPtr<ID3D11Device> entryDevice;
++ entry.texture->GetDevice(&entryDevice);
++ D3D11_TEXTURE2D_DESC entryDesc{};
++ entry.texture->GetDesc(&entryDesc);
++ return entryDevice.Get() == device && entryDesc.Format == desc.Format
++ && entryDesc.Width == desc.Width && entryDesc.Height == desc.Height;
++ };
++
++ m_textures.erase(std::remove_if(m_textures.begin(), m_textures.end(),
++ [&](const Texture &entry) {
++ return !entry.inUse->load() && !matches(entry);
++ }),
++ m_textures.end());
++
++ for (const Texture &entry : m_textures) {
++ if (!entry.inUse->load() && matches(entry)) {
++ entry.inUse->store(true);
++ return entry;
++ }
++ }
++
++ Texture entry{ {}, std::make_shared<std::atomic_bool>(true) };
++ if (const HRESULT hr = device->CreateTexture2D(&desc, nullptr, &entry.texture); FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "CreateTexture2D failed for D3D12 import:" << QSystemError::windowsComString(hr);
++ return {};
++ }
++ m_textures.push_back(entry);
++ return entry;
++ }
++
++ bool QWindowsD3D11RhiTextureImporter::isRhiBackendSupported(QRhi &rhi)
++ {
++ switch (rhi.backend()) {
++ case QRhi::D3D11: {
++ // A software (WARP) D3D11 device lives on a different adapter than the source device,
++ // so opening a shared handle across them would either fail or, worse, block on the
++ // keyed mutex until the finite timeout expires. Treat it the same as unsupported.
++ if (rhi.driverInfo().deviceType == QRhiDriverInfo::CpuDevice)
++ return false;
++
++ const QRhiD3D11NativeHandles *nh = resolveD3D11NativeHandles(rhi);
++ return nh && nh->dev;
++ }
++ case QRhi::D3D12:
++ return true;
++ #if QT_CONFIG(opengl)
++ case QRhi::OpenGLES2: {
++ const auto *nh = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
++ if (!nh || !nh->context)
++ return false;
++ if (!supportsGlCopy(*nh->context))
++ return false; // GL_ARB_copy_image or OpenGL >= 4.3 is required to copy the D3D texture into a GL texture
++
++ QOpenGLContext *glCtx = nh->context;
++ WglDxInterop &wgl = g_wglDxInteropMapper.getOrCreate(rhi, [&] {
++ return WglDxInterop::resolve(glCtx);
++ });
++ return wgl.valid();
++ }
++ #endif
++ default:
++ return false;
++ }
++ }
++
++ bool QWindowsD3D11RhiTextureImporter::isPixelFormatSupportedByRhiBackend(
++ QRhi &rhi, QVideoFrameFormat::PixelFormat pixelFormat)
++ {
++ switch (rhi.backend()) {
++ case QRhi::D3D12:
++ case QRhi::OpenGLES2: {
++ const auto *desc = QVideoTextureHelper::textureDescription(pixelFormat);
++ if (desc && desc->nplanes == 1)
++ return true;
++ return QWindowsD3D11PlaneSplitter::isPixelFormatSupported(pixelFormat);
++ }
++ default:
++ return true;
++ }
++ }
++
++ QVideoFrameTexturesHandlesUPtr QWindowsD3D11RhiTextureImporter::importTexture(
++ QRhi &rhi, ID3D11Device *srcDevice, ID3D11DeviceContext *srcContext,
++ const ComPtr<ID3D11Texture2D> &texture, const QSize &frameSize,
++ QVideoFrameFormat::PixelFormat pixelFormat, UINT subresourceIndex)
++ {
++ if (rhi.backend() == QRhi::D3D12 || rhi.backend() == QRhi::OpenGLES2) {
++ const auto *desc = QVideoTextureHelper::textureDescription(pixelFormat);
++ if (desc && desc->nplanes > 1)
++ return importSplitPlanes(rhi, srcDevice, srcContext, texture, frameSize, pixelFormat,
++ subresourceIndex);
++ }
++
++ switch (rhi.backend()) {
++ case QRhi::D3D11:
++ return importTextureD3D11(rhi, srcDevice, srcContext, texture, frameSize, subresourceIndex);
++ case QRhi::D3D12:
++ return importTextureD3D12(rhi, srcDevice, srcContext, texture, frameSize, subresourceIndex,
++ m_bridge, m_d3d12OutputPool);
++ #if QT_CONFIG(opengl)
++ case QRhi::OpenGLES2:
++ return importTextureOpenGL(rhi, srcDevice, srcContext, texture, frameSize, m_glInteropTex);
++ #endif
++ default:
++ return nullptr;
++ }
++ }
++
++ QVideoFrameTexturesHandlesUPtr QWindowsD3D11RhiTextureImporter::importSplitPlanes(
++ QRhi &rhi, ID3D11Device *srcDevice, ID3D11DeviceContext *srcContext,
++ const ComPtr<ID3D11Texture2D> &texture, const QSize &frameSize,
++ QVideoFrameFormat::PixelFormat pixelFormat, UINT subresourceIndex)
++ {
++ Q_ASSERT(rhi.backend() == QRhi::D3D12 || rhi.backend() == QRhi::OpenGLES2);
++
++ if (!m_planeSplitter || m_planeSplitterDevice.Get() != srcDevice) {
++ m_planeSplitter = QWindowsD3D11PlaneSplitter::create(srcDevice);
++ m_planeSplitterDevice = srcDevice;
++ if (!m_planeSplitter) {
++ qCWarning(lcD3D11RhiTextureImporter) << "Cannot create plane splitter";
++ return nullptr;
++ }
++ }
++
++ const std::optional<std::vector<QWindowsD3D11PlaneSplitter::Plane>> planes =
++ m_planeSplitter->split(srcDevice, srcContext, texture, subresourceIndex, frameSize,
++ pixelFormat);
++ if (!planes)
++ return nullptr;
++
++ if (planes->size() > 2) {
++ qCWarning(lcD3D11RhiTextureImporter) << "Splitting more than 2 planes is not supported";
++ return nullptr;
++ }
++
++ QWindowsD3D11TextureBridge *bridges[] = { &m_bridge, &m_bridge2 };
++ D3D12OutputTexturePool *outputPools[] = { &m_d3d12OutputPool, &m_d3d12OutputPool2 };
++ #if QT_CONFIG(opengl)
++ ComPtr<ID3D11Texture2D> *glInteropSlots[] = { &m_glInteropTex, &m_glInteropTex2 };
++ #endif
++
++ QVarLengthArray<QVideoFrameTexturesHandlesUPtr, 2> planeHandles;
++ for (size_t i = 0; i < planes->size(); ++i) {
++ QVideoFrameTexturesHandlesUPtr handles;
++ if (rhi.backend() == QRhi::D3D12) {
++ handles = importTextureD3D12(rhi, srcDevice, srcContext, (*planes)[i].texture,
++ (*planes)[i].size, 0, *bridges[i], *outputPools[i]);
++ }
++ #if QT_CONFIG(opengl)
++ else {
++ handles = importTextureOpenGL(rhi, srcDevice, srcContext, (*planes)[i].texture,
++ (*planes)[i].size, *glInteropSlots[i]);
++ }
++ #endif
++ if (!handles)
++ return nullptr;
++ planeHandles.push_back(std::move(handles));
++ }
++
++ return std::make_unique<MultiPlaneTextureHandles>(std::move(planeHandles));
++ }
++
++ QVideoFrameTexturesHandlesUPtr QWindowsD3D11RhiTextureImporter::importTextureD3D11(
++ QRhi &rhi, ID3D11Device *srcDevice, ID3D11DeviceContext *srcContext,
++ const ComPtr<ID3D11Texture2D> &texture, const QSize &frameSize, UINT subresourceIndex)
++ {
++ Q_ASSERT(rhi.backend() == QRhi::D3D11);
++ const QRhiD3D11NativeHandles *nh = resolveD3D11NativeHandles(rhi);
++ Q_ASSERT(nh && nh->dev);
++
++ Q_ASSERT(srcDevice);
++ Q_ASSERT(srcContext);
++ Q_ASSERT(texture);
++
++ auto device = resolveD3D11Device(nh);
++ if (!device) {
++ qCWarning(lcD3D11RhiTextureImporter) << "QueryInterface(ID3D11Device1) failed:"
++ << QSystemError::windowsComString(device.error());
++ return nullptr;
++ }
++ ComPtr<ID3D11Device1> rhiDevice = std::move(*device);
++
++ ComPtr<ID3D11DeviceContext> rhiCtx;
++ rhiDevice->GetImmediateContext(&rhiCtx);
++
++ if (!m_bridge.copyToSharedTex(srcDevice, srcContext, texture, frameSize, subresourceIndex))
++ return nullptr;
++
++ ComPtr<ID3D11Texture2D> rhiTex = m_bridge.copyFromSharedTex(rhiDevice, rhiCtx);
++ if (!rhiTex)
++ return nullptr;
++
++ return std::make_unique<D3D11TextureHandles>(std::move(rhiTex));
++ }
++
++ QVideoFrameTexturesHandlesUPtr QWindowsD3D11RhiTextureImporter::importTextureD3D12(
++ QRhi &rhi, ID3D11Device *srcDevice, ID3D11DeviceContext *srcContext,
++ const ComPtr<ID3D11Texture2D> &texture, const QSize &frameSize, UINT subresourceIndex,
++ QWindowsD3D11TextureBridge &bridge, D3D12OutputTexturePool &outputPool)
++ {
++ Q_ASSERT(rhi.backend() == QRhi::D3D12);
++ Q_ASSERT(srcDevice);
++ Q_ASSERT(srcContext);
++ Q_ASSERT(texture);
++
++ const auto *nh = static_cast<const QRhiD3D12NativeHandles *>(rhi.nativeHandles());
++ if (!nh || !nh->dev || !nh->commandQueue)
++ return nullptr;
++
++ auto *d3d12Device = static_cast<ID3D12Device *>(nh->dev);
++ auto *cmdQueue = static_cast<ID3D12CommandQueue *>(nh->commandQueue);
++
++ if (!m_d3d11on12Device) {
++ // D3D11On12 needs a queue of its own: UnwrapUnderlyingResource() makes cmdQueue wait
++ // for a fence that D3D11On12 only signals in its next submission. If that submission
++ // goes to cmdQueue as well, it is queued behind the wait and cmdQueue deadlocks.
++ D3D12_COMMAND_QUEUE_DESC queueDesc{};
++ queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
++ ComPtr<ID3D12CommandQueue> on12Queue;
++ if (const HRESULT hr = d3d12Device->CreateCommandQueue(&queueDesc,
++ IID_PPV_ARGS(&on12Queue));
++ FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "CreateCommandQueue:" << QSystemError::windowsComString(hr);
++ return nullptr;
++ }
++
++ IUnknown *queues[] = { on12Queue.Get() };
++ HRESULT hr = D3D11On12CreateDevice(d3d12Device, 0, nullptr, 0, queues, 1, 0,
++ &m_d3d11on12Device, nullptr, nullptr);
++ if (FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "D3D11On12CreateDevice:" << QSystemError::windowsComString(hr);
++ return nullptr;
++ }
++ }
++
++ ComPtr<ID3D11Device1> d3d11on12Device1;
++ if (const HRESULT hr = m_d3d11on12Device.As(&d3d11on12Device1); FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "QueryInterface(ID3D11Device1) failed:" << QSystemError::windowsComString(hr);
++ return nullptr;
++ }
++
++ ComPtr<ID3D11DeviceContext> d3d11on12Ctx;
++ m_d3d11on12Device->GetImmediateContext(&d3d11on12Ctx);
++
++ if (!bridge.copyToSharedTex(srcDevice, srcContext, texture, frameSize, subresourceIndex))
++ return nullptr;
++
++ D3D11_TEXTURE2D_DESC srcDesc{};
++ texture->GetDesc(&srcDesc);
++ D3D11_TEXTURE2D_DESC outputDesc{};
++ outputDesc.Width = static_cast<UINT>(frameSize.width());
++ outputDesc.Height = static_cast<UINT>(frameSize.height());
++ outputDesc.MipLevels = 1;
++ outputDesc.ArraySize = 1;
++ outputDesc.Format = srcDesc.Format;
++ outputDesc.SampleDesc.Count = 1;
++ outputDesc.Usage = D3D11_USAGE_DEFAULT;
++ outputDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
++
++ D3D12OutputTexturePool::Texture output = outputPool.acquire(d3d11on12Device1.Get(), outputDesc);
++ if (!output.texture)
++ return nullptr;
++ // Give the texture back to the pool unless handles end up owning it.
++ auto releaseOutput = qScopeGuard([&] {
++ output.inUse->store(false);
++ });
++
++ if (!bridge.copyFromSharedTex(d3d11on12Device1, d3d11on12Ctx, output.texture.Get()))
++ return nullptr;
++
++ ComPtr<ID3D11On12Device2> on12Device2;
++ if (const HRESULT hr = m_d3d11on12Device.As(&on12Device2); FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter) << "QueryInterface(ID3D11On12Device2) failed:"
++ << QSystemError::windowsComString(hr);
++ return nullptr;
++ }
++
++ ComPtr<ID3D12Resource> resource;
++ if (const HRESULT hr = on12Device2->UnwrapUnderlyingResource(output.texture.Get(), cmdQueue,
++ IID_PPV_ARGS(&resource));
++ FAILED(hr)) {
++ qCWarning(lcD3D11RhiTextureImporter)
++ << "UnwrapUnderlyingResource:" << QSystemError::windowsComString(hr);
++ return nullptr;
++ }
++
++ // UnwrapUnderlyingResource() doesn't flush: submit the copy and the fence signal that
++ // cmdQueue now waits for.
++ d3d11on12Ctx->Flush();
++
++ releaseOutput.dismiss();
++ return std::make_unique<D3D12TextureHandles>(std::move(resource), std::move(on12Device2),
++ std::move(output.texture),
++ std::move(output.inUse));
++ }
++
++ #if QT_CONFIG(opengl)
++
++ QVideoFrameTexturesHandlesUPtr QWindowsD3D11RhiTextureImporter::importTextureOpenGL(
++ QRhi &rhi, ID3D11Device *srcDevice, ID3D11DeviceContext *srcContext,
++ const ComPtr<ID3D11Texture2D> &texture, const QSize &frameSize,
++ ComPtr<ID3D11Texture2D> &interopTexSlot)
++ {
++ Q_ASSERT(rhi.backend() == QRhi::OpenGLES2);
++ Q_ASSERT(srcDevice);
++ Q_ASSERT(srcContext);
++ Q_ASSERT(texture);
++
++ const auto *nh = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
++ if (!nh || !nh->context)
++ return nullptr;
++ QOpenGLContext *glCtx = nh->context;
++
++ WglDxInterop &wgl = g_wglDxInteropMapper.getOrCreate(rhi, [&] {
++ return WglDxInterop::resolve(glCtx);
++ });
++ if (!wgl.valid()) {
++ qCWarning(lcD3D11RhiTextureImporter) << "WGL_NV_DX_interop2 functions not available";
++ return nullptr;
++ }
++
++ // Reuse or create a non-shared texture matching the frame size for WGL interop (keyed-mutex
++ // textures are incompatible with wglDXRegisterObjectNV, and the source texture may be
++ // larger than the frame, e.g. due to a decoder's surface alignment requirements).
++ const UINT width = static_cast<UINT>(frameSize.width());
++ const UINT height = static_cast<UINT>(frameSize.height());
++ {
++ bool needRecreate = !interopTexSlot;
++ if (interopTexSlot) {
++ CD3D11_TEXTURE2D_DESC cachedDesc;
++ interopTexSlot->GetDesc(&cachedDesc);
++ if (cachedDesc.Width != width || cachedDesc.Height != height)
++ needRecreate = true;
++ }
++ if (needRecreate) {
++ CD3D11_TEXTURE2D_DESC desc;
++ texture->GetDesc(&desc);
++ desc.Width = width;
++ desc.Height = height;
++ desc.ArraySize = 1;
++ desc.MiscFlags = 0;
++ desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
++ if (FAILED(srcDevice->CreateTexture2D(&desc, nullptr,
++ interopTexSlot.ReleaseAndGetAddressOf())))
++ return nullptr;
++ }
++
++ const D3D11_BOX crop{ 0, 0, 0, width, height, 1 };
++ srcContext->CopySubresourceRegion(interopTexSlot.Get(), 0, 0, 0, 0, texture.Get(), 0,
++ &crop);
++ srcContext->Flush();
++ }
++
++ if (!m_glInteropDevice) {
++ HANDLE device = wgl.wglDXOpenDeviceNV(srcDevice);
++ if (!device) {
++ qCWarning(lcD3D11RhiTextureImporter) << "wglDXOpenDeviceNV failed";
++ return nullptr;
++ }
++ m_glInteropDevice = WglDxDeviceHandle{
++ device,
++ WglDxDeviceDeleter{ wgl.wglDXCloseDeviceNV },
++ };
++
++ // Release the WGL interop device before the GL context is destroyed to avoid leaking
++ // the handle.
++ if (!m_glCleanupContext)
++ m_glCleanupContext = std::make_unique<QObject>();
++
++ QObject::connect(nh->context, &QOpenGLContext::aboutToBeDestroyed, m_glCleanupContext.get(),
++ [this] {
++ releaseGLInterop();
++ });
++ }
++
++ GLuint texId = 0;
++ QOpenGLFunctions *f = glCtx->functions();
++ f->glGenTextures(1, &texId);
++ if (!texId)
++ return nullptr;
++ GlTextureHandle interopGlTex{
++ texId,
++ GlTextureDeleter{ glCtx },
++ };
++
++ HANDLE interopObj =
++ wgl.wglDXRegisterObjectNV(m_glInteropDevice.get(), interopTexSlot.Get(),
++ interopGlTex.get(), GL_TEXTURE_2D, WGL_ACCESS_READ_ONLY_NV);
++ if (!interopObj) {
++ qCWarning(lcD3D11RhiTextureImporter) << "wglDXRegisterObjectNV failed";
++ return nullptr;
++ }
++ auto unregisterInteropObj = qScopeGuard([&] {
++ wgl.wglDXUnregisterObjectNV(m_glInteropDevice.get(), interopObj);
++ });
++
++ if (!wgl.wglDXLockObjectsNV(m_glInteropDevice.get(), 1, &interopObj)) {
++ qCWarning(lcD3D11RhiTextureImporter) << "wglDXLockObjectsNV failed";
++ return nullptr;
++ }
++
++ // GL may only access a registered object while it is locked, and unregistering it detaches
++ // 'interopGlTex' from the D3D11 texture. So copy it into a texture that GL owns while it is
++ // locked. The copy stays valid after unregistering, and isn't affected when 'interopTexSlot'
++ // is reused for the next frame. Locking also synchronizes the D3D11 copy above with GL.
++ CD3D11_TEXTURE2D_DESC interopDesc;
++ interopTexSlot->GetDesc(&interopDesc);
++ GlTextureHandle ownedGlTex = copyToOwnedGlTexture(glCtx, interopGlTex.get(), interopDesc.Format,
++ GLsizei(width), GLsizei(height));
++ wgl.wglDXUnlockObjectsNV(m_glInteropDevice.get(), 1, &interopObj);
++
++ if (!ownedGlTex)
++ return nullptr;
++
++ return std::make_unique<OpenGLTextureHandles>(std::move(ownedGlTex));
++ }
++
++ void QWindowsD3D11RhiTextureImporter::releaseGLInterop()
++ {
++ m_glInteropDevice.reset();
++ m_glInteropTex.Reset();
++ m_glInteropTex2.Reset();
++ }
++
++ #endif // QT_CONFIG(opengl)
++
++ } // namespace QtMultimediaPrivate
++
++ QT_END_NAMESPACE