Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsd3d11rhitextureimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qvideotexturehelper_p.h>
7
8#include <QtGui/rhi/qrhi.h>
9
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qscopeguard.h>
12#include <QtCore/qvarlengtharray.h>
13#include <QtCore/private/qsystemerror_p.h>
14#include <QtCore/private/qexpected_p.h>
15
16#include <d3d11on12.h>
17#include <d3d12.h>
18
19#include <algorithm>
20
21#if QT_CONFIG(opengl)
22# include <QtMultimedia/private/qmultimedia_gl_support_p.h>
23# include <QtMultimedia/private/qrhivaluemapper_p.h>
24# include <QtGui/qopenglcontext.h>
25# include <QtGui/qopenglextrafunctions.h>
26# include <QtGui/qopenglfunctions.h>
27# include <QtGui/qopenglext.h>
28#endif
29
30QT_BEGIN_NAMESPACE
31
32namespace QtMultimediaPrivate {
33
34using namespace Qt::Literals;
35
36namespace {
37
38Q_LOGGING_CATEGORY(lcD3D11RhiTextureImporter, "qt.multimedia.windows.d3d11rhitextureimporter");
39
40class D3D11TextureHandles : public QVideoFrameTexturesHandles
41{
42public:
43 explicit D3D11TextureHandles(ComPtr<ID3D11Texture2D> tex) : m_tex(std::move(tex)) { }
44
45 quint64 textureHandle(QRhi &rhi, int /*plane*/) override
46 {
47 if (rhi.backend() != QRhi::D3D11)
48 return 0;
49 return quint64(m_tex.Get());
50 }
51
52private:
53 const ComPtr<ID3D11Texture2D> m_tex;
54};
55
56const QRhiD3D11NativeHandles *resolveD3D11NativeHandles(QRhi &rhi)
57{
58 Q_ASSERT(rhi.backend() == QRhi::D3D11);
59 return static_cast<const QRhiD3D11NativeHandles *>(rhi.nativeHandles());
60}
61
62q23::expected<ComPtr<ID3D11Device1>, HRESULT>
63resolveD3D11Device(const QRhiD3D11NativeHandles *nativeHandles)
64{
65 ComPtr<ID3D11Device> rhiDevice = static_cast<ID3D11Device *>(nativeHandles->dev);
66 ComPtr<ID3D11Device1> rhiDevice1;
67 if (const HRESULT hr = rhiDevice.As(&rhiDevice1); FAILED(hr))
68 return q23::unexpected{ hr };
69 return rhiDevice1;
70}
71
72// Exposes the D3D12 view of a D3D11 texture, unwrapped via D3D11On12
73class D3D12TextureHandles : public QVideoFrameTexturesHandles
74{
75public:
76 D3D12TextureHandles(ComPtr<ID3D12Resource> resource, ComPtr<ID3D11On12Device2> on12Device,
77 ComPtr<ID3D11Texture2D> wrappedResource,
78 std::shared_ptr<std::atomic_bool> wrappedResourceInUse)
79 : m_resource(std::move(resource)),
80 m_on12Device(std::move(on12Device)),
81 m_wrappedResource(std::move(wrappedResource)),
82 m_wrappedResourceInUse(std::move(wrappedResourceInUse))
83 {
84 }
85
86 ~D3D12TextureHandles() override
87 {
88 m_resource.Reset();
89 if (m_on12Device && m_wrappedResource)
90 m_on12Device->ReturnUnderlyingResource(m_wrappedResource.Get(), 0, nullptr, nullptr);
91 // Only now may the texture be reused for another frame.
92 if (m_wrappedResourceInUse)
93 m_wrappedResourceInUse->store(false);
94 }
95
96 quint64 textureHandle(QRhi &rhi, int /*plane*/) override
97 {
98 switch (rhi.backend()) {
99 case QRhi::D3D12:
100 return quint64(m_resource.Get());
101 default:
102 return 0;
103 }
104 }
105
106private:
107 ComPtr<ID3D12Resource> m_resource;
108 const ComPtr<ID3D11On12Device2> m_on12Device;
109 const ComPtr<ID3D11Texture2D> m_wrappedResource;
110 const std::shared_ptr<std::atomic_bool> m_wrappedResourceInUse;
111};
112
113#if QT_CONFIG(opengl)
114
115// WGL_NV_DX_interop2 function types
116using PFNWGLDXOPENDEVICENVPROC = HANDLE(WINAPI *)(void *);
117using PFNWGLDXCLOSEDEVICENVPROC = BOOL(WINAPI *)(HANDLE);
118using PFNWGLDXREGISTEROBJECTNVPROC = HANDLE(WINAPI *)(HANDLE, void *, GLuint, GLenum, GLenum);
119using PFNWGLDXUNREGISTEROBJECTNVPROC = BOOL(WINAPI *)(HANDLE, HANDLE);
120using PFNWGLDXLOCKOBJECTSNVPROC = BOOL(WINAPI *)(HANDLE, GLint, HANDLE *);
121using PFNWGLDXUNLOCKOBJECTSNVPROC = BOOL(WINAPI *)(HANDLE, GLint, HANDLE *);
122
123# ifndef WGL_ACCESS_READ_ONLY_NV
124# define WGL_ACCESS_READ_ONLY_NV 0x00000000
125# endif
126
127struct WglDxInterop
128{
129 PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV = nullptr;
130 PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV = nullptr;
131 PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV = nullptr;
132 PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV = nullptr;
133 PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV = nullptr;
134 PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV = nullptr;
135
136 static WglDxInterop resolve(QOpenGLContext *ctx)
137 {
138 using namespace QtMultimediaPrivate;
139 WglDxInterop f;
140 if (!ctx)
141 return f;
142 f.wglDXOpenDeviceNV = resolveGlFunction<PFNWGLDXOPENDEVICENVPROC>(ctx, "wglDXOpenDeviceNV");
143 f.wglDXCloseDeviceNV =
144 resolveGlFunction<PFNWGLDXCLOSEDEVICENVPROC>(ctx, "wglDXCloseDeviceNV");
145 f.wglDXRegisterObjectNV =
146 resolveGlFunction<PFNWGLDXREGISTEROBJECTNVPROC>(ctx, "wglDXRegisterObjectNV");
147 f.wglDXUnregisterObjectNV =
148 resolveGlFunction<PFNWGLDXUNREGISTEROBJECTNVPROC>(ctx, "wglDXUnregisterObjectNV");
149 f.wglDXLockObjectsNV =
150 resolveGlFunction<PFNWGLDXLOCKOBJECTSNVPROC>(ctx, "wglDXLockObjectsNV");
151 f.wglDXUnlockObjectsNV =
152 resolveGlFunction<PFNWGLDXUNLOCKOBJECTSNVPROC>(ctx, "wglDXUnlockObjectsNV");
153 return f;
154 }
155
156 bool valid() const
157 {
158 return wglDXOpenDeviceNV && wglDXCloseDeviceNV && wglDXRegisterObjectNV
159 && wglDXUnregisterObjectNV && wglDXLockObjectsNV && wglDXUnlockObjectsNV;
160 }
161};
162
163QRhiValueMapper<WglDxInterop> g_wglDxInteropMapper;
164
165bool supportsGlCopy(QOpenGLContext &glCtx)
166{
167 return (glCtx.format().version() >= std::pair(4, 3)
168 || glCtx.hasExtension("GL_ARB_copy_image"_ba));
169}
170
171GLenum dxFormatToGlTextureFormat(DXGI_FORMAT d3dFormat)
172{
173 switch (d3dFormat) {
174 case DXGI_FORMAT_R8G8B8A8_UNORM:
175 return GL_RGBA8;
176 case DXGI_FORMAT_R8_UNORM:
177 return GL_R8;
178 case DXGI_FORMAT_R8G8_UNORM:
179 return GL_RG8;
180 case DXGI_FORMAT_R16_UNORM:
181 return GL_R16;
182 case DXGI_FORMAT_R16G16_UNORM:
183 return GL_RG16;
184 default:
185 return GLenum(0);
186 }
187}
188
189// Unlike the D3D11/D3D12 handles, which keep their shared source texture alive, the GL result
190// must survive the transient WGL registration (see importTextureOpenGL): makes a GPU-side copy
191// of 'source' into a standalone texture with its own storage. Leaves the GL_TEXTURE_2D binding
192// as found, since QRhi tracks GL state.
193GlTextureHandle copyToOwnedGlTexture(QOpenGLContext *glCtx, GLuint source, DXGI_FORMAT sourceFormat,
194 GLsizei width, GLsizei height)
195{
196 Q_ASSERT(supportsGlCopy(*glCtx));
197
198 const GLenum internalFormat = dxFormatToGlTextureFormat(sourceFormat);
199 if (!internalFormat) {
200 qCWarning(lcD3D11RhiTextureImporter)
201 << "Cannot import D3D11 texture to OpenGL: unsupported DXGI format" << sourceFormat;
202 return {};
203 }
204
205 QOpenGLExtraFunctions *f = glCtx->extraFunctions();
206 clearGlErrors(f);
207 const auto checkGlError = [&](const char *call) {
208 const GLError error = GLError(f->glGetError());
209 if (error == GLError::NoError)
210 return true;
211 qCWarning(lcD3D11RhiTextureImporter) << call << "failed with GL error" << error;
212 return false;
213 };
214
215 GLint previousBinding = 0;
216 f->glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousBinding);
217 if (!checkGlError("glGetIntegerv(GL_TEXTURE_BINDING_2D)"))
218 return {};
219 auto restoreBinding = qScopeGuard([&] {
220 f->glBindTexture(GL_TEXTURE_2D, GLuint(previousBinding));
221 });
222
223 GLuint texId = 0;
224 f->glGenTextures(1, &texId);
225 if (!checkGlError("glGenTextures") || !texId)
226 return {};
227 GlTextureHandle result{
228 texId,
229 GlTextureDeleter{ glCtx },
230 };
231
232 f->glBindTexture(GL_TEXTURE_2D, texId);
233 if (!checkGlError("glBindTexture"))
234 return {};
235
236 f->glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, width, height);
237 if (!checkGlError("glTexStorage2D"))
238 return {};
239
240 f->glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, texId, GL_TEXTURE_2D, 0, 0, 0, 0,
241 width, height, 1);
242 if (!checkGlError("glCopyImageSubData"))
243 return {};
244
245 return result;
246}
247
248class OpenGLTextureHandles : public QVideoFrameTexturesHandles
249{
250public:
251 explicit OpenGLTextureHandles(GlTextureHandle tex) : m_tex(std::move(tex)) { }
252
253 quint64 textureHandle(QRhi &rhi, int /*plane*/) override
254 {
255 if (rhi.backend() != QRhi::OpenGLES2)
256 return 0;
257 return quint64(m_tex.get());
258 }
259
260private:
261 const GlTextureHandle m_tex;
262};
263
264#endif // QT_CONFIG(opengl)
265
266// At most 2 planes: NV12 and P010 are luma plus a single chroma plane.
267class MultiPlaneTextureHandles : public QVideoFrameTexturesHandles
268{
269public:
270 explicit MultiPlaneTextureHandles(QVarLengthArray<QVideoFrameTexturesHandlesUPtr, 2> planes)
271 : m_planes(std::move(planes))
272 {
273 }
274
275 quint64 textureHandle(QRhi &rhi, int plane) override
276 {
277 if (plane < 0 || plane >= m_planes.size() || !m_planes[plane])
278 return 0;
279 return m_planes[plane]->textureHandle(rhi, 0);
280 }
281
282private:
283 const QVarLengthArray<QVideoFrameTexturesHandlesUPtr, 2> m_planes;
284};
285
286} // namespace
287
290
294{
295 const auto matches = [&](const Texture &entry) {
302 };
303
305 [&](const Texture &entry) {
306 return !entry.inUse->load() && !matches(entry);
307 }),
308 m_textures.end());
309
310 for (const Texture &entry : m_textures) {
311 if (!entry.inUse->load() && matches(entry)) {
312 entry.inUse->store(true);
313 return entry;
314 }
315 }
316
317 Texture entry{ {}, std::make_shared<std::atomic_bool>(true) };
318 if (const HRESULT hr = device->CreateTexture2D(&desc, nullptr, &entry.texture); FAILED(hr)) {
320 << "CreateTexture2D failed for D3D12 import:" << QSystemError::windowsComString(hr);
321 return {};
322 }
324 return entry;
325}
326
328{
329 switch (rhi.backend()) {
330 case QRhi::D3D11: {
331 // A software (WARP) D3D11 device lives on a different adapter than the source device,
332 // so opening a shared handle across them would either fail or, worse, block on the
333 // keyed mutex until the finite timeout expires. Treat it the same as unsupported.
335 return false;
336
338 return nh && nh->dev;
339 }
340 case QRhi::D3D12:
341 return true;
342#if QT_CONFIG(opengl)
343 case QRhi::OpenGLES2: {
344 const auto *nh = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
345 if (!nh || !nh->context)
346 return false;
347 if (!supportsGlCopy(*nh->context))
348 return false; // GL_ARB_copy_image or OpenGL >= 4.3 is required to copy the D3D texture into a GL texture
349
352 return WglDxInterop::resolve(glCtx);
353 });
354 return wgl.valid();
355 }
356#endif
357 default:
358 return false;
359 }
360}
361
364{
365 switch (rhi.backend()) {
366 case QRhi::D3D12:
367 case QRhi::OpenGLES2: {
369 if (desc && desc->nplanes == 1)
370 return true;
372 }
373 default:
374 return true;
375 }
376}
377
404
409{
411
415 if (!m_planeSplitter) {
416 qCWarning(lcD3D11RhiTextureImporter) << "Cannot create plane splitter";
417 return nullptr;
418 }
419 }
420
424 if (!planes)
425 return nullptr;
426
427 if (planes->size() > 2) {
428 qCWarning(lcD3D11RhiTextureImporter) << "Splitting more than 2 planes is not supported";
429 return nullptr;
430 }
431
434#if QT_CONFIG(opengl)
436#endif
437
439 for (size_t i = 0; i < planes->size(); ++i) {
441 if (rhi.backend() == QRhi::D3D12) {
443 (*planes)[i].size, 0, *bridges[i], *outputPools[i]);
444 }
445#if QT_CONFIG(opengl)
446 else {
448 (*planes)[i].size, *glInteropSlots[i]);
449 }
450#endif
451 if (!handles)
452 return nullptr;
454 }
455
457}
458
462{
465 Q_ASSERT(nh && nh->dev);
466
470
472 if (!device) {
473 qCWarning(lcD3D11RhiTextureImporter) << "QueryInterface(ID3D11Device1) failed:"
475 return nullptr;
476 }
478
481
483 return nullptr;
484
486 if (!rhiTex)
487 return nullptr;
488
490}
491
496{
501
502 const auto *nh = static_cast<const QRhiD3D12NativeHandles *>(rhi.nativeHandles());
503 if (!nh || !nh->dev || !nh->commandQueue)
504 return nullptr;
505
506 auto *d3d12Device = static_cast<ID3D12Device *>(nh->dev);
507 auto *cmdQueue = static_cast<ID3D12CommandQueue *>(nh->commandQueue);
508
509 if (!m_d3d11on12Device) {
510 // D3D11On12 needs a queue of its own: UnwrapUnderlyingResource() makes cmdQueue wait
511 // for a fence that D3D11On12 only signals in its next submission. If that submission
512 // goes to cmdQueue as well, it is queued behind the wait and cmdQueue deadlocks.
518 FAILED(hr)) {
520 << "CreateCommandQueue:" << QSystemError::windowsComString(hr);
521 return nullptr;
522 }
523
524 IUnknown *queues[] = { on12Queue.Get() };
525 HRESULT hr = D3D11On12CreateDevice(d3d12Device, 0, nullptr, 0, queues, 1, 0,
526 &m_d3d11on12Device, nullptr, nullptr);
527 if (FAILED(hr)) {
529 << "D3D11On12CreateDevice:" << QSystemError::windowsComString(hr);
530 return nullptr;
531 }
532 }
533
537 << "QueryInterface(ID3D11Device1) failed:" << QSystemError::windowsComString(hr);
538 return nullptr;
539 }
540
543
545 return nullptr;
546
550 outputDesc.Width = static_cast<UINT>(frameSize.width());
551 outputDesc.Height = static_cast<UINT>(frameSize.height());
558
560 if (!output.texture)
561 return nullptr;
562 // Give the texture back to the pool unless handles end up owning it.
563 auto releaseOutput = qScopeGuard([&] {
564 output.inUse->store(false);
565 });
566
568 return nullptr;
569
572 qCWarning(lcD3D11RhiTextureImporter) << "QueryInterface(ID3D11On12Device2) failed:"
574 return nullptr;
575 }
576
580 FAILED(hr)) {
582 << "UnwrapUnderlyingResource:" << QSystemError::windowsComString(hr);
583 return nullptr;
584 }
585
586 // UnwrapUnderlyingResource() doesn't flush: submit the copy and the fence signal that
587 // cmdQueue now waits for.
589
593 std::move(output.inUse));
594}
595
596#if QT_CONFIG(opengl)
597
602{
607
608 const auto *nh = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
609 if (!nh || !nh->context)
610 return nullptr;
612
614 return WglDxInterop::resolve(glCtx);
615 });
616 if (!wgl.valid()) {
617 qCWarning(lcD3D11RhiTextureImporter) << "WGL_NV_DX_interop2 functions not available";
618 return nullptr;
619 }
620
621 // Reuse or create a non-shared texture matching the frame size for WGL interop (keyed-mutex
622 // textures are incompatible with wglDXRegisterObjectNV, and the source texture may be
623 // larger than the frame, e.g. due to a decoder's surface alignment requirements).
624 const UINT width = static_cast<UINT>(frameSize.width());
625 const UINT height = static_cast<UINT>(frameSize.height());
626 {
628 if (interopTexSlot) {
632 needRecreate = true;
633 }
634 if (needRecreate) {
637 desc.Width = width;
639 desc.ArraySize = 1;
640 desc.MiscFlags = 0;
642 if (FAILED(srcDevice->CreateTexture2D(&desc, nullptr,
644 return nullptr;
645 }
646
647 const D3D11_BOX crop{ 0, 0, 0, width, height, 1 };
649 &crop);
650 srcContext->Flush();
651 }
652
653 if (!m_glInteropDevice) {
655 if (!device) {
656 qCWarning(lcD3D11RhiTextureImporter) << "wglDXOpenDeviceNV failed";
657 return nullptr;
658 }
660 device,
662 };
663
664 // Release the WGL interop device before the GL context is destroyed to avoid leaking
665 // the handle.
668
670 [this] {
672 });
673 }
674
675 GLuint texId = 0;
677 f->glGenTextures(1, &texId);
678 if (!texId)
679 return nullptr;
681 texId,
683 };
684
688 if (!interopObj) {
689 qCWarning(lcD3D11RhiTextureImporter) << "wglDXRegisterObjectNV failed";
690 return nullptr;
691 }
694 });
695
697 qCWarning(lcD3D11RhiTextureImporter) << "wglDXLockObjectsNV failed";
698 return nullptr;
699 }
700
701 // GL may only access a registered object while it is locked, and unregistering it detaches
702 // 'interopGlTex' from the D3D11 texture. So copy it into a texture that GL owns while it is
703 // locked. The copy stays valid after unregistering, and isn't affected when 'interopTexSlot'
704 // is reused for the next frame. Locking also synchronizes the D3D11 copy above with GL.
710
711 if (!ownedGlTex)
712 return nullptr;
713
715}
716
718{
722}
723
724#endif // QT_CONFIG(opengl)
725
726} // namespace QtMultimediaPrivate
727
728QT_END_NAMESPACE