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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | Q_DECLARE_LOGGING_CATEGORY(lcQpaBackingStore) |
20 | |
21 | QBackingStoreRhiSupport::~QBackingStoreRhiSupport() |
22 | { |
23 | reset(); |
24 | } |
25 | |
26 | void QBackingStoreRhiSupport::SwapchainData::reset() |
27 | { |
28 | delete swapchain; |
29 | delete renderPassDescriptor; |
30 | delete windowWatcher; |
31 | *this = {}; |
32 | } |
33 | |
34 | void 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 | |
48 | bool 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: ¶ms, 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: ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms)) { |
106 | rhi = QRhi::create(QRhi::Metal, ¶ms, flags); |
107 | } else { |
108 | qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL." ); |
109 | rhi = QRhi::create(QRhi::OpenGLES2, ¶ms, 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: ¶ms, 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 | |
146 | QRhiSwapChain *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 | |
188 | bool 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 | |
205 | QSurface::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 | |
228 | QRhi::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 | |
249 | bool 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 | |
317 | QT_END_NAMESPACE |
318 | |