1// Copyright (C) 2021 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 "qbackingstorerhisupport_p.h"
5#include <qpa/qplatformintegration.h>
6#include <private/qguiapplication_p.h>
7
8#if QT_CONFIG(opengl)
9#include <QtGui/qoffscreensurface.h>
10#include <QtGui/private/qopenglcontext_p.h>
11#endif
12
13#if QT_CONFIG(vulkan)
14#include <QtGui/private/qvulkandefaultinstance_p.h>
15#endif
16
17QT_BEGIN_NAMESPACE
18
19Q_DECLARE_LOGGING_CATEGORY(lcQpaBackingStore)
20
21QBackingStoreRhiSupport::~QBackingStoreRhiSupport()
22{
23 reset();
24}
25
26void QBackingStoreRhiSupport::SwapchainData::reset()
27{
28 delete swapchain;
29 delete renderPassDescriptor;
30 delete windowWatcher;
31 *this = {};
32}
33
34void QBackingStoreRhiSupport::reset()
35{
36 for (SwapchainData &d : m_swapchains)
37 d.reset();
38
39 m_swapchains.clear();
40
41 delete m_rhi;
42 m_rhi = nullptr;
43
44 delete m_openGLFallbackSurface;
45 m_openGLFallbackSurface = nullptr;
46}
47
48bool QBackingStoreRhiSupport::create()
49{
50 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::RhiBasedRendering))
51 return false;
52
53 // note: m_window may be null (special case for fully offscreen rendering)
54
55 QRhi *rhi = nullptr;
56 QOffscreenSurface *surface = nullptr;
57 QRhi::Flags flags;
58
59 // This must be the same env.var. Qt Quick uses, to ensure symmetry in the
60 // behavior between a QQuickWindow and a (QRhi-based) widget top-level window.
61 if (qEnvironmentVariableIntValue(varName: "QSG_RHI_PREFER_SOFTWARE_RENDERER"))
62 flags |= QRhi::PreferSoftwareRenderer;
63
64 if (m_config.api() == QPlatformBackingStoreRhiConfig::Null) {
65 QRhiNullInitParams params;
66 rhi = QRhi::create(impl: QRhi::Null, params: &params, flags);
67 }
68
69#if QT_CONFIG(opengl)
70 if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::OpenGL) {
71 surface = QRhiGles2InitParams::newFallbackSurface(format: m_format);
72 QRhiGles2InitParams params;
73 params.fallbackSurface = surface;
74 params.window = m_window;
75 params.format = m_format;
76 params.shareContext = qt_gl_global_share_context();
77 rhi = QRhi::create(impl: QRhi::OpenGLES2, params: &params, flags);
78 }
79#endif
80
81#ifdef Q_OS_WIN
82 if (!rhi) {
83 if (m_config.api() == QPlatformBackingStoreRhiConfig::D3D11) {
84 QRhiD3D11InitParams params;
85 params.enableDebugLayer = m_config.isDebugLayerEnabled();
86 rhi = QRhi::create(QRhi::D3D11, &params, flags);
87 if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
88 qCDebug(lcQpaBackingStore, "Failed to create a D3D device with default settings; "
89 "attempting to get a software rasterizer backed device instead");
90 flags |= QRhi::PreferSoftwareRenderer;
91 rhi = QRhi::create(QRhi::D3D11, &params, flags);
92 }
93 } else if (m_config.api() == QPlatformBackingStoreRhiConfig::D3D12) {
94 QRhiD3D12InitParams params;
95 params.enableDebugLayer = m_config.isDebugLayerEnabled();
96 rhi = QRhi::create(QRhi::D3D12, &params, flags);
97 }
98 }
99#endif
100
101#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
102 if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::Metal) {
103 QRhiMetalInitParams params;
104 // For parity with Qt Quick, fall back to OpenGL when there is no Metal (f.ex. in macOS virtual machines).
105 if (QRhi::probe(QRhi::Metal, &params)) {
106 rhi = QRhi::create(QRhi::Metal, &params, flags);
107 } else {
108 qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL.");
109 rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
110 }
111 }
112#endif
113
114#if QT_CONFIG(vulkan)
115 if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::Vulkan) {
116 if (m_config.isDebugLayerEnabled())
117 QVulkanDefaultInstance::setFlag(flag: QVulkanDefaultInstance::EnableValidation);
118 QRhiVulkanInitParams params;
119 if (m_window) {
120 if (!m_window->vulkanInstance())
121 m_window->setVulkanInstance(QVulkanDefaultInstance::instance());
122 params.inst = m_window->vulkanInstance();
123 } else {
124 params.inst = QVulkanDefaultInstance::instance();
125 }
126 if (!params.inst) {
127 qWarning(msg: "No QVulkanInstance set for the top-level window, this is wrong.");
128 return false;
129 }
130 params.window = m_window;
131 rhi = QRhi::create(impl: QRhi::Vulkan, params: &params, flags);
132 }
133#endif
134
135 if (!rhi) {
136 qWarning(msg: "Failed to create QRhi for QBackingStoreRhiSupport");
137 delete surface;
138 return false;
139 }
140
141 m_rhi = rhi;
142 m_openGLFallbackSurface = surface;
143 return true;
144}
145
146QRhiSwapChain *QBackingStoreRhiSupport::swapChainForWindow(QWindow *window)
147{
148 auto it = m_swapchains.constFind(key: window);
149 if (it != m_swapchains.constEnd())
150 return it.value().swapchain;
151
152 QRhiSwapChain *swapchain = nullptr;
153 QRhiRenderPassDescriptor *rp = nullptr;
154 if (window && m_rhi) {
155 QRhiSwapChain::Flags flags;
156 const QSurfaceFormat format = window->requestedFormat();
157 if (format.swapInterval() == 0)
158 flags |= QRhiSwapChain::NoVSync;
159 if (format.alphaBufferSize() > 0)
160 flags |= QRhiSwapChain::SurfaceHasNonPreMulAlpha;
161#if QT_CONFIG(vulkan)
162 if (m_config.api() == QPlatformBackingStoreRhiConfig::Vulkan && !window->vulkanInstance())
163 window->setVulkanInstance(QVulkanDefaultInstance::instance());
164#endif
165 qCDebug(lcQpaBackingStore) << "Creating swapchain for window" << window;
166 swapchain = m_rhi->newSwapChain();
167 swapchain->setWindow(window);
168 swapchain->setFlags(flags);
169 rp = swapchain->newCompatibleRenderPassDescriptor();
170 swapchain->setRenderPassDescriptor(rp);
171 if (!swapchain->createOrResize()) {
172 qWarning(msg: "Failed to create swapchain for window flushed with an RHI-enabled backingstore");
173 delete rp;
174 return nullptr;
175 }
176 }
177 if (swapchain) {
178 SwapchainData d;
179 d.swapchain = swapchain;
180 d.renderPassDescriptor = rp;
181 d.windowWatcher = new QBackingStoreRhiSupportWindowWatcher(this);
182 m_swapchains.insert(key: window, value: d);
183 window->installEventFilter(filterObj: d.windowWatcher);
184 }
185 return swapchain;
186}
187
188bool QBackingStoreRhiSupportWindowWatcher::eventFilter(QObject *obj, QEvent *event)
189{
190 if (event->type() == QEvent::PlatformSurface
191 && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
192 {
193 QWindow *window = qobject_cast<QWindow *>(o: obj);
194 auto it = m_rhiSupport->m_swapchains.find(key: window);
195 if (it != m_rhiSupport->m_swapchains.end()) {
196 qCDebug(lcQpaBackingStore) << "SurfaceAboutToBeDestroyed received for tracked window" << window << "cleaning up swapchain";
197 auto data = *it;
198 m_rhiSupport->m_swapchains.erase(it);
199 data.reset(); // deletes 'this'
200 }
201 }
202 return false;
203}
204
205QSurface::SurfaceType QBackingStoreRhiSupport::surfaceTypeForConfig(const QPlatformBackingStoreRhiConfig &config)
206{
207 QSurface::SurfaceType type = QSurface::RasterSurface;
208 switch (config.api()) {
209 case QPlatformBackingStoreRhiConfig::D3D11:
210 case QPlatformBackingStoreRhiConfig::D3D12:
211 type = QSurface::Direct3DSurface;
212 break;
213 case QPlatformBackingStoreRhiConfig::Vulkan:
214 type = QSurface::VulkanSurface;
215 break;
216 case QPlatformBackingStoreRhiConfig::Metal:
217 type = QSurface::MetalSurface;
218 break;
219 case QPlatformBackingStoreRhiConfig::OpenGL:
220 type = QSurface::OpenGLSurface;
221 break;
222 default:
223 break;
224 }
225 return type;
226}
227
228QRhi::Implementation QBackingStoreRhiSupport::apiToRhiBackend(QPlatformBackingStoreRhiConfig::Api api)
229{
230 switch (api) {
231 case QPlatformBackingStoreRhiConfig::OpenGL:
232 return QRhi::OpenGLES2;
233 case QPlatformBackingStoreRhiConfig::Metal:
234 return QRhi::Metal;
235 case QPlatformBackingStoreRhiConfig::Vulkan:
236 return QRhi::Vulkan;
237 case QPlatformBackingStoreRhiConfig::D3D11:
238 return QRhi::D3D11;
239 case QPlatformBackingStoreRhiConfig::D3D12:
240 return QRhi::D3D12;
241 case QPlatformBackingStoreRhiConfig::Null:
242 return QRhi::Null;
243 default:
244 break;
245 }
246 return QRhi::Null;
247}
248
249bool QBackingStoreRhiSupport::checkForceRhi(QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType)
250{
251 static QPlatformBackingStoreRhiConfig config;
252 static bool checked = false;
253
254 if (!checked) {
255 checked = true;
256
257 const bool alwaysRhi = qEnvironmentVariableIntValue(varName: "QT_WIDGETS_RHI");
258 if (alwaysRhi)
259 config.setEnabled(true);
260
261 // if enabled, choose an api
262 if (config.isEnabled()) {
263#if defined(Q_OS_WIN)
264 config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
265#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
266 config.setApi(QPlatformBackingStoreRhiConfig::Metal);
267#elif QT_CONFIG(opengl)
268 config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
269#elif QT_CONFIG(vulkan)
270 config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
271#else
272 qWarning("QT_WIDGETS_RHI is set but no backend is available; ignoring");
273 return false;
274#endif
275
276 // the env.var. will always override
277 if (qEnvironmentVariableIsSet(varName: "QT_WIDGETS_RHI_BACKEND")) {
278 const QString backend = qEnvironmentVariable(varName: "QT_WIDGETS_RHI_BACKEND");
279#ifdef Q_OS_WIN
280 if (backend == QStringLiteral("d3d11") || backend == QStringLiteral("d3d"))
281 config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
282 if (backend == QStringLiteral("d3d12"))
283 config.setApi(QPlatformBackingStoreRhiConfig::D3D12);
284#endif
285#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
286 if (backend == QStringLiteral("metal"))
287 config.setApi(QPlatformBackingStoreRhiConfig::Metal);
288#endif
289#if QT_CONFIG(opengl)
290 if (backend == QStringLiteral("opengl") || backend == QStringLiteral("gl"))
291 config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
292#endif
293#if QT_CONFIG(vulkan)
294 if (backend == QStringLiteral("vulkan"))
295 config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
296#endif
297 }
298
299 if (qEnvironmentVariableIntValue(varName: "QT_WIDGETS_RHI_DEBUG_LAYER"))
300 config.setDebugLayer(true);
301 }
302
303 qCDebug(lcQpaBackingStore) << "Check for forced use of QRhi resulted in enable"
304 << config.isEnabled() << "with api" << QRhi::backendName(impl: apiToRhiBackend(api: config.api()));
305 }
306
307 if (config.isEnabled()) {
308 if (outConfig)
309 *outConfig = config;
310 if (outType)
311 *outType = surfaceTypeForConfig(config);
312 return true;
313 }
314 return false;
315}
316
317QT_END_NAMESPACE
318

source code of qtbase/src/gui/painting/qbackingstorerhisupport.cpp