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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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