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