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