1// Copyright (C) 2019 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
4#include "qsgrhisupport_p.h"
5#include "qsgcontext_p.h"
6#include "qsgdefaultrendercontext_p.h"
7
8#include <QtQuick/private/qquickitem_p.h>
9#include <QtQuick/private/qquickwindow_p.h>
10
11#include <QtGui/qwindow.h>
12
13#if QT_CONFIG(vulkan)
14#include <QtGui/private/qvulkandefaultinstance_p.h>
15#endif
16
17#include <QOperatingSystemVersion>
18#include <QLockFile>
19#include <QSaveFile>
20#include <QStandardPaths>
21#include <QDir>
22#include <QFileInfo>
23#include <QSysInfo>
24#include <QOffscreenSurface>
25
26#ifdef Q_OS_WIN
27#include <dxgiformat.h>
28#endif
29
30#include <memory>
31
32QT_BEGIN_NAMESPACE
33
34QSGRhiSupport::QSGRhiSupport()
35{
36}
37
38void QSGRhiSupport::applySettings()
39{
40 // Multiple calls to this function are perfectly possible!
41 // Just store that it was called at least once.
42 m_settingsApplied = true;
43
44 // This is also done when creating the renderloop but we may be before that
45 // in case we get here due to a setGraphicsApi() -> configure() early
46 // on in main(). Avoid losing info logs since troubleshooting gets
47 // confusing otherwise.
48 QSGRhiSupport::checkEnvQSgInfo();
49
50 if (m_requested.valid) {
51 // explicit rhi backend request from C++ (e.g. via QQuickWindow)
52 switch (m_requested.api) {
53 case QSGRendererInterface::OpenGL:
54 m_rhiBackend = QRhi::OpenGLES2;
55 break;
56 case QSGRendererInterface::Direct3D11:
57 m_rhiBackend = QRhi::D3D11;
58 break;
59 case QSGRendererInterface::Direct3D12:
60 m_rhiBackend = QRhi::D3D12;
61 break;
62 case QSGRendererInterface::Vulkan:
63 m_rhiBackend = QRhi::Vulkan;
64 break;
65 case QSGRendererInterface::Metal:
66 m_rhiBackend = QRhi::Metal;
67 break;
68 case QSGRendererInterface::Null:
69 m_rhiBackend = QRhi::Null;
70 break;
71 default:
72 Q_ASSERT_X(false, "QSGRhiSupport", "Internal error: unhandled GraphicsApi type");
73 break;
74 }
75 } else {
76 // check env.vars., fall back to platform-specific defaults when backend is not set
77 const QByteArray rhiBackend = qgetenv(varName: "QSG_RHI_BACKEND");
78 if (rhiBackend == QByteArrayLiteral("gl")
79 || rhiBackend == QByteArrayLiteral("gles2")
80 || rhiBackend == QByteArrayLiteral("opengl"))
81 {
82 m_rhiBackend = QRhi::OpenGLES2;
83 } else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
84 m_rhiBackend = QRhi::D3D11;
85 } else if (rhiBackend == QByteArrayLiteral("d3d12")) {
86 m_rhiBackend = QRhi::D3D12;
87 } else if (rhiBackend == QByteArrayLiteral("vulkan")) {
88 m_rhiBackend = QRhi::Vulkan;
89 } else if (rhiBackend == QByteArrayLiteral("metal")) {
90 m_rhiBackend = QRhi::Metal;
91 } else if (rhiBackend == QByteArrayLiteral("null")) {
92 m_rhiBackend = QRhi::Null;
93 } else {
94 if (!rhiBackend.isEmpty()) {
95 qWarning(msg: "Unknown key \"%s\" for QSG_RHI_BACKEND, falling back to default backend.",
96 rhiBackend.constData());
97 }
98#if defined(Q_OS_WIN)
99 m_rhiBackend = QRhi::D3D11;
100#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
101 m_rhiBackend = QRhi::Metal;
102#elif QT_CONFIG(opengl)
103 m_rhiBackend = QRhi::OpenGLES2;
104#else
105 m_rhiBackend = QRhi::Vulkan;
106#endif
107
108 // Now that we established our initial choice, we may want to opt
109 // for another backend under certain special circumstances.
110 adjustToPlatformQuirks();
111 }
112 }
113
114 // At this point the RHI backend is fixed, it cannot be changed once we
115 // return from this function. This is because things like the QWindow
116 // (QQuickWindow) may depend on the graphics API as well (surfaceType
117 // f.ex.), and all that is based on what we report from here. So further
118 // adjustments are not possible (or, at minimum, not safe and portable).
119}
120
121void QSGRhiSupport::adjustToPlatformQuirks()
122{
123#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
124 // A macOS VM may not have Metal support at all. We have to decide at this
125 // point, it will be too late afterwards, and the only way is to see if
126 // MTLCreateSystemDefaultDevice succeeds.
127 if (m_rhiBackend == QRhi::Metal) {
128 QRhiMetalInitParams rhiParams;
129 if (!QRhi::probe(m_rhiBackend, &rhiParams)) {
130 m_rhiBackend = QRhi::OpenGLES2;
131 qCDebug(QSG_LOG_INFO, "Metal does not seem to be supported. Falling back to OpenGL.");
132 }
133 }
134#endif
135}
136
137void QSGRhiSupport::checkEnvQSgInfo()
138{
139 // For compatibility with 5.3 and earlier's QSG_INFO environment variables
140 if (qEnvironmentVariableIsSet(varName: "QSG_INFO"))
141 const_cast<QLoggingCategory &>(QSG_LOG_INFO()).setEnabled(type: QtDebugMsg, enable: true);
142}
143
144
145#if QT_CONFIG(opengl)
146#ifndef GL_BGRA
147#define GL_BGRA 0x80E1
148#endif
149
150#ifndef GL_R8
151#define GL_R8 0x8229
152#endif
153
154#ifndef GL_RG8
155#define GL_RG8 0x822B
156#endif
157
158#ifndef GL_RG
159#define GL_RG 0x8227
160#endif
161
162#ifndef GL_R16
163#define GL_R16 0x822A
164#endif
165
166#ifndef GL_RG16
167#define GL_RG16 0x822C
168#endif
169
170#ifndef GL_RED
171#define GL_RED 0x1903
172#endif
173
174#ifndef GL_RGBA8
175#define GL_RGBA8 0x8058
176#endif
177
178#ifndef GL_RGBA32F
179#define GL_RGBA32F 0x8814
180#endif
181
182#ifndef GL_RGBA16F
183#define GL_RGBA16F 0x881A
184#endif
185
186#ifndef GL_R16F
187#define GL_R16F 0x822D
188#endif
189
190#ifndef GL_R32F
191#define GL_R32F 0x822E
192#endif
193
194#ifndef GL_DEPTH_COMPONENT16
195#define GL_DEPTH_COMPONENT16 0x81A5
196#endif
197
198#ifndef GL_DEPTH_COMPONENT24
199#define GL_DEPTH_COMPONENT24 0x81A6
200#endif
201
202#ifndef GL_DEPTH_COMPONENT32F
203#define GL_DEPTH_COMPONENT32F 0x8CAC
204#endif
205
206#ifndef GL_DEPTH24_STENCIL8
207#define GL_DEPTH24_STENCIL8 0x88F0
208#endif
209
210#ifndef GL_DEPTH_STENCIL
211#define GL_DEPTH_STENCIL 0x84F9
212#endif
213
214#ifndef GL_RGB10_A2
215#define GL_RGB10_A2 0x8059
216#endif
217
218QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromGL(uint format)
219{
220 auto rhiFormat = QRhiTexture::UnknownFormat;
221 switch (format) {
222 case GL_RGBA:
223 Q_FALLTHROUGH();
224 case GL_RGBA8:
225 case 0:
226 rhiFormat = QRhiTexture::RGBA8;
227 break;
228 case GL_BGRA:
229 rhiFormat = QRhiTexture::BGRA8;
230 break;
231 case GL_R16:
232 rhiFormat = QRhiTexture::R16;
233 break;
234 case GL_RG16:
235 rhiFormat = QRhiTexture::RG16;
236 break;
237 case GL_RED:
238 Q_FALLTHROUGH();
239 case GL_R8:
240 rhiFormat = QRhiTexture::R8;
241 break;
242 case GL_RG:
243 Q_FALLTHROUGH();
244 case GL_RG8:
245 rhiFormat = QRhiTexture::RG8;
246 break;
247 case GL_ALPHA:
248 rhiFormat = QRhiTexture::RED_OR_ALPHA8;
249 break;
250 case GL_RGBA16F:
251 rhiFormat = QRhiTexture::RGBA16F;
252 break;
253 case GL_RGBA32F:
254 rhiFormat = QRhiTexture::RGBA32F;
255 break;
256 case GL_R16F:
257 rhiFormat = QRhiTexture::R16F;
258 break;
259 case GL_R32F:
260 rhiFormat = QRhiTexture::R32F;
261 break;
262 case GL_RGB10_A2:
263 rhiFormat = QRhiTexture::RGB10A2;
264 break;
265 case GL_DEPTH_COMPONENT:
266 Q_FALLTHROUGH();
267 case GL_DEPTH_COMPONENT16:
268 rhiFormat = QRhiTexture::D16;
269 break;
270 case GL_DEPTH_COMPONENT24:
271 rhiFormat = QRhiTexture::D24;
272 break;
273 case GL_DEPTH_STENCIL:
274 Q_FALLTHROUGH();
275 case GL_DEPTH24_STENCIL8:
276 rhiFormat = QRhiTexture::D24S8;
277 break;
278 case GL_DEPTH_COMPONENT32F:
279 rhiFormat = QRhiTexture::D32F;
280 break;
281 default:
282 qWarning(msg: "GL format %d is not supported", format);
283 break;
284 }
285 return rhiFormat;
286}
287#endif
288
289#if QT_CONFIG(vulkan)
290QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromVulkan(uint format, QRhiTexture::Flags *flags)
291{
292 auto rhiFormat = QRhiTexture::UnknownFormat;
293 bool sRGB = false;
294 switch (format) {
295 case VK_FORMAT_R8G8B8A8_SRGB:
296 sRGB = true;
297 Q_FALLTHROUGH();
298 case VK_FORMAT_R8G8B8A8_UNORM:
299 case VK_FORMAT_UNDEFINED:
300 rhiFormat = QRhiTexture::RGBA8;
301 break;
302 case VK_FORMAT_B8G8R8A8_SRGB:
303 sRGB = true;
304 Q_FALLTHROUGH();
305 case VK_FORMAT_B8G8R8A8_UNORM:
306 rhiFormat = QRhiTexture::BGRA8;
307 break;
308 case VK_FORMAT_R8_SRGB:
309 sRGB = true;
310 Q_FALLTHROUGH();
311 case VK_FORMAT_R8_UNORM:
312 rhiFormat = QRhiTexture::R8;
313 break;
314 case VK_FORMAT_R8G8_SRGB:
315 sRGB = true;
316 Q_FALLTHROUGH();
317 case VK_FORMAT_R8G8_UNORM:
318 rhiFormat = QRhiTexture::RG8;
319 break;
320 case VK_FORMAT_R16_UNORM:
321 rhiFormat = QRhiTexture::R16;
322 break;
323 case VK_FORMAT_R16G16_UNORM:
324 rhiFormat = QRhiTexture::RG16;
325 break;
326 case VK_FORMAT_R16G16B16A16_SFLOAT:
327 rhiFormat = QRhiTexture::RGBA16F;
328 break;
329 case VK_FORMAT_R32G32B32A32_SFLOAT:
330 rhiFormat = QRhiTexture::RGBA32F;
331 break;
332 case VK_FORMAT_R16_SFLOAT:
333 rhiFormat = QRhiTexture::R16F;
334 break;
335 case VK_FORMAT_R32_SFLOAT:
336 rhiFormat = QRhiTexture::R32F;
337 break;
338 case VK_FORMAT_A2B10G10R10_UNORM_PACK32: // intentionally
339 Q_FALLTHROUGH();
340 case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
341 rhiFormat = QRhiTexture::RGB10A2;
342 break;
343 case VK_FORMAT_D16_UNORM:
344 rhiFormat = QRhiTexture::D16;
345 break;
346 case VK_FORMAT_X8_D24_UNORM_PACK32:
347 rhiFormat = QRhiTexture::D24;
348 break;
349 case VK_FORMAT_D24_UNORM_S8_UINT:
350 rhiFormat = QRhiTexture::D24S8;
351 break;
352 case VK_FORMAT_D32_SFLOAT:
353 rhiFormat = QRhiTexture::D32F;
354 break;
355 case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
356 sRGB = true;
357 Q_FALLTHROUGH();
358 case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
359 rhiFormat = QRhiTexture::BC1;
360 break;
361 case VK_FORMAT_BC2_SRGB_BLOCK:
362 sRGB = true;
363 Q_FALLTHROUGH();
364 case VK_FORMAT_BC2_UNORM_BLOCK:
365 rhiFormat = QRhiTexture::BC2;
366 break;
367 case VK_FORMAT_BC3_SRGB_BLOCK:
368 sRGB = true;
369 Q_FALLTHROUGH();
370 case VK_FORMAT_BC3_UNORM_BLOCK:
371 rhiFormat = QRhiTexture::BC3;
372 break;
373 case VK_FORMAT_BC4_UNORM_BLOCK:
374 rhiFormat = QRhiTexture::BC4;
375 break;
376 case VK_FORMAT_BC5_UNORM_BLOCK:
377 rhiFormat = QRhiTexture::BC5;
378 break;
379 case VK_FORMAT_BC6H_UFLOAT_BLOCK:
380 rhiFormat = QRhiTexture::BC6H;
381 break;
382 case VK_FORMAT_BC7_SRGB_BLOCK:
383 sRGB = true;
384 Q_FALLTHROUGH();
385 case VK_FORMAT_BC7_UNORM_BLOCK:
386 rhiFormat = QRhiTexture::BC7;
387 break;
388 case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
389 sRGB = true;
390 Q_FALLTHROUGH();
391 case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
392 rhiFormat = QRhiTexture::ETC2_RGB8;
393 break;
394 case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
395 sRGB = true;
396 Q_FALLTHROUGH();
397 case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
398 rhiFormat = QRhiTexture::ETC2_RGB8A1;
399 break;
400 case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
401 sRGB = true;
402 Q_FALLTHROUGH();
403 case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
404 rhiFormat = QRhiTexture::ETC2_RGBA8;
405 break;
406 case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
407 sRGB = true;
408 Q_FALLTHROUGH();
409 case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
410 rhiFormat = QRhiTexture::ASTC_4x4;
411 break;
412 case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
413 sRGB = true;
414 Q_FALLTHROUGH();
415 case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
416 rhiFormat = QRhiTexture::ASTC_5x4;
417 break;
418 case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
419 sRGB = true;
420 Q_FALLTHROUGH();
421 case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
422 rhiFormat = QRhiTexture::ASTC_5x5;
423 break;
424 case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
425 sRGB = true;
426 Q_FALLTHROUGH();
427 case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
428 rhiFormat = QRhiTexture::ASTC_6x5;
429 break;
430 case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
431 sRGB = true;
432 Q_FALLTHROUGH();
433 case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
434 rhiFormat = QRhiTexture::ASTC_6x6;
435 break;
436 case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
437 sRGB = true;
438 Q_FALLTHROUGH();
439 case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
440 rhiFormat = QRhiTexture::ASTC_8x5;
441 break;
442 case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
443 sRGB = true;
444 Q_FALLTHROUGH();
445 case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
446 rhiFormat = QRhiTexture::ASTC_8x6;
447 break;
448 case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
449 sRGB = true;
450 Q_FALLTHROUGH();
451 case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
452 rhiFormat = QRhiTexture::ASTC_8x8;
453 break;
454 case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
455 sRGB = true;
456 Q_FALLTHROUGH();
457 case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
458 rhiFormat = QRhiTexture::ASTC_10x5;
459 break;
460 case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
461 sRGB = true;
462 Q_FALLTHROUGH();
463 case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
464 rhiFormat = QRhiTexture::ASTC_10x6;
465 break;
466 case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
467 sRGB = true;
468 Q_FALLTHROUGH();
469 case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
470 rhiFormat = QRhiTexture::ASTC_10x8;
471 break;
472 case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
473 sRGB = true;
474 Q_FALLTHROUGH();
475 case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
476 rhiFormat = QRhiTexture::ASTC_10x10;
477 break;
478 case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
479 sRGB = true;
480 Q_FALLTHROUGH();
481 case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
482 rhiFormat = QRhiTexture::ASTC_12x10;
483 break;
484 case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
485 sRGB = true;
486 Q_FALLTHROUGH();
487 case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
488 rhiFormat = QRhiTexture::ASTC_12x12;
489 break;
490 default:
491 qWarning(msg: "VkFormat %d is not supported", format);
492 break;
493 }
494 if (sRGB)
495 (*flags) |=(QRhiTexture::sRGB);
496 return rhiFormat;
497}
498#endif
499
500#ifdef Q_OS_WIN
501QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromDXGI(uint format, QRhiTexture::Flags *flags)
502{
503 auto rhiFormat = QRhiTexture::UnknownFormat;
504 bool sRGB = false;
505 switch (format) {
506 case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
507 sRGB = true;
508 Q_FALLTHROUGH();
509 case DXGI_FORMAT_R8G8B8A8_UNORM:
510 case DXGI_FORMAT_UNKNOWN:
511 rhiFormat = QRhiTexture::RGBA8;
512 break;
513 case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
514 sRGB = true;
515 Q_FALLTHROUGH();
516 case DXGI_FORMAT_B8G8R8A8_UNORM:
517 rhiFormat = QRhiTexture::BGRA8;
518 break;
519 case DXGI_FORMAT_R8_UNORM:
520 rhiFormat = QRhiTexture::R8;
521 break;
522 case DXGI_FORMAT_R8G8_UNORM:
523 rhiFormat = QRhiTexture::RG8;
524 break;
525 case DXGI_FORMAT_R16_UNORM:
526 rhiFormat = QRhiTexture::R16;
527 break;
528 case DXGI_FORMAT_R16G16_UNORM:
529 rhiFormat = QRhiTexture::RG16;
530 break;
531 case DXGI_FORMAT_R16G16B16A16_FLOAT:
532 rhiFormat = QRhiTexture::RGBA16F;
533 break;
534 case DXGI_FORMAT_R32G32B32A32_FLOAT:
535 rhiFormat = QRhiTexture::RGBA32F;
536 break;
537 case DXGI_FORMAT_R16_FLOAT:
538 rhiFormat = QRhiTexture::R16F;
539 break;
540 case DXGI_FORMAT_R32_FLOAT:
541 rhiFormat = QRhiTexture::R32F;
542 break;
543 case DXGI_FORMAT_R10G10B10A2_UNORM:
544 rhiFormat = QRhiTexture::RGB10A2;
545 break;
546 case DXGI_FORMAT_R16_TYPELESS:
547 rhiFormat = QRhiTexture::D16;
548 break;
549 case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
550 rhiFormat = QRhiTexture::D24;
551 break;
552 case DXGI_FORMAT_D24_UNORM_S8_UINT:
553 rhiFormat = QRhiTexture::D24S8;
554 break;
555 case DXGI_FORMAT_R32_TYPELESS:
556 rhiFormat = QRhiTexture::D32F;
557 break;
558 case DXGI_FORMAT_BC1_UNORM_SRGB:
559 sRGB = true;
560 Q_FALLTHROUGH();
561 case DXGI_FORMAT_BC1_UNORM:
562 rhiFormat = QRhiTexture::BC1;
563 break;
564 case DXGI_FORMAT_BC2_UNORM_SRGB:
565 sRGB = true;
566 Q_FALLTHROUGH();
567 case DXGI_FORMAT_BC2_UNORM:
568 rhiFormat = QRhiTexture::BC2;
569 break;
570 case DXGI_FORMAT_BC3_UNORM_SRGB:
571 sRGB = true;
572 Q_FALLTHROUGH();
573 case DXGI_FORMAT_BC3_UNORM:
574 rhiFormat = QRhiTexture::BC3;
575 break;
576 case DXGI_FORMAT_BC4_UNORM:
577 rhiFormat = QRhiTexture::BC4;
578 break;
579 case DXGI_FORMAT_BC5_UNORM:
580 rhiFormat = QRhiTexture::BC5;
581 break;
582 case DXGI_FORMAT_BC6H_UF16:
583 rhiFormat = QRhiTexture::BC6H;
584 break;
585 case DXGI_FORMAT_BC7_UNORM_SRGB:
586 sRGB = true;
587 Q_FALLTHROUGH();
588 case DXGI_FORMAT_BC7_UNORM:
589 rhiFormat = QRhiTexture::BC7;
590 break;
591 default:
592 qWarning("DXGI_FORMAT %d is not supported", format);
593 break;
594 }
595 if (sRGB)
596 (*flags) |=(QRhiTexture::sRGB);
597 return rhiFormat;
598}
599#endif
600
601#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
602namespace QSGRhiSupportMac {
603 QRhiTexture::Format toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags);
604}
605QRhiTexture::Format QSGRhiSupport::toRhiTextureFormatFromMetal(uint format, QRhiTexture::Flags *flags)
606{
607 return QSGRhiSupportMac::toRhiTextureFormatFromMetal(format, flags);
608}
609#endif
610
611void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api)
612{
613 if (api == QSGRendererInterface::Unknown) {
614 // behave as if nothing was explicitly requested
615 m_requested.valid = false;
616 applySettings();
617 } else {
618 Q_ASSERT(QSGRendererInterface::isApiRhiBased(api));
619 m_requested.valid = true;
620 m_requested.api = api;
621 applySettings();
622 }
623}
624
625QSGRhiSupport *QSGRhiSupport::instance_internal()
626{
627 static QSGRhiSupport inst;
628 return &inst;
629}
630
631QSGRhiSupport *QSGRhiSupport::instance()
632{
633 QSGRhiSupport *inst = instance_internal();
634 if (!inst->m_settingsApplied)
635 inst->applySettings();
636 return inst;
637}
638
639QString QSGRhiSupport::rhiBackendName() const
640{
641 return QString::fromUtf8(utf8: QRhi::backendName(impl: m_rhiBackend));
642}
643
644QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
645{
646 switch (m_rhiBackend) {
647 case QRhi::Null:
648 return QSGRendererInterface::Null;
649 case QRhi::Vulkan:
650 return QSGRendererInterface::Vulkan;
651 case QRhi::OpenGLES2:
652 return QSGRendererInterface::OpenGL;
653 case QRhi::D3D11:
654 return QSGRendererInterface::Direct3D11;
655 case QRhi::D3D12:
656 return QSGRendererInterface::Direct3D12;
657 case QRhi::Metal:
658 return QSGRendererInterface::Metal;
659 default:
660 return QSGRendererInterface::Unknown;
661 }
662}
663
664QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
665{
666 switch (m_rhiBackend) {
667 case QRhi::Vulkan:
668 return QSurface::VulkanSurface;
669 case QRhi::OpenGLES2:
670 return QSurface::OpenGLSurface;
671 case QRhi::D3D11:
672 case QRhi::D3D12:
673 return QSurface::Direct3DSurface;
674 case QRhi::Metal:
675 return QSurface::MetalSurface;
676 default:
677 return QSurface::OpenGLSurface;
678 }
679}
680
681#if QT_CONFIG(vulkan)
682static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res,
683 const QRhiNativeHandles *nat,
684 const QRhiNativeHandles *cbNat,
685 const QRhiNativeHandles *rpNat)
686{
687 const QRhiVulkanNativeHandles *vknat = static_cast<const QRhiVulkanNativeHandles *>(nat);
688 const QRhiVulkanCommandBufferNativeHandles *maybeVkCbNat =
689 static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbNat);
690 const QRhiVulkanRenderPassNativeHandles *maybeVkRpNat =
691 static_cast<const QRhiVulkanRenderPassNativeHandles *>(rpNat);
692
693 switch (res) {
694 case QSGRendererInterface::DeviceResource:
695 return &vknat->dev;
696 case QSGRendererInterface::CommandQueueResource:
697 return &vknat->gfxQueue;
698 case QSGRendererInterface::CommandListResource:
699 if (maybeVkCbNat)
700 return &maybeVkCbNat->commandBuffer;
701 else
702 return nullptr;
703 case QSGRendererInterface::PhysicalDeviceResource:
704 return &vknat->physDev;
705 case QSGRendererInterface::RenderPassResource:
706 if (maybeVkRpNat)
707 return &maybeVkRpNat->renderPass;
708 else
709 return nullptr;
710 case QSGRendererInterface::GraphicsQueueFamilyIndexResource:
711 return &vknat->gfxQueueFamilyIdx;
712 case QSGRendererInterface::GraphicsQueueIndexResource:
713 return &vknat->gfxQueueIdx;
714 default:
715 return nullptr;
716 }
717}
718#endif
719
720#if QT_CONFIG(opengl)
721static const void *qsgrhi_gl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
722{
723 const QRhiGles2NativeHandles *glnat = static_cast<const QRhiGles2NativeHandles *>(nat);
724 switch (res) {
725 case QSGRendererInterface::OpenGLContextResource:
726 return glnat->context;
727 default:
728 return nullptr;
729 }
730}
731#endif
732
733#ifdef Q_OS_WIN
734static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
735{
736 const QRhiD3D11NativeHandles *d3dnat = static_cast<const QRhiD3D11NativeHandles *>(nat);
737 switch (res) {
738 case QSGRendererInterface::DeviceResource:
739 return d3dnat->dev;
740 case QSGRendererInterface::DeviceContextResource:
741 return d3dnat->context;
742 default:
743 return nullptr;
744 }
745}
746
747static const void *qsgrhi_d3d12_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
748{
749 const QRhiD3D12NativeHandles *d3dnat = static_cast<const QRhiD3D12NativeHandles *>(nat);
750 switch (res) {
751 case QSGRendererInterface::DeviceResource:
752 return d3dnat->dev;
753 case QSGRendererInterface::CommandQueueResource:
754 return d3dnat->commandQueue;
755 default:
756 return nullptr;
757 }
758}
759#endif
760
761#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
762static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
763 const QRhiNativeHandles *cbNat)
764{
765 const QRhiMetalNativeHandles *mtlnat = static_cast<const QRhiMetalNativeHandles *>(nat);
766 const QRhiMetalCommandBufferNativeHandles *maybeMtlCbNat =
767 static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbNat);
768
769 switch (res) {
770 case QSGRendererInterface::DeviceResource:
771 return mtlnat->dev;
772 case QSGRendererInterface::CommandQueueResource:
773 return mtlnat->cmdQueue;
774 case QSGRendererInterface::CommandListResource:
775 if (maybeMtlCbNat)
776 return maybeMtlCbNat->commandBuffer;
777 else
778 return nullptr;
779 case QSGRendererInterface::CommandEncoderResource:
780 if (maybeMtlCbNat)
781 return maybeMtlCbNat->encoder;
782 else
783 return nullptr;
784 default:
785 return nullptr;
786 }
787}
788#endif
789
790const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res,
791 const QSGDefaultRenderContext *rc,
792 const QQuickWindow *w)
793{
794 QRhi *rhi = rc->rhi();
795 if (!rhi)
796 return nullptr;
797
798 // Accessing the underlying QRhi* objects are essential both for Qt Quick
799 // 3D and advanced solutions, such as VR engine integrations.
800 switch (res) {
801 case QSGRendererInterface::RhiResource:
802 return rhi;
803 case QSGRendererInterface::RhiSwapchainResource:
804 return QQuickWindowPrivate::get(c: w)->swapchain;
805 case QSGRendererInterface::RhiRedirectCommandBuffer:
806 return QQuickWindowPrivate::get(c: w)->redirect.commandBuffer;
807 case QSGRendererInterface::RhiRedirectRenderTarget:
808 return QQuickWindowPrivate::get(c: w)->redirect.rt.renderTarget;
809 default:
810 break;
811 }
812
813 const QRhiNativeHandles *nat = rhi->nativeHandles();
814 if (!nat)
815 return nullptr;
816
817 switch (m_rhiBackend) {
818#if QT_CONFIG(vulkan)
819 case QRhi::Vulkan:
820 {
821 QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
822 QRhiRenderPassDescriptor *rp = rc->currentFrameRenderPass();
823 return qsgrhi_vk_rifResource(res, nat,
824 cbNat: cb ? cb->nativeHandles() : nullptr,
825 rpNat: rp ? rp->nativeHandles() : nullptr);
826 }
827#endif
828#if QT_CONFIG(opengl)
829 case QRhi::OpenGLES2:
830 return qsgrhi_gl_rifResource(res, nat);
831#endif
832#ifdef Q_OS_WIN
833 case QRhi::D3D11:
834 return qsgrhi_d3d11_rifResource(res, nat);
835 case QRhi::D3D12:
836 return qsgrhi_d3d12_rifResource(res, nat);
837#endif
838#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
839 case QRhi::Metal:
840 {
841 QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
842 return qsgrhi_mtl_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
843 }
844#endif
845 default:
846 return nullptr;
847 }
848}
849
850int QSGRhiSupport::chooseSampleCount(int samples, QRhi *rhi)
851{
852 int msaaSampleCount = samples;
853 if (qEnvironmentVariableIsSet(varName: "QSG_SAMPLES"))
854 msaaSampleCount = qEnvironmentVariableIntValue(varName: "QSG_SAMPLES");
855 msaaSampleCount = qMax(a: 1, b: msaaSampleCount);
856 if (msaaSampleCount > 1) {
857 const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts();
858 if (!supportedSampleCounts.contains(t: msaaSampleCount)) {
859 int reducedSampleCount = 1;
860 for (int i = supportedSampleCounts.size() - 1; i >= 0; --i) {
861 if (supportedSampleCounts[i] <= msaaSampleCount) {
862 reducedSampleCount = supportedSampleCounts[i];
863 break;
864 }
865 }
866 qWarning() << "Requested MSAA sample count" << msaaSampleCount
867 << "but supported sample counts are" << supportedSampleCounts
868 << ", using sample count" << reducedSampleCount << "instead";
869 msaaSampleCount = reducedSampleCount;
870 }
871 }
872 return msaaSampleCount;
873}
874
875int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi)
876{
877 return chooseSampleCount(samples: qMax(a: QSurfaceFormat::defaultFormat().samples(), b: window->requestedFormat().samples()), rhi);
878}
879
880// must be called on the main thread
881QOffscreenSurface *QSGRhiSupport::maybeCreateOffscreenSurface(QWindow *window)
882{
883 QOffscreenSurface *offscreenSurface = nullptr;
884#if QT_CONFIG(opengl)
885 if (rhiBackend() == QRhi::OpenGLES2) {
886 const QSurfaceFormat format = window->requestedFormat();
887 offscreenSurface = QRhiGles2InitParams::newFallbackSurface(format);
888 }
889#else
890 Q_UNUSED(window);
891#endif
892 return offscreenSurface;
893}
894
895void QSGRhiSupport::prepareWindowForRhi(QQuickWindow *window)
896{
897#if QT_CONFIG(vulkan)
898 if (rhiBackend() == QRhi::Vulkan) {
899 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
900 // QQuickWindows must get a QVulkanInstance automatically (it is
901 // created when the first window is constructed and is destroyed only
902 // on exit), unless the application decided to set its own. With
903 // QQuickRenderControl, no QVulkanInstance is created, because it must
904 // always be under the application's control then (since the default
905 // instance we could create here would not be configurable by the
906 // application in any way, and that is often not acceptable).
907 if (!window->vulkanInstance() && !wd->renderControl) {
908 QVulkanInstance *vkinst = QVulkanDefaultInstance::instance();
909 if (vkinst)
910 qCDebug(QSG_LOG_INFO) << "Got Vulkan instance from QVulkanDefaultInstance, requested api version was" << vkinst->apiVersion();
911 else
912 qCDebug(QSG_LOG_INFO) << "No Vulkan instance from QVulkanDefaultInstance, expect problems";
913 window->setVulkanInstance(vkinst);
914 }
915 }
916#else
917 Q_UNUSED(window);
918#endif
919}
920
921static inline bool ensureWritableDir(const QString &name)
922{
923 QDir::root().mkpath(dirPath: name);
924 return QFileInfo(name).isWritable();
925}
926
927static QString automaticPipelineCacheDir()
928{
929 static bool checked = false;
930 static QString currentCacheDir;
931 static bool cacheWritable = false;
932
933 if (checked)
934 return cacheWritable ? currentCacheDir : QString();
935
936 checked = true;
937
938 // Intentionally not using the global cache path (GenericCacheLocation) -
939 // we do not want forever growing pipeline cache files that contain
940 // everything from all Qt apps ever run (that would affect load times
941 // eventually, resource use, etc.). Stick to being application-specific.
942
943 const QString cachePath = QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation);
944 const QString subPath = QLatin1String("/qtpipelinecache-") + QSysInfo::buildAbi() + QLatin1Char('/');
945
946 if (!cachePath.isEmpty()) {
947 currentCacheDir = cachePath + subPath;
948 cacheWritable = ensureWritableDir(name: currentCacheDir);
949 }
950
951 return cacheWritable ? currentCacheDir : QString();
952}
953
954static inline QString automaticPipelineCacheFileName(QRhi *rhi)
955{
956 const QString cacheDir = automaticPipelineCacheDir();
957 if (!cacheDir.isEmpty())
958 return cacheDir + QLatin1String("qqpc_") + QString::fromLatin1(ba: rhi->backendName()).toLower();
959
960 return QString();
961}
962
963static inline bool isAutomaticPipelineCacheLoadSkippedForWindow(Qt::WindowFlags wflags)
964{
965 return wflags.testFlag(flag: Qt::ToolTip) || wflags.testFlag(flag: Qt::SplashScreen);
966}
967
968static inline bool isAutomaticPipelineCacheSaveSkippedForWindow(Qt::WindowFlags wflags)
969{
970 // this catches Tool, ToolTip, SplashScreen as well
971 return wflags.testFlag(flag: Qt::Dialog) || wflags.testFlag(flag: Qt::Popup);
972}
973
974static inline QString pipelineCacheLockFileName(const QString &name)
975{
976 return name + QLatin1String(".lck");
977}
978
979void QSGRhiSupport::preparePipelineCache(QRhi *rhi, QQuickWindow *window)
980{
981 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
982
983 // the explicitly set filename always takes priority as per docs
984 QString pipelineCacheLoad = wd->graphicsConfig.pipelineCacheLoadFile();
985 bool isAutomatic = false;
986 if (pipelineCacheLoad.isEmpty() && wd->graphicsConfig.isAutomaticPipelineCacheEnabled()) {
987 if (!isAutomaticPipelineCacheLoadSkippedForWindow(wflags: window->flags())) {
988 pipelineCacheLoad = automaticPipelineCacheFileName(rhi);
989 isAutomatic = true;
990 }
991 }
992
993 if (pipelineCacheLoad.isEmpty())
994 return;
995
996 QLockFile lock(pipelineCacheLockFileName(name: pipelineCacheLoad));
997 if (!lock.lock()) {
998 qWarning(msg: "Could not create pipeline cache lock file '%s'",
999 qPrintable(lock.fileName()));
1000 return;
1001 }
1002
1003 QFile f(pipelineCacheLoad);
1004 if (!f.open(flags: QIODevice::ReadOnly)) {
1005 if (!isAutomatic) {
1006 qWarning(msg: "Could not open pipeline cache source file '%s'",
1007 qPrintable(pipelineCacheLoad));
1008 }
1009 return;
1010 }
1011
1012 const QByteArray buf = f.readAll();
1013 if (!buf.isEmpty()) {
1014 qCDebug(QSG_LOG_INFO, "Attempting to seed pipeline cache for QRhi %p from '%s'",
1015 rhi, qPrintable(pipelineCacheLoad));
1016 rhi->setPipelineCacheData(buf);
1017 }
1018}
1019
1020void QSGRhiSupport::finalizePipelineCache(QRhi *rhi, const QQuickGraphicsConfiguration &config)
1021{
1022 // output the rhi statistics about pipelines, as promised by the documentation
1023 qCDebug(QSG_LOG_INFO, "Total time spent on pipeline creation during the lifetime of the QRhi %p was %lld ms",
1024 rhi, rhi->statistics().totalPipelineCreationTime);
1025
1026 // the explicitly set filename always takes priority as per docs
1027 QString pipelineCacheSave = config.pipelineCacheSaveFile();
1028 bool isAutomatic = false;
1029 if (pipelineCacheSave.isEmpty() && config.isAutomaticPipelineCacheEnabled()) {
1030 pipelineCacheSave = automaticPipelineCacheFileName(rhi);
1031 isAutomatic = true;
1032 }
1033
1034 if (pipelineCacheSave.isEmpty())
1035 return;
1036
1037 const QByteArray buf = rhi->pipelineCacheData();
1038
1039 // If empty, do nothing. This is exactly what will happen if the rhi was
1040 // created without QRhi::EnablePipelineCacheDataSave set.
1041 if (buf.isEmpty()) {
1042 if (isAutomatic) {
1043 // Attempt to remove the file. If it does not exist or this fails,
1044 // that's fine. The goal is just to prevent warnings from
1045 // setPipelineCacheData in future runs, e.g. if the Qt or driver
1046 // version does not match _and_ we do not generate any data at run
1047 // time, then not writing the file out also means the warning would
1048 // appear again and again on every run. Prevent that.
1049 QDir().remove(fileName: pipelineCacheSave);
1050 }
1051 return;
1052 }
1053
1054 QLockFile lock(pipelineCacheLockFileName(name: pipelineCacheSave));
1055 if (!lock.lock()) {
1056 qWarning(msg: "Could not create pipeline cache lock file '%s'",
1057 qPrintable(lock.fileName()));
1058 return;
1059 }
1060
1061#if QT_CONFIG(temporaryfile)
1062 QSaveFile f(pipelineCacheSave);
1063#else
1064 QFile f(pipelineCacheSave);
1065#endif
1066 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
1067 if (!isAutomatic) {
1068 const QString msg = f.errorString();
1069 qWarning(msg: "Could not open pipeline cache output file '%s': %s",
1070 qPrintable(pipelineCacheSave), qPrintable(msg));
1071 }
1072 return;
1073 }
1074
1075 qCDebug(QSG_LOG_INFO, "Writing pipeline cache contents (%d bytes) for QRhi %p to '%s'",
1076 int(buf.size()), rhi, qPrintable(pipelineCacheSave));
1077
1078 if (f.write(data: buf) != buf.size()
1079#if QT_CONFIG(temporaryfile)
1080 || !f.commit()
1081#endif
1082 )
1083 {
1084 if (!isAutomatic) {
1085 const QString msg = f.errorString();
1086 qWarning(msg: "Could not write pipeline cache: %s", qPrintable(msg));
1087 }
1088 return;
1089 }
1090}
1091
1092// must be called on the render thread
1093QSGRhiSupport::RhiCreateResult QSGRhiSupport::createRhi(QQuickWindow *window, QSurface *offscreenSurface)
1094{
1095 QRhi *rhi = nullptr;
1096 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1097 const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(p: &wd->customDeviceObjects);
1098 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Rhi) {
1099 rhi = customDevD->u.rhi;
1100 if (rhi) {
1101 preparePipelineCache(rhi, window);
1102 return { .rhi: rhi, .own: false };
1103 }
1104 }
1105
1106 const bool debugLayer = wd->graphicsConfig.isDebugLayerEnabled();
1107 const bool debugMarkers = wd->graphicsConfig.isDebugMarkersEnabled();
1108 const bool timestamps = wd->graphicsConfig.timestampsEnabled();
1109 const bool preferSoftware = wd->graphicsConfig.prefersSoftwareDevice();
1110 const bool pipelineCacheSave = !wd->graphicsConfig.pipelineCacheSaveFile().isEmpty()
1111 || (wd->graphicsConfig.isAutomaticPipelineCacheEnabled()
1112 && !isAutomaticPipelineCacheSaveSkippedForWindow(wflags: window->flags()));
1113
1114 const QString backendName = rhiBackendName();
1115 qCDebug(QSG_LOG_INFO,
1116 "Creating QRhi with backend %s for window %p (wflags 0x%X)\n"
1117 " Graphics API debug/validation layers: %d\n"
1118 " Debug markers: %d\n"
1119 " Timestamps: %d\n"
1120 " Prefer software device: %d\n"
1121 " Shader/pipeline cache collection: %d",
1122 qPrintable(backendName), window, int(window->flags()), debugLayer,
1123 debugMarkers, timestamps, preferSoftware, pipelineCacheSave);
1124
1125 QRhi::Flags flags;
1126 if (debugMarkers)
1127 flags |= QRhi::EnableDebugMarkers;
1128 if (timestamps)
1129 flags |= QRhi::EnableTimestamps;
1130 if (preferSoftware)
1131 flags |= QRhi::PreferSoftwareRenderer;
1132 if (pipelineCacheSave)
1133 flags |= QRhi::EnablePipelineCacheDataSave;
1134
1135 const QRhi::Implementation backend = rhiBackend();
1136 if (backend == QRhi::Null) {
1137 QRhiNullInitParams rhiParams;
1138 rhi = QRhi::create(impl: backend, params: &rhiParams, flags);
1139 }
1140#if QT_CONFIG(opengl)
1141 if (backend == QRhi::OpenGLES2) {
1142 const QSurfaceFormat format = window->requestedFormat();
1143 QRhiGles2InitParams rhiParams;
1144 rhiParams.format = format;
1145 rhiParams.fallbackSurface = offscreenSurface;
1146 rhiParams.window = window;
1147 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::OpenGLContext) {
1148 QRhiGles2NativeHandles importDev;
1149 importDev.context = customDevD->u.context;
1150 qCDebug(QSG_LOG_INFO, "Using existing QOpenGLContext %p", importDev.context);
1151 rhi = QRhi::create(impl: backend, params: &rhiParams, flags, importDevice: &importDev);
1152 } else {
1153 rhi = QRhi::create(impl: backend, params: &rhiParams, flags);
1154 }
1155 }
1156#else
1157 Q_UNUSED(offscreenSurface);
1158 if (backend == QRhi::OpenGLES2)
1159 qWarning("OpenGL was requested for Qt Quick, but this build of Qt has no OpenGL support.");
1160#endif
1161#if QT_CONFIG(vulkan)
1162 if (backend == QRhi::Vulkan) {
1163 if (debugLayer)
1164 QVulkanDefaultInstance::setFlag(flag: QVulkanDefaultInstance::EnableValidation, on: true);
1165 QRhiVulkanInitParams rhiParams;
1166 prepareWindowForRhi(window); // sets a vulkanInstance if not yet present
1167 rhiParams.inst = window->vulkanInstance();
1168 if (!rhiParams.inst)
1169 qWarning(msg: "No QVulkanInstance set for QQuickWindow, this is wrong.");
1170 if (window->handle()) // only used for vkGetPhysicalDeviceSurfaceSupportKHR and that implies having a valid native window
1171 rhiParams.window = window;
1172 rhiParams.deviceExtensions = wd->graphicsConfig.deviceExtensions();
1173 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceObjects) {
1174 QRhiVulkanNativeHandles importDev;
1175 importDev.physDev = reinterpret_cast<VkPhysicalDevice>(customDevD->u.deviceObjects.physicalDevice);
1176 importDev.dev = reinterpret_cast<VkDevice>(customDevD->u.deviceObjects.device);
1177 importDev.gfxQueueFamilyIdx = customDevD->u.deviceObjects.queueFamilyIndex;
1178 importDev.gfxQueueIdx = customDevD->u.deviceObjects.queueIndex;
1179 qCDebug(QSG_LOG_INFO, "Using existing native Vulkan physical device %p device %p graphics queue family index %d",
1180 importDev.physDev, importDev.dev, importDev.gfxQueueFamilyIdx);
1181 rhi = QRhi::create(impl: backend, params: &rhiParams, flags, importDevice: &importDev);
1182 } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::PhysicalDevice) {
1183 QRhiVulkanNativeHandles importDev;
1184 importDev.physDev = reinterpret_cast<VkPhysicalDevice>(customDevD->u.physicalDevice.physicalDevice);
1185 qCDebug(QSG_LOG_INFO, "Using existing native Vulkan physical device %p", importDev.physDev);
1186 rhi = QRhi::create(impl: backend, params: &rhiParams, flags, importDevice: &importDev);
1187 } else {
1188 rhi = QRhi::create(impl: backend, params: &rhiParams, flags);
1189 }
1190 }
1191#else
1192 if (backend == QRhi::Vulkan)
1193 qWarning("Vulkan was requested for Qt Quick, but this build of Qt has no Vulkan support.");
1194#endif
1195#ifdef Q_OS_WIN
1196 if (backend == QRhi::D3D11) {
1197 QRhiD3D11InitParams rhiParams;
1198 rhiParams.enableDebugLayer = debugLayer;
1199 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
1200 QRhiD3D11NativeHandles importDev;
1201 importDev.dev = customDevD->u.deviceAndContext.device;
1202 importDev.context = customDevD->u.deviceAndContext.context;
1203 qCDebug(QSG_LOG_INFO, "Using existing native D3D11 device %p and context %p",
1204 importDev.dev, importDev.context);
1205 rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
1206 } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) {
1207 QRhiD3D11NativeHandles importDev;
1208 importDev.adapterLuidLow = customDevD->u.adapter.luidLow;
1209 importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh;
1210 importDev.featureLevel = customDevD->u.adapter.featureLevel;
1211 qCDebug(QSG_LOG_INFO, "Using D3D11 adapter LUID %u, %d and feature level %d",
1212 importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.featureLevel);
1213 rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
1214 } else {
1215 rhi = QRhi::create(backend, &rhiParams, flags);
1216 if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
1217 qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
1218 "attempting to get a software rasterizer backed device instead");
1219 flags |= QRhi::PreferSoftwareRenderer;
1220 rhi = QRhi::create(backend, &rhiParams, flags);
1221 }
1222 }
1223 } else if (backend == QRhi::D3D12) {
1224 QRhiD3D12InitParams rhiParams;
1225 rhiParams.enableDebugLayer = debugLayer;
1226 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
1227 QRhiD3D12NativeHandles importDev;
1228 importDev.dev = customDevD->u.deviceAndContext.device;
1229 qCDebug(QSG_LOG_INFO, "Using existing native D3D12 device %p", importDev.dev);
1230 rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
1231 } else if (customDevD->type == QQuickGraphicsDevicePrivate::Type::Adapter) {
1232 QRhiD3D12NativeHandles importDev;
1233 importDev.adapterLuidLow = customDevD->u.adapter.luidLow;
1234 importDev.adapterLuidHigh = customDevD->u.adapter.luidHigh;
1235 importDev.minimumFeatureLevel = customDevD->u.adapter.featureLevel;
1236 qCDebug(QSG_LOG_INFO, "Using D3D12 adapter LUID %u, %d and minimum feature level %d",
1237 importDev.adapterLuidLow, importDev.adapterLuidHigh, importDev.minimumFeatureLevel);
1238 rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
1239 } else {
1240 rhi = QRhi::create(backend, &rhiParams, flags);
1241 if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
1242 qCDebug(QSG_LOG_INFO, "Failed to create a D3D device with default settings; "
1243 "attempting to get a software rasterizer backed device instead");
1244 flags |= QRhi::PreferSoftwareRenderer;
1245 rhi = QRhi::create(backend, &rhiParams, flags);
1246 }
1247 }
1248 }
1249#endif
1250#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
1251 if (backend == QRhi::Metal) {
1252 QRhiMetalInitParams rhiParams;
1253 if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) {
1254 QRhiMetalNativeHandles importDev;
1255 importDev.dev = (MTLDevice *) customDevD->u.deviceAndCommandQueue.device;
1256 importDev.cmdQueue = (MTLCommandQueue *) customDevD->u.deviceAndCommandQueue.cmdQueue;
1257 qCDebug(QSG_LOG_INFO, "Using existing native Metal device %p and command queue %p",
1258 importDev.dev, importDev.cmdQueue);
1259 rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
1260 } else {
1261 rhi = QRhi::create(backend, &rhiParams, flags);
1262 }
1263 }
1264#endif
1265
1266 if (rhi) {
1267 qCDebug(QSG_LOG_INFO, "Created QRhi %p for window %p", rhi, window);
1268 preparePipelineCache(rhi, window);
1269 } else {
1270 qWarning(msg: "Failed to create RHI (backend %d)", backend);
1271 }
1272
1273 return { .rhi: rhi, .own: true };
1274}
1275
1276void QSGRhiSupport::destroyRhi(QRhi *rhi, const QQuickGraphicsConfiguration &config)
1277{
1278 if (!rhi)
1279 return;
1280
1281 if (!rhi->isDeviceLost())
1282 finalizePipelineCache(rhi, config);
1283
1284 delete rhi;
1285}
1286
1287QImage QSGRhiSupport::grabAndBlockInCurrentFrame(QRhi *rhi, QRhiCommandBuffer *cb, QRhiTexture *src)
1288{
1289 Q_ASSERT(rhi->isRecordingFrame());
1290
1291 QRhiReadbackResult result;
1292 QRhiReadbackDescription readbackDesc(src); // null src == read from swapchain backbuffer
1293 QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
1294 resourceUpdates->readBackTexture(rb: readbackDesc, result: &result);
1295
1296 cb->resourceUpdate(resourceUpdates);
1297 rhi->finish(); // make sure the readback has finished, stall the pipeline if needed
1298
1299 // May be RGBA or BGRA. Plus premultiplied alpha.
1300 QImage::Format imageFormat;
1301 if (result.format == QRhiTexture::BGRA8) {
1302#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
1303 imageFormat = QImage::Format_ARGB32_Premultiplied;
1304#else
1305 imageFormat = QImage::Format_RGBA8888_Premultiplied;
1306 // ### and should swap too
1307#endif
1308 } else {
1309 imageFormat = QImage::Format_RGBA8888_Premultiplied;
1310 }
1311
1312 const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
1313 const QImage img(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat);
1314
1315 if (rhi->isYUpInFramebuffer())
1316 return img.mirrored();
1317
1318 return img.copy();
1319}
1320
1321QImage QSGRhiSupport::grabOffscreen(QQuickWindow *window)
1322{
1323 // Set up and then tear down the entire rendering infrastructure. This
1324 // function is called on the gui/main thread - but that's alright because
1325 // there is no onscreen rendering initialized at this point (so no render
1326 // thread for instance).
1327
1328 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
1329 // It is expected that window is not using QQuickRenderControl, i.e. it is
1330 // a normal QQuickWindow that just happens to be not exposed.
1331 Q_ASSERT(!wd->renderControl);
1332
1333 QScopedPointer<QOffscreenSurface> offscreenSurface(maybeCreateOffscreenSurface(window));
1334 RhiCreateResult rhiResult = createRhi(window, offscreenSurface: offscreenSurface.data());
1335 if (!rhiResult.rhi) {
1336 qWarning(msg: "Failed to initialize QRhi for offscreen readback");
1337 return QImage();
1338 }
1339 std::unique_ptr<QRhi> rhiOwner(rhiResult.rhi);
1340 QRhi *rhi = rhiResult.own ? rhiOwner.get() : rhiOwner.release();
1341
1342 const QSize pixelSize = window->size() * window->devicePixelRatio();
1343 QScopedPointer<QRhiTexture> texture(rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize, sampleCount: 1,
1344 flags: QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
1345 if (!texture->create()) {
1346 qWarning(msg: "Failed to build texture for offscreen readback");
1347 return QImage();
1348 }
1349 QScopedPointer<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount: 1));
1350 if (!depthStencil->create()) {
1351 qWarning(msg: "Failed to create depth/stencil buffer for offscreen readback");
1352 return QImage();
1353 }
1354 QRhiTextureRenderTargetDescription rtDesc(texture.data());
1355 rtDesc.setDepthStencilBuffer(depthStencil.data());
1356 QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc: rtDesc));
1357 QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
1358 rt->setRenderPassDescriptor(rpDesc.data());
1359 if (!rt->create()) {
1360 qWarning(msg: "Failed to build render target for offscreen readback");
1361 return QImage();
1362 }
1363
1364 wd->rhi = rhi;
1365
1366 QSGDefaultRenderContext::InitParams params;
1367 params.rhi = rhi;
1368 params.sampleCount = 1;
1369 params.initialSurfacePixelSize = pixelSize;
1370 params.maybeSurface = window;
1371 wd->context->initialize(params: &params);
1372
1373 // There was no rendercontrol which means a custom render target
1374 // should not be set either. Set our own, temporarily.
1375 window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(renderTarget: rt.data()));
1376
1377 QRhiCommandBuffer *cb = nullptr;
1378 if (rhi->beginOffscreenFrame(cb: &cb) != QRhi::FrameOpSuccess) {
1379 qWarning(msg: "Failed to start recording the frame for offscreen readback");
1380 return QImage();
1381 }
1382
1383 wd->setCustomCommandBuffer(cb);
1384 wd->polishItems();
1385 wd->syncSceneGraph();
1386 wd->renderSceneGraph();
1387 wd->setCustomCommandBuffer(nullptr);
1388
1389 QImage image = grabAndBlockInCurrentFrame(rhi, cb, src: texture.data());
1390 rhi->endOffscreenFrame();
1391
1392 image.setDevicePixelRatio(window->devicePixelRatio());
1393 wd->cleanupNodesOnShutdown();
1394 wd->context->invalidate();
1395
1396 window->setRenderTarget(QQuickRenderTarget());
1397 wd->rhi = nullptr;
1398
1399 return image;
1400}
1401
1402#ifdef Q_OS_WEBOS
1403QImage QSGRhiSupport::grabOffscreenForProtectedContent(QQuickWindow *window)
1404{
1405 // If a context is created for protected content, grabbing GPU
1406 // resources are restricted. For the case, normal context
1407 // and surface are needed to allow CPU access.
1408 // So dummy offscreen window is used here
1409 // This function is called in rendering thread.
1410
1411 QScopedPointer<QQuickWindow> offscreenWindow;
1412 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1413 // It is expected that window is not using QQuickRenderControl, i.e. it is
1414 // a normal QQuickWindow that just happens to be not exposed.
1415 Q_ASSERT(!wd->renderControl);
1416
1417 // If context and surface are created for protected content,
1418 // CPU can't read the frame resources. So normal context and surface are needed.
1419 if (window->requestedFormat().testOption(QSurfaceFormat::ProtectedContent)) {
1420 QSurfaceFormat surfaceFormat = window->requestedFormat();
1421 surfaceFormat.setOption(QSurfaceFormat::ProtectedContent, false);
1422 offscreenWindow.reset(new QQuickWindow());
1423 offscreenWindow->setFormat(surfaceFormat);
1424 }
1425
1426 QScopedPointer<QOffscreenSurface> offscreenSurface(maybeCreateOffscreenSurface(window));
1427 RhiCreateResult rhiResult = createRhi(offscreenWindow.data() ? offscreenWindow.data() : window, offscreenSurface.data());
1428 if (!rhiResult.rhi) {
1429 qWarning("Failed to initialize QRhi for offscreen readback");
1430 return QImage();
1431 }
1432 QScopedPointer<QRhi> rhiOwner(rhiResult.rhi);
1433 QRhi *rhi = rhiResult.own ? rhiOwner.data() : rhiOwner.take();
1434
1435 const QSize pixelSize = window->size() * window->devicePixelRatio();
1436 QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, pixelSize, 1,
1437 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
1438 if (!texture->create()) {
1439 qWarning("Failed to build texture for offscreen readback");
1440 return QImage();
1441 }
1442 QScopedPointer<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, 1));
1443 if (!depthStencil->create()) {
1444 qWarning("Failed to create depth/stencil buffer for offscreen readback");
1445 return QImage();
1446 }
1447 QRhiTextureRenderTargetDescription rtDesc(texture.data());
1448 rtDesc.setDepthStencilBuffer(depthStencil.data());
1449 QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
1450 QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
1451 rt->setRenderPassDescriptor(rpDesc.data());
1452 if (!rt->create()) {
1453 qWarning("Failed to build render target for offscreen readback");
1454 return QImage();
1455 }
1456
1457 // Backup the original Rhi
1458 QRhi *currentRhi = wd->rhi;
1459 wd->rhi = rhi;
1460
1461 QSGDefaultRenderContext::InitParams params;
1462 params.rhi = rhi;
1463 params.sampleCount = 1;
1464 params.initialSurfacePixelSize = pixelSize;
1465 params.maybeSurface = window;
1466 wd->context->initialize(&params);
1467
1468 // Backup the original RenderTarget
1469 QQuickRenderTarget currentRenderTarget = window->renderTarget();
1470 // There was no rendercontrol which means a custom render target
1471 // should not be set either. Set our own, temporarily.
1472 window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(rt.data()));
1473
1474 QRhiCommandBuffer *cb = nullptr;
1475 if (rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) {
1476 qWarning("Failed to start recording the frame for offscreen readback");
1477 return QImage();
1478 }
1479
1480 wd->setCustomCommandBuffer(cb);
1481 wd->polishItems();
1482 wd->syncSceneGraph();
1483 wd->renderSceneGraph();
1484 wd->setCustomCommandBuffer(nullptr);
1485
1486 QImage image = grabAndBlockInCurrentFrame(rhi, cb, texture.data());
1487 rhi->endOffscreenFrame();
1488
1489 image.setDevicePixelRatio(window->devicePixelRatio());
1490
1491 // Called from gui/main thread on no onscreen rendering initialized
1492 if (!currentRhi) {
1493 wd->cleanupNodesOnShutdown();
1494 wd->context->invalidate();
1495
1496 window->setRenderTarget(QQuickRenderTarget());
1497 wd->rhi = nullptr;
1498 } else {
1499 // Called from rendering thread for protected content
1500 // Restore to original Rhi, RenderTarget and Context
1501 window->setRenderTarget(currentRenderTarget);
1502 wd->rhi = currentRhi;
1503 params.rhi = currentRhi;
1504 wd->context->initialize(&params);
1505 }
1506
1507 return image;
1508}
1509#endif
1510
1511void QSGRhiSupport::applySwapChainFormat(QRhiSwapChain *scWithWindowSet, QQuickWindow *window)
1512{
1513 Q_ASSERT(scWithWindowSet->window() == window);
1514
1515 QRhiSwapChain::Format swapChainFormat = QRhiSwapChain::SDR;
1516
1517 QByteArray hdrRequest = qgetenv(varName: "QSG_RHI_HDR");
1518 if (!hdrRequest.isEmpty()) {
1519 hdrRequest = hdrRequest.toLower();
1520 if (hdrRequest == QByteArrayLiteral("scrgb") || hdrRequest == QByteArrayLiteral("extendedsrgblinear"))
1521 swapChainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
1522 else if (hdrRequest == QByteArrayLiteral("hdr10"))
1523 swapChainFormat = QRhiSwapChain::HDR10;
1524 }
1525
1526 const char *fmtStr = "unknown";
1527 switch (swapChainFormat) {
1528 case QRhiSwapChain::SDR:
1529 fmtStr = "SDR";
1530 break;
1531 case QRhiSwapChain::HDRExtendedSrgbLinear:
1532 fmtStr = "scRGB";
1533 break;
1534 case QRhiSwapChain::HDR10:
1535 fmtStr = "HDR10";
1536 break;
1537 default:
1538 break;
1539 }
1540
1541 if (!scWithWindowSet->isFormatSupported(f: swapChainFormat)) {
1542 if (swapChainFormat != QRhiSwapChain::SDR) {
1543 qCDebug(QSG_LOG_INFO, "Requested a %s swapchain but it is reported to be unsupported with the current display(s). "
1544 "In multi-screen configurations make sure the window is located on a HDR-enabled screen. "
1545 "Request ignored, using SDR swapchain.", fmtStr);
1546 }
1547 return;
1548 }
1549
1550 scWithWindowSet->setFormat(swapChainFormat);
1551
1552 if (swapChainFormat != QRhiSwapChain::SDR) {
1553 qCDebug(QSG_LOG_INFO, "Creating %s swapchain", fmtStr);
1554 qCDebug(QSG_LOG_INFO) << "HDR output info:" << scWithWindowSet->hdrInfo();
1555 }
1556}
1557
1558QRhiTexture::Format QSGRhiSupport::toRhiTextureFormat(uint nativeFormat, QRhiTexture::Flags *flags) const
1559{
1560 switch (m_rhiBackend) {
1561#if QT_CONFIG(vulkan)
1562 case QRhi::Vulkan:
1563 return toRhiTextureFormatFromVulkan(format: nativeFormat, flags);
1564#endif
1565#if QT_CONFIG(opengl)
1566 case QRhi::OpenGLES2:
1567 Q_UNUSED(flags);
1568 return toRhiTextureFormatFromGL(format: nativeFormat);
1569#endif
1570#ifdef Q_OS_WIN
1571 case QRhi::D3D11:
1572 case QRhi::D3D12:
1573 return toRhiTextureFormatFromDXGI(nativeFormat, flags);
1574#endif
1575#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
1576 case QRhi::Metal:
1577 return toRhiTextureFormatFromMetal(nativeFormat, flags);
1578#endif
1579 default:
1580 return QRhiTexture::UnknownFormat;
1581 }
1582}
1583
1584QT_END_NAMESPACE
1585

source code of qtdeclarative/src/quick/scenegraph/qsgrhisupport.cpp