Revision control

Copy as Markdown

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qopenxrgraphics_vulkan_p.h"
#include "qopenxrhelpers_p.h"
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickGraphicsDevice>
#include <QtQuick/QQuickGraphicsConfiguration>
#include <QtQuick/private/qquickrendertarget_p.h>
#include <rhi/qrhi.h>
//#define XR_USE_GRAPHICS_API_VULKAN
QT_BEGIN_NAMESPACE
QOpenXRGraphicsVulkan::QOpenXRGraphicsVulkan()
{
m_graphicsBinding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
}
bool QOpenXRGraphicsVulkan::initialize(const QVector<XrExtensionProperties> &extensions)
{
return hasExtension(extensions, XR_KHR_VULKAN_ENABLE_EXTENSION_NAME);
}
QVector<const char *> QOpenXRGraphicsVulkan::getRequiredExtensions() const
{
return { XR_KHR_VULKAN_ENABLE_EXTENSION_NAME };
}
const XrBaseInStructure *QOpenXRGraphicsVulkan::handle() const
{
return reinterpret_cast<const XrBaseInStructure*>(&m_graphicsBinding);
}
bool QOpenXRGraphicsVulkan::setupGraphics(const XrInstance &instance, XrSystemId &systemId, const QQuickGraphicsConfiguration &quickConfig)
{
// Setup Vulkan Instance.
// In hybrid applications that also show Qt Quick windows on the desktop, it
// is not ideal to create multiple VkInstances (as the on-screen
// QQuickWindow(s) will have another one), but there is nothing we can do
// due to the forced upfront nature of Vulkan API design. And we need to do
// OpenXR API calls to get the things we need to create the instance. This
// is hard to reconcile with Quick, that knows nothing about XrView and
// such, and cannot predict the future either (i.e., "guess" if the user is
// ever going to instantiate an XRView, and so on).
//
// This has no relevance for XR-only apps, and even the hybrid case this
// works in practice, so we might just live with this for now.
PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = nullptr;
OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
"xrGetVulkanGraphicsRequirementsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsRequirementsKHR)),
instance);
if (!pfnGetVulkanGraphicsRequirementsKHR) {
qWarning("Could not resolve xrGetVulkanGraphicsRequirementsKHR; perhaps the OpenXR implementation does not support Vulkan?");
return false;
}
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr;
OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
"xrGetVulkanInstanceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanInstanceExtensionsKHR)),
instance);
XrGraphicsRequirementsVulkanKHR graphicsRequirements{};
graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR;
OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsRequirementsKHR(instance,
systemId,
&graphicsRequirements),
instance);
quint32 extensionNamesSize = 0;
OpenXRHelpers::checkXrResult(pfnGetVulkanInstanceExtensionsKHR(instance,
systemId,
0,
&extensionNamesSize,
nullptr),
instance);
QByteArray extensionNames;
extensionNames.resize(extensionNamesSize);
OpenXRHelpers::checkXrResult(pfnGetVulkanInstanceExtensionsKHR(instance,
systemId,
extensionNamesSize,
&extensionNamesSize,
extensionNames.data()),
instance);
// The last extension could have extra null characters but
// the way we handle extenions doesn't handle null terminated
// strings well, so we have to strip them ourselves
auto stripNullChars = [](const QByteArray &string) {
auto begin = string.begin();
auto end = string.end();
while (begin < end && end[-1] == '\x00')
--end;
return QByteArray(begin, end - begin);
};
QByteArrayList extensions = extensionNames.split(' ');
for (auto &ext : extensions)
ext = stripNullChars(ext);
for (auto &rhiExt : QRhiVulkanInitParams::preferredInstanceExtensions()) {
if (!extensions.contains(rhiExt))
extensions.append(rhiExt);
}
m_vulkanInstance.setExtensions(extensions);
// Multiview is a Vulkan 1.1 feature and won't work without setting up the instance accordingly.
const QVersionNumber supportedVersion = m_vulkanInstance.supportedApiVersion();
if (supportedVersion >= QVersionNumber(1, 3))
m_vulkanInstance.setApiVersion(QVersionNumber(1, 3));
else if (supportedVersion >= QVersionNumber(1, 2))
m_vulkanInstance.setApiVersion(QVersionNumber(1, 2));
else if (supportedVersion >= QVersionNumber(1, 1))
m_vulkanInstance.setApiVersion(QVersionNumber(1, 1));
if (quickConfig.isDebugLayerEnabled())
m_vulkanInstance.setLayers({ "VK_LAYER_LUNARG_standard_validation" });
if (!m_vulkanInstance.create()) {
qWarning("Quick 3D XR: Failed to create Vulkan instance");
return false;
}
// Get Vulkan device extensions
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr;
OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance,
"xrGetVulkanDeviceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanDeviceExtensionsKHR)),
instance);
uint32_t deviceExtensionNamesSize = 0;
OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
systemId,
0,
&deviceExtensionNamesSize,
nullptr),
instance);
QByteArray deviceExtensionNames;
deviceExtensionNames.resize(deviceExtensionNamesSize);
OpenXRHelpers::checkXrResult(pfnGetVulkanDeviceExtensionsKHR(instance,
systemId,
deviceExtensionNamesSize,
&deviceExtensionNamesSize,
deviceExtensionNames.data()),
instance);
auto deviceExtensions = deviceExtensionNames.split(' ');
for (auto &ext : deviceExtensions) {
ext = stripNullChars(ext);
}
m_graphicsConfiguration.setDeviceExtensions(deviceExtensions);
// Get the Vulkan Graphics Device
PFN_xrGetVulkanGraphicsDeviceKHR pfnGetVulkanGraphicsDeviceKHR = nullptr;
OpenXRHelpers::checkXrResult(xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDeviceKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanGraphicsDeviceKHR)), instance);
OpenXRHelpers::checkXrResult(pfnGetVulkanGraphicsDeviceKHR(instance, systemId, m_vulkanInstance.vkInstance(), &m_vulkanPhysicalDevice), instance);
return true;
}
bool QOpenXRGraphicsVulkan::finializeGraphics(QRhi *rhi)
{
const QRhiVulkanNativeHandles *vulkanRhi = static_cast<const QRhiVulkanNativeHandles *>(rhi->nativeHandles());
m_vulkanDevice = vulkanRhi->dev;
Q_ASSERT(m_vulkanPhysicalDevice == vulkanRhi->physDev);
m_vulkanCommandQueue = vulkanRhi->gfxQueue;
m_queueFamilyIndex = vulkanRhi->gfxQueueFamilyIdx;
m_graphicsBinding.instance = m_vulkanInstance.vkInstance();
m_graphicsBinding.physicalDevice = m_vulkanPhysicalDevice;
m_graphicsBinding.device = m_vulkanDevice;
m_graphicsBinding.queueFamilyIndex = m_queueFamilyIndex;
m_graphicsBinding.queueIndex = 0;
m_rhi = rhi;
return true;
}
int64_t QOpenXRGraphicsVulkan::colorSwapchainFormat(const QVector<int64_t> &swapchainFormats) const
{
// List of supported color swapchain formats.
constexpr int64_t supportedColorSwapchainFormats[] = {
VK_FORMAT_B8G8R8A8_SRGB,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_R8G8B8A8_UNORM
};
auto swapchainFormatIt = std::find_first_of(std::begin(supportedColorSwapchainFormats),
std::end(supportedColorSwapchainFormats),
swapchainFormats.begin(),
swapchainFormats.end());
return *swapchainFormatIt;
}
int64_t QOpenXRGraphicsVulkan::depthSwapchainFormat(const QVector<int64_t> &swapchainFormats) const
{
// in order of preference
QVarLengthArray<int64_t, 4> supportedDepthSwapchainFormats;
constexpr std::pair<int64_t, QRhiTexture::Format> nativeDepthSwapchainFormats[] = {
{ VK_FORMAT_D24_UNORM_S8_UINT, QRhiTexture::D24S8 },
{ VK_FORMAT_D32_SFLOAT_S8_UINT, QRhiTexture::D32F },
{ VK_FORMAT_D32_SFLOAT, QRhiTexture::D32F },
{ VK_FORMAT_D16_UNORM, QRhiTexture::D16 }
};
for (const auto &format : nativeDepthSwapchainFormats) {
if (m_rhi->isTextureFormatSupported(format.second))
supportedDepthSwapchainFormats.append(format.first);
}
if (supportedDepthSwapchainFormats.isEmpty())
return 0;
// order matters, we prefer D24S8 above all the others
return *std::find_first_of(std::begin(supportedDepthSwapchainFormats),
std::end(supportedDepthSwapchainFormats),
swapchainFormats.begin(),
swapchainFormats.end());
}
QVector<XrSwapchainImageBaseHeader*> QOpenXRGraphicsVulkan::allocateSwapchainImages(int count, XrSwapchain swapchain)
{
QVector<XrSwapchainImageBaseHeader*> swapchainImages;
QVector<XrSwapchainImageVulkanKHR> swapchainImageBuffer(count);
for (XrSwapchainImageVulkanKHR& image : swapchainImageBuffer) {
image.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR;
swapchainImages.push_back(reinterpret_cast<XrSwapchainImageBaseHeader*>(&image));
}
m_swapchainImageBuffer.insert(swapchain, swapchainImageBuffer);
return swapchainImages;
}
QQuickRenderTarget QOpenXRGraphicsVulkan::renderTarget(const XrSwapchainSubImage &subImage,
const XrSwapchainImageBaseHeader *swapchainImage,
quint64 swapchainFormat,
int samples,
int arraySize,
const XrSwapchainImageBaseHeader *depthSwapchainImage,
quint64 depthSwapchainFormat) const
{
VkImage colorTexture = reinterpret_cast<const XrSwapchainImageVulkanKHR*>(swapchainImage)->image;
VkFormat viewFormat = VkFormat(swapchainFormat);
switch (swapchainFormat) {
case VK_FORMAT_R8G8B8A8_SRGB:
viewFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
case VK_FORMAT_B8G8R8A8_SRGB:
viewFormat = VK_FORMAT_B8G8R8A8_UNORM;
break;
default:
break;
}
QQuickRenderTarget::Flags flags;
if (samples > 1)
flags |= QQuickRenderTarget::Flag::MultisampleResolve;
const QSize pixelSize(subImage.imageRect.extent.width, subImage.imageRect.extent.height);
QQuickRenderTarget rt = QQuickRenderTarget::fromVulkanImage(colorTexture,
VK_IMAGE_LAYOUT_UNDEFINED,
VkFormat(swapchainFormat),
viewFormat,
pixelSize,
samples,
arraySize,
flags);
if (depthSwapchainImage) {
// There might be issues with stencil when MSAA is not used and the
// format is D16 or D32F or the half-unsupported D32FS8 (because then
// the OpenXR-provided texture is the one and only depth-stencil buffer,
// perhaps without stencil). But we prefer D24S8 whenever that's
// available, so hopefully this problem won't come up in practice.
QRhiTexture::Format format = QRhiTexture::D24S8;
switch (depthSwapchainFormat) {
case VK_FORMAT_D32_SFLOAT_S8_UINT:
case VK_FORMAT_D32_SFLOAT:
format = QRhiTexture::D32F;
break;
case VK_FORMAT_D16_UNORM:
format = QRhiTexture::D16;
break;
}
VkImage depthImage = reinterpret_cast<const XrSwapchainImageVulkanKHR*>(depthSwapchainImage)->image;
if (m_depthTexture && (m_depthTexture->format() != format || m_depthTexture->pixelSize() != pixelSize || m_depthTexture->arraySize() != arraySize)) {
delete m_depthTexture;
m_depthTexture = nullptr;
}
if (!m_depthTexture) {
// this is never multisample, QQuickRt takes care of resolving depth-stencil
if (arraySize > 1)
m_depthTexture = m_rhi->newTextureArray(format, arraySize, pixelSize, 1, QRhiTexture::RenderTarget);
else
m_depthTexture = m_rhi->newTexture(format, pixelSize, 1, QRhiTexture::RenderTarget);
}
m_depthTexture->createFrom({ quint64(depthImage), VK_IMAGE_LAYOUT_UNDEFINED });
rt.setDepthTexture(m_depthTexture);
}
return rt;
}
void QOpenXRGraphicsVulkan::setupWindow(QQuickWindow *quickWindow)
{
quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromPhysicalDevice(m_vulkanPhysicalDevice));
quickWindow->setGraphicsConfiguration(m_graphicsConfiguration);
quickWindow->setVulkanInstance(&m_vulkanInstance);
}
void QOpenXRGraphicsVulkan::releaseResources()
{
delete m_depthTexture;
m_depthTexture = nullptr;
}
QT_END_NAMESPACE