Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
17
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 extension.extensionName))
29 return true;
30 }
31 return false;
32}
33
34
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(xrGetInstanceProcAddr(instance,
65 "xrGetVulkanGraphicsRequirementsKHR",
66 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsRequirementsKHR)),
67 instance);
68
69 if (!pfnGetVulkanGraphicsRequirementsKHR) {
70 qWarning("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(xrGetInstanceProcAddr(instance,
76 "xrGetVulkanInstanceExtensionsKHR",
77 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanInstanceExtensionsKHR)),
78 instance);
79
80 XrGraphicsRequirementsVulkanKHR graphicsRequirements{};
81 graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR;
82 OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsRequirementsKHR(instance,
83 systemId,
84 &graphicsRequirements),
85 instance);
86
87 quint32 extensionNamesSize = 0;
88 OpenXRHelpers::checkXrResult(pfnGetVulkanInstanceExtensionsKHR(instance,
89 systemId,
90 0,
91 &extensionNamesSize,
92 nullptr),
93 instance);
94
95 QByteArray extensionNames;
96 extensionNames.resize(extensionNamesSize);
97 OpenXRHelpers::checkXrResult(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(' ');
116 for (auto &ext : extensions)
117 ext = stripNullChars(ext);
118
119 for (auto &rhiExt : QRhiVulkanInitParams::preferredInstanceExtensions()) {
120 if (!extensions.contains(rhiExt))
121 extensions.append(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("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(xrGetInstanceProcAddr(instance,
146 "xrGetVulkanDeviceExtensionsKHR",
147 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanDeviceExtensionsKHR)),
148 instance);
149
150 uint32_t deviceExtensionNamesSize = 0;
151 OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
152 systemId,
153 0,
154 &deviceExtensionNamesSize,
155 nullptr),
156 instance);
157 QByteArray deviceExtensionNames;
158 deviceExtensionNames.resize(deviceExtensionNamesSize);
159 OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
160 systemId,
161 deviceExtensionNamesSize,
162 &deviceExtensionNamesSize,
163 deviceExtensionNames.data()),
164 instance);
165
166 auto deviceExtensions = deviceExtensionNames.split(' ');
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(xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDeviceKHR",
175 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsDeviceKHR)), instance);
176
177 OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsDeviceKHR(instance, systemId, m_vulkanInstance.vkInstance(), &m_vulkanPhysicalDevice), instance);
178
179 return true;
180}
181
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(std::begin(supportedColorSwapchainFormats),
213 std::end(supportedColorSwapchainFormats),
214 swapchainFormats.begin(),
215 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.second))
231 supportedDepthSwapchainFormats.append(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(std::begin(supportedDepthSwapchainFormats),
239 std::end(supportedDepthSwapchainFormats),
240 swapchainFormats.begin(),
241 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(reinterpret_cast<XrSwapchainImageBaseHeader*>(&image));
251 }
252 m_swapchainImageBuffer.insert(swapchain, 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)
282
283 const QSize pixelSize(subImage.imageRect.extent.width, subImage.imageRect.extent.height);
284 QQuickRenderTarget rt = QQuickRenderTarget::fromVulkanImage(colorTexture,
285 VK_IMAGE_LAYOUT_UNDEFINED,
286 VkFormat(swapchainFormat),
287 viewFormat,
288 pixelSize,
289 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.
299 switch (depthSwapchainFormat) {
300 case VK_FORMAT_D32_SFLOAT_S8_UINT:
301 case VK_FORMAT_D32_SFLOAT:
303 break;
304 case VK_FORMAT_D16_UNORM:
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, 1, QRhiTexture::RenderTarget);
317 else
318 m_depthTexture = m_rhi->newTexture(format, pixelSize, 1, QRhiTexture::RenderTarget);
319 }
320 m_depthTexture->createFrom({ quint64(depthImage), VK_IMAGE_LAYOUT_UNDEFINED });
321 rt.setDepthTexture(m_depthTexture);
322 }
323 return rt;
324}
325
327{
328 quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromPhysicalDevice(m_vulkanPhysicalDevice));
329 quickWindow->setGraphicsConfiguration(m_graphicsConfiguration);
330 quickWindow->setVulkanInstance(&m_vulkanInstance);
331}
332
334{
335 delete m_depthTexture;
336 m_depthTexture = nullptr;
337}
338
\inmodule QtCore
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:612
QList< QByteArray > split(char sep) const
Splits the byte array into subarrays wherever sep occurs, and returns the list of those arrays.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QRhi * rhi() const override
int64_t colorSwapchainFormat(const QVector< int64_t > &swapchainFormats) const override
const char * extensionName() const override
bool isExtensionSupported(const QVector< XrExtensionProperties > &extensions) const override
bool finializeGraphics(QRhi *rhi) 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
const XrBaseInStructure * handle() const override
QVector< XrSwapchainImageBaseHeader * > allocateSwapchainImages(int count, XrSwapchain swapchain) override
bool setupGraphics(const XrInstance &instance, XrSystemId &systemId, const QQuickGraphicsConfiguration &quickConfig) override
QQuickGraphicsConfiguration controls lower level graphics settings for the QQuickWindow.
The QQuickRenderTarget class provides an opaque container for native graphics resources specifying a ...
void setDepthTexture(QRhiTexture *texture)
Requests using the given texture as the depth or depth-stencil buffer.
\qmltype Window \instantiates QQuickWindow \inqmlmodule QtQuick
Format format() const
Definition qrhi.h:973
int arraySize() const
Definition qrhi.h:982
@ RenderTarget
Definition qrhi.h:898
Format
Specifies the texture format.
Definition qrhi.h:914
virtual bool createFrom(NativeTexture src)
Similar to create(), except that no new native textures are created.
Definition qrhi.cpp:4490
QSize pixelSize() const
Definition qrhi.h:976
\variable QRhiVulkanInitParams::inst
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1805
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
Definition qrhi.cpp:10110
QRhiTexture * newTextureArray(QRhiTexture::Format format, int arraySize, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10644
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10570
const QRhiNativeHandles * nativeHandles()
Definition qrhi.cpp:10145
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
void extension()
[6]
Definition dialogs.cpp:230
bool checkXrResult(XrResult result, XrInstance instance)
Combined button and popup list for selecting options.
Definition image.cpp:4
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qWarning
Definition qlogging.h:167
GLsizei samples
GLuint GLuint end
GLenum GLenum GLsizei count
GLbitfield flags
GLint GLsizei GLsizei GLenum format
GLsizei const GLchar *const * string
[0]
Definition qopenglext.h:694
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
unsigned int quint32
Definition qtypes.h:50
unsigned long long quint64
Definition qtypes.h:61