1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgrhisupport_p.h"
41#include "qsgdefaultrendercontext_p.h"
42#include <QtGui/qwindow.h>
43
44#if QT_CONFIG(vulkan)
45#include <QtGui/qvulkaninstance.h>
46#endif
47
48QT_BEGIN_NAMESPACE
49
50#if QT_CONFIG(vulkan)
51QVulkanInstance *s_vulkanInstance = nullptr;
52#endif
53
54QVulkanInstance *QSGRhiSupport::vulkanInstance()
55{
56#if QT_CONFIG(vulkan)
57 QSGRhiSupport *inst = QSGRhiSupport::instance();
58 if (!inst->isRhiEnabled() || inst->rhiBackend() != QRhi::Vulkan)
59 return nullptr;
60
61 if (!s_vulkanInstance) {
62 s_vulkanInstance = new QVulkanInstance;
63 if (inst->isDebugLayerRequested()) {
64#ifndef Q_OS_ANDROID
65 s_vulkanInstance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
66#else
67 s_vulkanInstance->setLayers(QByteArrayList()
68 << "VK_LAYER_GOOGLE_threading"
69 << "VK_LAYER_LUNARG_parameter_validation"
70 << "VK_LAYER_LUNARG_object_tracker"
71 << "VK_LAYER_LUNARG_core_validation"
72 << "VK_LAYER_LUNARG_image"
73 << "VK_LAYER_LUNARG_swapchain"
74 << "VK_LAYER_GOOGLE_unique_objects");
75#endif
76 }
77 s_vulkanInstance->setExtensions(QByteArrayList()
78 << "VK_KHR_get_physical_device_properties2");
79 if (!s_vulkanInstance->create()) {
80 qWarning("Failed to create Vulkan instance");
81 delete s_vulkanInstance;
82 s_vulkanInstance = nullptr;
83 }
84 }
85 return s_vulkanInstance;
86#else
87 return nullptr;
88#endif
89}
90
91void QSGRhiSupport::cleanup()
92{
93#if QT_CONFIG(vulkan)
94 delete s_vulkanInstance;
95 s_vulkanInstance = nullptr;
96#endif
97}
98
99QSGRhiSupport::QSGRhiSupport()
100 : m_set(false),
101 m_enableRhi(false),
102 m_debugLayer(false),
103 m_profile(false),
104 m_shaderEffectDebug(false)
105{
106}
107
108void QSGRhiSupport::applySettings()
109{
110 m_set = true;
111
112 if (m_requested.valid) {
113 // explicit rhi backend request from C++ (e.g. via QQuickWindow)
114 m_enableRhi = m_requested.rhi;
115 switch (m_requested.api) {
116 case QSGRendererInterface::OpenGLRhi:
117 m_rhiBackend = QRhi::OpenGLES2;
118 break;
119 case QSGRendererInterface::Direct3D11Rhi:
120 m_rhiBackend = QRhi::D3D11;
121 break;
122 case QSGRendererInterface::VulkanRhi:
123 m_rhiBackend = QRhi::Vulkan;
124 break;
125 case QSGRendererInterface::MetalRhi:
126 m_rhiBackend = QRhi::Metal;
127 break;
128 case QSGRendererInterface::NullRhi:
129 m_rhiBackend = QRhi::Null;
130 break;
131 default:
132 Q_ASSERT_X(false, "QSGRhiSupport", "Internal error: unhandled GraphicsApi type");
133 break;
134 }
135 } else {
136 // check env.vars., fall back to platform-specific defaults when backend is not set
137 m_enableRhi = qEnvironmentVariableIntValue("QSG_RHI");
138 const QByteArray rhiBackend = qgetenv("QSG_RHI_BACKEND");
139 if (rhiBackend == QByteArrayLiteral("gl")
140 || rhiBackend == QByteArrayLiteral("gles2")
141 || rhiBackend == QByteArrayLiteral("opengl"))
142 {
143 m_rhiBackend = QRhi::OpenGLES2;
144 } else if (rhiBackend == QByteArrayLiteral("d3d11") || rhiBackend == QByteArrayLiteral("d3d")) {
145 m_rhiBackend = QRhi::D3D11;
146 } else if (rhiBackend == QByteArrayLiteral("vulkan")) {
147 m_rhiBackend = QRhi::Vulkan;
148 } else if (rhiBackend == QByteArrayLiteral("metal")) {
149 m_rhiBackend = QRhi::Metal;
150 } else if (rhiBackend == QByteArrayLiteral("null")) {
151 m_rhiBackend = QRhi::Null;
152 } else {
153#if defined(Q_OS_WIN)
154 m_rhiBackend = QRhi::D3D11;
155#elif defined(Q_OS_DARWIN)
156 m_rhiBackend = QRhi::Metal;
157#else
158 m_rhiBackend = QRhi::OpenGLES2;
159#endif
160 // Vulkan has to be requested explicitly
161 }
162 }
163
164 if (!m_enableRhi)
165 return;
166
167 // validation layers (Vulkan) or debug layer (D3D)
168 m_debugLayer = qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER");
169
170 // EnableProfiling + DebugMarkers
171 m_profile = qEnvironmentVariableIntValue("QSG_RHI_PROFILE");
172
173 m_shaderEffectDebug = qEnvironmentVariableIntValue("QSG_RHI_SHADEREFFECT_DEBUG");
174
175 const char *backendName = "unknown";
176 switch (m_rhiBackend) {
177 case QRhi::Null:
178 backendName = "Null";
179 break;
180 case QRhi::Vulkan:
181 backendName = "Vulkan";
182 break;
183 case QRhi::OpenGLES2:
184 backendName = "OpenGL";
185 break;
186 case QRhi::D3D11:
187 backendName = "D3D11";
188 break;
189 case QRhi::Metal:
190 backendName = "Metal";
191 break;
192 default:
193 break;
194 }
195 qDebug("Using QRhi with backend %s\n graphics API debug/validation layers: %d\n QRhi profiling and debug markers: %d",
196 backendName, m_debugLayer, m_profile);
197}
198
199QSGRhiSupport *QSGRhiSupport::staticInst()
200{
201 static QSGRhiSupport inst;
202 return &inst;
203}
204
205void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api)
206{
207 Q_ASSERT(QSGRendererInterface::isApiRhiBased(api));
208 QSGRhiSupport *inst = staticInst();
209 if (inst->m_set) {
210 qWarning("QRhi is already configured, request ignored");
211 return;
212 }
213 inst->m_requested.valid = true;
214 inst->m_requested.api = api;
215 inst->m_requested.rhi = true;
216 inst->applySettings();
217}
218
219QSGRhiSupport *QSGRhiSupport::instance()
220{
221 QSGRhiSupport *inst = staticInst();
222 if (!inst->m_set)
223 inst->applySettings();
224 return inst;
225}
226
227QSGRendererInterface::GraphicsApi QSGRhiSupport::graphicsApi() const
228{
229 if (!m_enableRhi)
230 return QSGRendererInterface::OpenGL;
231
232 switch (m_rhiBackend) {
233 case QRhi::Null:
234 return QSGRendererInterface::NullRhi;
235 case QRhi::Vulkan:
236 return QSGRendererInterface::VulkanRhi;
237 case QRhi::OpenGLES2:
238 return QSGRendererInterface::OpenGLRhi;
239 case QRhi::D3D11:
240 return QSGRendererInterface::Direct3D11Rhi;
241 case QRhi::Metal:
242 return QSGRendererInterface::MetalRhi;
243 default:
244 return QSGRendererInterface::Unknown;
245 }
246}
247
248QSurface::SurfaceType QSGRhiSupport::windowSurfaceType() const
249{
250 if (!m_enableRhi)
251 return QSurface::OpenGLSurface;
252
253 switch (m_rhiBackend) {
254 case QRhi::Vulkan:
255 return QSurface::VulkanSurface;
256 case QRhi::OpenGLES2:
257 return QSurface::OpenGLSurface;
258 case QRhi::D3D11:
259 return QSurface::OpenGLSurface; // yup, OpenGLSurface
260 case QRhi::Metal:
261 return QSurface::MetalSurface;
262 default:
263 return QSurface::OpenGLSurface;
264 }
265}
266
267#if QT_CONFIG(vulkan)
268static const void *qsgrhi_vk_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
269 const QRhiNativeHandles *cbNat)
270{
271 const QRhiVulkanNativeHandles *vknat = static_cast<const QRhiVulkanNativeHandles *>(nat);
272 const QRhiVulkanCommandBufferNativeHandles *maybeVkCbNat =
273 static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbNat);
274
275 switch (res) {
276 case QSGRendererInterface::DeviceResource:
277 return &vknat->dev;
278 case QSGRendererInterface::CommandQueueResource:
279 return &vknat->gfxQueue;
280 case QSGRendererInterface::CommandListResource:
281 if (maybeVkCbNat)
282 return &maybeVkCbNat->commandBuffer;
283 else
284 return nullptr;
285 case QSGRendererInterface::PhysicalDeviceResource:
286 return &vknat->physDev;
287 default:
288 return nullptr;
289 }
290}
291#endif
292
293#if QT_CONFIG(opengl)
294static const void *qsgrhi_gl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
295{
296 const QRhiGles2NativeHandles *glnat = static_cast<const QRhiGles2NativeHandles *>(nat);
297 switch (res) {
298 case QSGRendererInterface::OpenGLContextResource:
299 return glnat->context;
300 default:
301 return nullptr;
302 }
303}
304#endif
305
306#ifdef Q_OS_WIN
307static const void *qsgrhi_d3d11_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat)
308{
309 const QRhiD3D11NativeHandles *d3dnat = static_cast<const QRhiD3D11NativeHandles *>(nat);
310 switch (res) {
311 case QSGRendererInterface::DeviceResource:
312 return d3dnat->dev;
313 case QSGRendererInterface::DeviceContextResource:
314 return d3dnat->context;
315 default:
316 return nullptr;
317 }
318}
319#endif
320
321#ifdef Q_OS_DARWIN
322static const void *qsgrhi_mtl_rifResource(QSGRendererInterface::Resource res, const QRhiNativeHandles *nat,
323 const QRhiNativeHandles *cbNat)
324{
325 const QRhiMetalNativeHandles *mtlnat = static_cast<const QRhiMetalNativeHandles *>(nat);
326 const QRhiMetalCommandBufferNativeHandles *maybeMtlCbNat =
327 static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbNat);
328
329 switch (res) {
330 case QSGRendererInterface::DeviceResource:
331 return mtlnat->dev;
332 case QSGRendererInterface::CommandQueueResource:
333 return mtlnat->cmdQueue;
334 case QSGRendererInterface::CommandListResource:
335 if (maybeMtlCbNat)
336 return maybeMtlCbNat->commandBuffer;
337 else
338 return nullptr;
339 case QSGRendererInterface::CommandEncoderResource:
340 if (maybeMtlCbNat)
341 return maybeMtlCbNat->encoder;
342 else
343 return nullptr;
344 default:
345 return nullptr;
346 }
347}
348#endif
349
350const void *QSGRhiSupport::rifResource(QSGRendererInterface::Resource res, const QSGDefaultRenderContext *rc)
351{
352 QRhi *rhi = rc->rhi();
353 if (res == QSGRendererInterface::RhiResource || !rhi)
354 return rhi;
355
356 const QRhiNativeHandles *nat = rhi->nativeHandles();
357 if (!nat)
358 return nullptr;
359
360 switch (m_rhiBackend) {
361#if QT_CONFIG(vulkan)
362 case QRhi::Vulkan:
363 {
364 QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
365 return qsgrhi_vk_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
366 }
367#endif
368#if QT_CONFIG(opengl)
369 case QRhi::OpenGLES2:
370 return qsgrhi_gl_rifResource(res, nat);
371#endif
372#ifdef Q_OS_WIN
373 case QRhi::D3D11:
374 return qsgrhi_d3d11_rifResource(res, nat);
375#endif
376#ifdef Q_OS_DARWIN
377 case QRhi::Metal:
378 {
379 QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer();
380 return qsgrhi_mtl_rifResource(res, nat, cb ? cb->nativeHandles() : nullptr);
381 }
382#endif
383 default:
384 return nullptr;
385 }
386}
387
388int QSGRhiSupport::chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi)
389{
390 int msaaSampleCount = qMax(QSurfaceFormat::defaultFormat().samples(), window->requestedFormat().samples());
391 if (qEnvironmentVariableIsSet("QSG_SAMPLES"))
392 msaaSampleCount = qEnvironmentVariableIntValue("QSG_SAMPLES");
393 msaaSampleCount = qMax(1, msaaSampleCount);
394 if (msaaSampleCount > 1) {
395 const QVector<int> supportedSampleCounts = rhi->supportedSampleCounts();
396 if (!supportedSampleCounts.contains(msaaSampleCount)) {
397 int reducedSampleCount = 1;
398 for (int i = supportedSampleCounts.count() - 1; i >= 0; --i) {
399 if (supportedSampleCounts[i] <= msaaSampleCount) {
400 reducedSampleCount = supportedSampleCounts[i];
401 break;
402 }
403 }
404 qWarning() << "Requested MSAA sample count" << msaaSampleCount
405 << "but supported sample counts are" << supportedSampleCounts
406 << ", using sample count" << reducedSampleCount << "instead";
407 msaaSampleCount = reducedSampleCount;
408 }
409 }
410 return msaaSampleCount;
411}
412
413// must be called on the main thread
414QOffscreenSurface *QSGRhiSupport::maybeCreateOffscreenSurface(QWindow *window)
415{
416 QOffscreenSurface *offscreenSurface = nullptr;
417#if QT_CONFIG(opengl)
418 if (rhiBackend() == QRhi::OpenGLES2) {
419 const QSurfaceFormat format = window->requestedFormat();
420 offscreenSurface = QRhiGles2InitParams::newFallbackSurface(format);
421 }
422#else
423 Q_UNUSED(window);
424#endif
425 return offscreenSurface;
426}
427
428// must be called on the render thread
429QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurface)
430{
431 QRhi *rhi = nullptr;
432
433 QRhi::Flags flags = 0;
434 if (isProfilingRequested())
435 flags |= QRhi::EnableProfiling | QRhi::EnableDebugMarkers;
436
437 QRhi::Implementation backend = rhiBackend();
438 if (backend == QRhi::Null) {
439 QRhiNullInitParams rhiParams;
440 rhi = QRhi::create(backend, &rhiParams, flags);
441 }
442#if QT_CONFIG(opengl)
443 if (backend == QRhi::OpenGLES2) {
444 const QSurfaceFormat format = window->requestedFormat();
445 QRhiGles2InitParams rhiParams;
446 rhiParams.format = format;
447 rhiParams.fallbackSurface = offscreenSurface;
448 rhiParams.window = window;
449 rhi = QRhi::create(backend, &rhiParams, flags);
450 }
451#endif
452#if QT_CONFIG(vulkan)
453 if (backend == QRhi::Vulkan) {
454 QRhiVulkanInitParams rhiParams;
455 rhiParams.inst = window->vulkanInstance();
456 if (!rhiParams.inst)
457 qWarning("No QVulkanInstance set for QQuickWindow, this is wrong.");
458 rhiParams.window = window;
459 rhi = QRhi::create(backend, &rhiParams, flags);
460 }
461#endif
462#ifdef Q_OS_WIN
463 if (backend == QRhi::D3D11) {
464 QRhiD3D11InitParams rhiParams;
465 rhiParams.enableDebugLayer = isDebugLayerRequested();
466 rhi = QRhi::create(backend, &rhiParams, flags);
467 }
468#endif
469#ifdef Q_OS_DARWIN
470 if (backend == QRhi::Metal) {
471 QRhiMetalInitParams rhiParams;
472 rhi = QRhi::create(backend, &rhiParams, flags);
473 }
474#endif
475
476 if (!rhi)
477 qWarning("Failed to create RHI (backend %d)", backend);
478
479 return rhi;
480}
481
482QImage QSGRhiSupport::grabAndBlockInCurrentFrame(QRhi *rhi, QRhiSwapChain *swapchain)
483{
484 Q_ASSERT(rhi->isRecordingFrame());
485
486 QRhiReadbackResult result;
487 QRhiReadbackDescription readbackDesc; // read from swapchain backbuffer
488 QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
489 resourceUpdates->readBackTexture(readbackDesc, &result);
490
491 swapchain->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
492 rhi->finish(); // make sure the readback has finished, stall the pipeline if needed
493
494 // May be RGBA or BGRA. Plus premultiplied alpha.
495 QImage::Format imageFormat;
496 if (result.format == QRhiTexture::BGRA8) {
497#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
498 imageFormat = QImage::Format_ARGB32_Premultiplied;
499#else
500 imageFormat = QImage::Format_RGBA8888_Premultiplied;
501 // ### and should swap too
502#endif
503 } else {
504 imageFormat = QImage::Format_RGBA8888_Premultiplied;
505 }
506
507 const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
508 const QImage img(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat);
509
510 if (rhi->isYUpInFramebuffer())
511 return img.mirrored();
512
513 return img.copy();
514}
515
516QSGRhiProfileConnection *QSGRhiProfileConnection::instance()
517{
518 static QSGRhiProfileConnection inst;
519 return &inst;
520}
521
522void QSGRhiProfileConnection::initialize(QRhi *rhi)
523{
524#ifdef RHI_REMOTE_PROFILER
525 const QString profHost = qEnvironmentVariable("QSG_RHI_PROFILE_HOST");
526 if (!profHost.isEmpty()) {
527 int profPort = qEnvironmentVariableIntValue("QSG_RHI_PROFILE_PORT");
528 if (!profPort)
529 profPort = 30667;
530 qDebug("Sending RHI profiling output to %s:%d", qPrintable(profHost), profPort);
531 m_profConn.reset(new QTcpSocket);
532 QObject::connect(m_profConn.data(), QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), m_profConn.data(),
533 [this](QAbstractSocket::SocketError socketError) { qDebug(" RHI profiler error: %d (%s)",
534 socketError, qPrintable(m_profConn->errorString())); });
535 m_profConn->connectToHost(profHost, profPort);
536 m_profConn->waitForConnected(); // blocking wait because we want to send stuff already from the init below
537 rhi->profiler()->setDevice(m_profConn.data());
538 m_lastMemStatWrite.start();
539 }
540#else
541 Q_UNUSED(rhi);
542#endif
543}
544
545void QSGRhiProfileConnection::cleanup()
546{
547#ifdef RHI_REMOTE_PROFILER
548 m_profConn.reset();
549#endif
550}
551
552void QSGRhiProfileConnection::send(QRhi *rhi)
553{
554#ifdef RHI_REMOTE_PROFILER
555 if (m_profConn) {
556 // do this every 5 sec at most
557 if (m_lastMemStatWrite.elapsed() >= 5000) {
558 rhi->profiler()->addVMemAllocatorStats();
559 m_lastMemStatWrite.restart();
560 }
561 }
562#else
563 Q_UNUSED(rhi);
564#endif
565}
566
567QT_END_NAMESPACE
568