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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | QOpenXRGraphicsVulkan::QOpenXRGraphicsVulkan() |
19 | { |
20 | m_graphicsBinding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; |
21 | } |
22 | |
23 | |
24 | bool 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 | |
35 | const char *QOpenXRGraphicsVulkan::extensionName() const |
36 | { |
37 | return XR_KHR_VULKAN_ENABLE_EXTENSION_NAME; |
38 | } |
39 | |
40 | |
41 | const XrBaseInStructure *QOpenXRGraphicsVulkan::handle() const |
42 | { |
43 | return reinterpret_cast<const XrBaseInStructure*>(&m_graphicsBinding); |
44 | } |
45 | |
46 | |
47 | bool 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 | |
182 | bool 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 | |
202 | int64_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 | |
219 | int64_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 | |
244 | QVector<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 | |
257 | QQuickRenderTarget QOpenXRGraphicsVulkan::(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 | |
326 | void 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 | |
333 | void QOpenXRGraphicsVulkan::releaseResources() |
334 | { |
335 | delete m_depthTexture; |
336 | m_depthTexture = nullptr; |
337 | } |
338 | |
339 | QT_END_NAMESPACE |
340 | |