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
qopenxrgraphics_vulkan.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
9#include <QtQuick/QQuickWindow>
10#include <QtQuick/QQuickGraphicsDevice>
11#include <QtQuick/QQuickGraphicsConfiguration>
12#include <QtQuick/private/qquickrendertarget_p.h>
13
14#include <rhi/qrhi.h>
15
16//#define XR_USE_GRAPHICS_API_VULKAN
17
19
20QOpenXRGraphicsVulkan::QOpenXRGraphicsVulkan()
21{
22 m_graphicsBinding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
23}
24
25bool QOpenXRGraphicsVulkan::initialize(const QVector<XrExtensionProperties> &extensions)
26{
27 return hasExtension(extensions, XR_KHR_VULKAN_ENABLE_EXTENSION_NAME);
28}
29
31{
32 return { XR_KHR_VULKAN_ENABLE_EXTENSION_NAME };
33}
34
35
37{
38 return reinterpret_cast<const XrBaseInStructure*>(&m_graphicsBinding);
39}
40
41
42bool QOpenXRGraphicsVulkan::setupGraphics(const XrInstance &instance, XrSystemId &systemId, const QQuickGraphicsConfiguration &quickConfig)
43{
44 // Setup Vulkan Instance.
45
46 // In hybrid applications that also show Qt Quick windows on the desktop, it
47 // is not ideal to create multiple VkInstances (as the on-screen
48 // QQuickWindow(s) will have another one), but there is nothing we can do
49 // due to the forced upfront nature of Vulkan API design. And we need to do
50 // OpenXR API calls to get the things we need to create the instance. This
51 // is hard to reconcile with Quick, that knows nothing about XrView and
52 // such, and cannot predict the future either (i.e., "guess" if the user is
53 // ever going to instantiate an XRView, and so on).
54 //
55 // This has no relevance for XR-only apps, and even the hybrid case this
56 // works in practice, so we might just live with this for now.
57
58 PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = nullptr;
59 OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
60 "xrGetVulkanGraphicsRequirementsKHR",
61 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsRequirementsKHR)),
62 instance);
63
64 if (!pfnGetVulkanGraphicsRequirementsKHR) {
65 qWarning("Could not resolve xrGetVulkanGraphicsRequirementsKHR; perhaps the OpenXR implementation does not support Vulkan?");
66 return false;
67 }
68
69 PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr;
70 OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
71 "xrGetVulkanInstanceExtensionsKHR",
72 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanInstanceExtensionsKHR)),
73 instance);
74
75 XrGraphicsRequirementsVulkanKHR graphicsRequirements{};
76 graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR;
77 OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsRequirementsKHR(instance,
78 systemId,
79 &graphicsRequirements),
80 instance);
81
82 quint32 extensionNamesSize = 0;
83 OpenXRHelpers::checkXrResult(pfnGetVulkanInstanceExtensionsKHR(instance,
84 systemId,
85 0,
86 &extensionNamesSize,
87 nullptr),
88 instance);
89
90 QByteArray extensionNames;
91 extensionNames.resize(extensionNamesSize);
92 OpenXRHelpers::checkXrResult(pfnGetVulkanInstanceExtensionsKHR(instance,
93 systemId,
94 extensionNamesSize,
95 &extensionNamesSize,
96 extensionNames.data()),
97 instance);
98
99 // The last extension could have extra null characters but
100 // the way we handle extenions doesn't handle null terminated
101 // strings well, so we have to strip them ourselves
102 auto stripNullChars = [](const QByteArray &string) {
103 auto begin = string.begin();
104 auto end = string.end();
105 while (begin < end && end[-1] == '\x00')
106 --end;
107 return QByteArray(begin, end - begin);
108 };
109
110 QByteArrayList extensions = extensionNames.split(' ');
111 for (auto &ext : extensions)
112 ext = stripNullChars(ext);
113
114 for (auto &rhiExt : QRhiVulkanInitParams::preferredInstanceExtensions()) {
115 if (!extensions.contains(rhiExt))
116 extensions.append(rhiExt);
117 }
118
119 m_vulkanInstance.setExtensions(extensions);
120
121 // Multiview is a Vulkan 1.1 feature and won't work without setting up the instance accordingly.
122 const QVersionNumber supportedVersion = m_vulkanInstance.supportedApiVersion();
123 if (supportedVersion >= QVersionNumber(1, 3))
124 m_vulkanInstance.setApiVersion(QVersionNumber(1, 3));
125 else if (supportedVersion >= QVersionNumber(1, 2))
126 m_vulkanInstance.setApiVersion(QVersionNumber(1, 2));
127 else if (supportedVersion >= QVersionNumber(1, 1))
128 m_vulkanInstance.setApiVersion(QVersionNumber(1, 1));
129
130 if (quickConfig.isDebugLayerEnabled())
131 m_vulkanInstance.setLayers({ "VK_LAYER_LUNARG_standard_validation" });
132
133 if (!m_vulkanInstance.create()) {
134 qWarning("Quick 3D XR: Failed to create Vulkan instance");
135 return false;
136 }
137
138 // Get Vulkan device extensions
139 PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr;
140 OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
141 "xrGetVulkanDeviceExtensionsKHR",
142 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanDeviceExtensionsKHR)),
143 instance);
144
145 uint32_t deviceExtensionNamesSize = 0;
146 OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
147 systemId,
148 0,
149 &deviceExtensionNamesSize,
150 nullptr),
151 instance);
152 QByteArray deviceExtensionNames;
153 deviceExtensionNames.resize(deviceExtensionNamesSize);
154 OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
155 systemId,
156 deviceExtensionNamesSize,
157 &deviceExtensionNamesSize,
158 deviceExtensionNames.data()),
159 instance);
160
161 auto deviceExtensions = deviceExtensionNames.split(' ');
162 for (auto &ext : deviceExtensions) {
163 ext = stripNullChars(ext);
164 }
165 m_graphicsConfiguration.setDeviceExtensions(deviceExtensions);
166
167 // Get the Vulkan Graphics Device
168 PFN_xrGetVulkanGraphicsDeviceKHR pfnGetVulkanGraphicsDeviceKHR = nullptr;
169 OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDeviceKHR",
170 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsDeviceKHR)), instance);
171
172 OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsDeviceKHR(instance, systemId, m_vulkanInstance.vkInstance(), &m_vulkanPhysicalDevice), instance);
173
174 return true;
175}
176
177bool QOpenXRGraphicsVulkan::finializeGraphics(QRhi *rhi)
178{
179 const QRhiVulkanNativeHandles *vulkanRhi = static_cast<const QRhiVulkanNativeHandles *>(rhi->nativeHandles());
180 m_vulkanDevice = vulkanRhi->dev;
181 Q_ASSERT(m_vulkanPhysicalDevice == vulkanRhi->physDev);
182 m_vulkanCommandQueue = vulkanRhi->gfxQueue;
183 m_queueFamilyIndex = vulkanRhi->gfxQueueFamilyIdx;
184
185 m_graphicsBinding.instance = m_vulkanInstance.vkInstance();
186 m_graphicsBinding.physicalDevice = m_vulkanPhysicalDevice;
187 m_graphicsBinding.device = m_vulkanDevice;
188 m_graphicsBinding.queueFamilyIndex = m_queueFamilyIndex;
189 m_graphicsBinding.queueIndex = 0;
190
191 m_rhi = rhi;
192
193 return true;
194}
195
196
197int64_t QOpenXRGraphicsVulkan::colorSwapchainFormat(const QVector<int64_t> &swapchainFormats) const
198{
199 // List of supported color swapchain formats.
200 constexpr int64_t supportedColorSwapchainFormats[] = {
201 VK_FORMAT_B8G8R8A8_SRGB,
202 VK_FORMAT_R8G8B8A8_SRGB,
203 VK_FORMAT_B8G8R8A8_UNORM,
204 VK_FORMAT_R8G8B8A8_UNORM
205 };
206
207 auto swapchainFormatIt = std::find_first_of(std::begin(supportedColorSwapchainFormats),
208 std::end(supportedColorSwapchainFormats),
209 swapchainFormats.begin(),
210 swapchainFormats.end());
211 return *swapchainFormatIt;
212}
213
214int64_t QOpenXRGraphicsVulkan::depthSwapchainFormat(const QVector<int64_t> &swapchainFormats) const
215{
216 // in order of preference
217 QVarLengthArray<int64_t, 4> supportedDepthSwapchainFormats;
218 constexpr std::pair<int64_t, QRhiTexture::Format> nativeDepthSwapchainFormats[] = {
219 { VK_FORMAT_D24_UNORM_S8_UINT, QRhiTexture::D24S8 },
220 { VK_FORMAT_D32_SFLOAT_S8_UINT, QRhiTexture::D32F },
221 { VK_FORMAT_D32_SFLOAT, QRhiTexture::D32F },
222 { VK_FORMAT_D16_UNORM, QRhiTexture::D16 }
223 };
224 for (const auto &format : nativeDepthSwapchainFormats) {
225 if (m_rhi->isTextureFormatSupported(format.second))
226 supportedDepthSwapchainFormats.append(format.first);
227 }
228
229 if (supportedDepthSwapchainFormats.isEmpty())
230 return 0;
231
232 // order matters, we prefer D24S8 above all the others
233 return *std::find_first_of(std::begin(supportedDepthSwapchainFormats),
234 std::end(supportedDepthSwapchainFormats),
235 swapchainFormats.begin(),
236 swapchainFormats.end());
237}
238
240{
241 QVector<XrSwapchainImageBaseHeader*> swapchainImages;
242 QVector<XrSwapchainImageVulkanKHR> swapchainImageBuffer(count);
243 for (XrSwapchainImageVulkanKHR& image : swapchainImageBuffer) {
244 image.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR;
245 swapchainImages.push_back(reinterpret_cast<XrSwapchainImageBaseHeader*>(&image));
246 }
247 m_swapchainImageBuffer.insert(swapchain, swapchainImageBuffer);
248 return swapchainImages;
249}
250
251
252QQuickRenderTarget QOpenXRGraphicsVulkan::renderTarget(const XrSwapchainSubImage &subImage,
253 const XrSwapchainImageBaseHeader *swapchainImage,
254 quint64 swapchainFormat,
255 int samples,
256 int arraySize,
257 const XrSwapchainImageBaseHeader *depthSwapchainImage,
258 quint64 depthSwapchainFormat) const
259{
260 VkImage colorTexture = reinterpret_cast<const XrSwapchainImageVulkanKHR*>(swapchainImage)->image;
261
262 VkFormat viewFormat = VkFormat(swapchainFormat);
263 switch (swapchainFormat) {
264 case VK_FORMAT_R8G8B8A8_SRGB:
265 viewFormat = VK_FORMAT_R8G8B8A8_UNORM;
266 break;
267 case VK_FORMAT_B8G8R8A8_SRGB:
268 viewFormat = VK_FORMAT_B8G8R8A8_UNORM;
269 break;
270 default:
271 break;
272 }
273
274 QQuickRenderTarget::Flags flags;
275 if (samples > 1)
276 flags |= QQuickRenderTarget::Flag::MultisampleResolve;
277
278 const QSize pixelSize(subImage.imageRect.extent.width, subImage.imageRect.extent.height);
279 QQuickRenderTarget rt = QQuickRenderTarget::fromVulkanImage(colorTexture,
280 VK_IMAGE_LAYOUT_UNDEFINED,
281 VkFormat(swapchainFormat),
282 viewFormat,
283 pixelSize,
284 samples,
285 arraySize,
286 flags);
287 if (depthSwapchainImage) {
288 // There might be issues with stencil when MSAA is not used and the
289 // format is D16 or D32F or the half-unsupported D32FS8 (because then
290 // the OpenXR-provided texture is the one and only depth-stencil buffer,
291 // perhaps without stencil). But we prefer D24S8 whenever that's
292 // available, so hopefully this problem won't come up in practice.
293 QRhiTexture::Format format = QRhiTexture::D24S8;
294 switch (depthSwapchainFormat) {
295 case VK_FORMAT_D32_SFLOAT_S8_UINT:
296 case VK_FORMAT_D32_SFLOAT:
297 format = QRhiTexture::D32F;
298 break;
299 case VK_FORMAT_D16_UNORM:
300 format = QRhiTexture::D16;
301 break;
302 }
303 VkImage depthImage = reinterpret_cast<const XrSwapchainImageVulkanKHR*>(depthSwapchainImage)->image;
304 if (m_depthTexture && (m_depthTexture->format() != format || m_depthTexture->pixelSize() != pixelSize || m_depthTexture->arraySize() != arraySize)) {
305 delete m_depthTexture;
306 m_depthTexture = nullptr;
307 }
308 if (!m_depthTexture) {
309 // this is never multisample, QQuickRt takes care of resolving depth-stencil
310 if (arraySize > 1)
311 m_depthTexture = m_rhi->newTextureArray(format, arraySize, pixelSize, 1, QRhiTexture::RenderTarget);
312 else
313 m_depthTexture = m_rhi->newTexture(format, pixelSize, 1, QRhiTexture::RenderTarget);
314 }
315 m_depthTexture->createFrom({ quint64(depthImage), VK_IMAGE_LAYOUT_UNDEFINED });
316 rt.setDepthTexture(m_depthTexture);
317 }
318 return rt;
319}
320
321void QOpenXRGraphicsVulkan::setupWindow(QQuickWindow *quickWindow)
322{
323 quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromPhysicalDevice(m_vulkanPhysicalDevice));
324 quickWindow->setGraphicsConfiguration(m_graphicsConfiguration);
325 quickWindow->setVulkanInstance(&m_vulkanInstance);
326}
327
329{
330 delete m_depthTexture;
331 m_depthTexture = nullptr;
332}
333
334QT_END_NAMESPACE
int64_t colorSwapchainFormat(const QVector< int64_t > &swapchainFormats) const override
QVector< const char * > getRequiredExtensions() const override
void setupWindow(QQuickWindow *quickWindow) override
QQuickRenderTarget renderTarget(const XrSwapchainSubImage &subImage, const XrSwapchainImageBaseHeader *swapchainImage, quint64 swapchainFormat, int samples, int arraySize, const XrSwapchainImageBaseHeader *depthSwapchainImage, quint64 depthSwapchainFormat) const override
int64_t depthSwapchainFormat(const QVector< int64_t > &swapchainFormats) const override
bool initialize(const QVector< XrExtensionProperties > &extensions) override
const XrBaseInStructure * handle() const override
QVector< XrSwapchainImageBaseHeader * > allocateSwapchainImages(int count, XrSwapchain swapchain) override
bool setupGraphics(const XrInstance &instance, XrSystemId &systemId, const QQuickGraphicsConfiguration &quickConfig) override
Combined button and popup list for selecting options.