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

source code of qtquick3d/src/xr/quick3dxr/openxr/qopenxrgraphics_vulkan.cpp