Files
thejerf--baijiSteamVR/docs/Source/Core/VideoBackends/Vulkan/VulkanOpenXR.cpp
T

664 lines
23 KiB
C++

// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef ENABLE_VR
// VulkanLoader.h must come first — it defines VK_NO_PROTOTYPES before vulkan.h.
#include "VideoBackends/Vulkan/VulkanLoader.h"
#define XR_USE_GRAPHICS_API_VULKAN
#include "VideoBackends/Vulkan/VulkanOpenXR.h"
#include <algorithm>
#include <array>
#include <sstream>
#include <string>
#include <vector>
#include "Common/Assert.h"
#include "Common/Logging/Log.h"
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKTexture.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/TextureConfig.h"
#include "VideoCommon/VR/OpenXRManager.h"
#ifdef _WIN32
#include <windows.h> // for SEH __try/__except
#endif
namespace Vulkan
{
std::unique_ptr<VulkanOpenXR> g_openxr_vk;
static const char* VkFormatToString(int64_t format)
{
switch (static_cast<VkFormat>(format))
{
case VK_FORMAT_R8G8B8A8_UNORM:
return "VK_FORMAT_R8G8B8A8_UNORM";
case VK_FORMAT_R8G8B8A8_SRGB:
return "VK_FORMAT_R8G8B8A8_SRGB";
case VK_FORMAT_B8G8R8A8_UNORM:
return "VK_FORMAT_B8G8R8A8_UNORM";
case VK_FORMAT_B8G8R8A8_SRGB:
return "VK_FORMAT_B8G8R8A8_SRGB";
case VK_FORMAT_R16G16B16A16_SFLOAT:
return "VK_FORMAT_R16G16B16A16_SFLOAT";
default:
return "UNKNOWN_VK_FORMAT";
}
}
static AbstractTextureFormat VkFormatToAbstractFormat(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
return AbstractTextureFormat::RGBA8;
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
return AbstractTextureFormat::BGRA8;
case VK_FORMAT_R16G16B16A16_SFLOAT:
return AbstractTextureFormat::RGBA16F;
default:
return AbstractTextureFormat::RGBA8;
}
}
static bool SelectSwapchainFormat(XrSession session, int64_t* out_format)
{
uint32_t format_count = 0;
XrResult result = xrEnumerateSwapchainFormats(session, 0, &format_count, nullptr);
if (XR_FAILED(result) || format_count == 0)
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrEnumerateSwapchainFormats (count) failed ({}).",
static_cast<int>(result));
return false;
}
std::vector<int64_t> runtime_formats(format_count);
result = xrEnumerateSwapchainFormats(session, format_count, &format_count, runtime_formats.data());
if (XR_FAILED(result))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrEnumerateSwapchainFormats failed ({}).",
static_cast<int>(result));
return false;
}
for (const int64_t format : runtime_formats)
{
INFO_LOG_FMT(VIDEO, "OpenXR: Runtime swapchain format {} ({})", static_cast<long long>(format),
VkFormatToString(format));
}
// Prefer linear (non-sRGB) formats — Dolphin handles gamma in shaders.
static constexpr std::array<VkFormat, 5> preferred_formats = {
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_R16G16B16A16_SFLOAT};
for (const VkFormat preferred : preferred_formats)
{
const int64_t wanted = static_cast<int64_t>(preferred);
if (std::find(runtime_formats.begin(), runtime_formats.end(), wanted) != runtime_formats.end())
{
*out_format = wanted;
INFO_LOG_FMT(VIDEO, "OpenXR: Selected swapchain format {} ({}).",
static_cast<long long>(*out_format), VkFormatToString(*out_format));
return true;
}
}
*out_format = runtime_formats.front();
WARN_LOG_FMT(VIDEO,
"OpenXR: No preferred Vulkan swapchain format found; falling back to runtime "
"format {} ({}).",
static_cast<long long>(*out_format), VkFormatToString(*out_format));
return true;
}
// Separate function for SEH protection — __try cannot be used in functions
// that have C++ objects requiring unwinding.
#ifdef _WIN32
static XrResult SafeCreateSession(XrInstance instance, const XrSessionCreateInfo* info,
XrSession* session)
{
__try
{
return xrCreateSession(instance, info, session);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
ERROR_LOG_FMT(VIDEO,
"OpenXR: xrCreateSession CRASHED (exception {:#010x}). "
"The OpenXR runtime may require Vulkan extensions that Dolphin did not enable. "
"Check the 'Required Vulkan instance/device extensions' log lines above.",
static_cast<unsigned>(GetExceptionCode()));
return XR_ERROR_RUNTIME_FAILURE;
}
}
#else
static XrResult SafeCreateSession(XrInstance instance, const XrSessionCreateInfo* info,
XrSession* session)
{
return xrCreateSession(instance, info, session);
}
#endif
VulkanOpenXR::VulkanOpenXR() = default;
VulkanOpenXR::~VulkanOpenXR()
{
Shutdown();
}
// static
bool VulkanOpenXR::PreQueryVulkanExtensions(VulkanExtensionRequirements& out)
{
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Pre-querying required Vulkan extensions...");
auto mgr = std::make_unique<VR::OpenXRManager>();
const std::vector<const char*> extensions = {XR_KHR_VULKAN_ENABLE_EXTENSION_NAME};
if (!mgr->CreateInstance(extensions))
return false;
if (!mgr->InitializeSystem())
return false;
if (!mgr->EnumerateViewConfigurations())
return false;
const XrInstance xr_instance = mgr->GetInstance();
const XrSystemId xr_system = mgr->GetSystemId();
// Query required Vulkan instance extensions.
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetInstanceExts = nullptr;
xrGetInstanceProcAddr(xr_instance, "xrGetVulkanInstanceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetInstanceExts));
if (pfnGetInstanceExts)
{
uint32_t ext_len = 0;
pfnGetInstanceExts(xr_instance, xr_system, 0, &ext_len, nullptr);
if (ext_len > 0)
{
std::string ext_str(ext_len, '\0');
pfnGetInstanceExts(xr_instance, xr_system, ext_len, &ext_len, ext_str.data());
INFO_LOG_FMT(VIDEO, "OpenXR: Required Vulkan instance extensions: {}", ext_str);
// Parse space-separated extension list.
std::istringstream iss(ext_str);
std::string ext;
while (iss >> ext)
out.instance_extensions.push_back(ext);
}
}
// Query required Vulkan device extensions.
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetDeviceExts = nullptr;
xrGetInstanceProcAddr(xr_instance, "xrGetVulkanDeviceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetDeviceExts));
if (pfnGetDeviceExts)
{
uint32_t ext_len = 0;
pfnGetDeviceExts(xr_instance, xr_system, 0, &ext_len, nullptr);
if (ext_len > 0)
{
std::string ext_str(ext_len, '\0');
pfnGetDeviceExts(xr_instance, xr_system, ext_len, &ext_len, ext_str.data());
INFO_LOG_FMT(VIDEO, "OpenXR: Required Vulkan device extensions: {}", ext_str);
std::istringstream iss(ext_str);
std::string ext;
while (iss >> ext)
out.device_extensions.push_back(ext);
}
}
// Keep the OpenXRManager alive — Initialize() will reuse it.
VR::g_openxr = std::move(mgr);
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Pre-query complete ({} instance, {} device extensions).",
out.instance_extensions.size(), out.device_extensions.size());
return true;
}
bool VulkanOpenXR::Initialize()
{
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Starting initialization...");
// If PreQueryVulkanExtensions() was called, VR::g_openxr already exists.
if (!VR::g_openxr)
{
auto mgr = std::make_unique<VR::OpenXRManager>();
const std::vector<const char*> extensions = {XR_KHR_VULKAN_ENABLE_EXTENSION_NAME};
if (!mgr->CreateInstance(extensions))
return false;
if (!mgr->InitializeSystem())
return false;
if (!mgr->EnumerateViewConfigurations())
return false;
VR::g_openxr = std::move(mgr);
}
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Creating session...");
if (!CreateSessionVulkan())
{
ERROR_LOG_FMT(VIDEO, "OpenXR Vulkan: Session creation failed — disabling VR.");
VR::g_openxr.reset();
return false;
}
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Creating reference space...");
if (!VR::g_openxr->CreateReferenceSpace())
{
ERROR_LOG_FMT(VIDEO, "OpenXR Vulkan: Reference space creation failed — disabling VR.");
VR::g_openxr.reset();
return false;
}
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Creating swapchains...");
if (!CreateSwapchains())
{
ERROR_LOG_FMT(VIDEO, "OpenXR Vulkan: Swapchain creation failed — disabling VR.");
VR::g_openxr.reset();
return false;
}
// Register this object as the swapchain provider so Presenter can acquire eye images.
VR::g_openxr->SetSwapchain(this);
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Initialization complete.");
return true;
}
void VulkanOpenXR::Shutdown()
{
// Clear swapchain pointer before destroying swapchains so no dangling use occurs.
if (VR::g_openxr)
VR::g_openxr->SetSwapchain(nullptr);
DestroySwapchains();
VR::g_openxr.reset();
// Wait for all GPU work to finish before the Vulkan device is destroyed.
if (g_vulkan_context)
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Shut down.");
}
bool VulkanOpenXR::CreateSessionVulkan()
{
ASSERT(g_vulkan_context != nullptr);
ASSERT(VR::g_openxr != nullptr);
const XrInstance xr_instance = VR::g_openxr->GetInstance();
const XrSystemId xr_system = VR::g_openxr->GetSystemId();
// --- Query Vulkan graphics requirements (mandatory before session creation) ---
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Querying graphics requirements...");
PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanRequirements = nullptr;
XrResult result = xrGetInstanceProcAddr(
xr_instance, "xrGetVulkanGraphicsRequirementsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanRequirements));
if (XR_FAILED(result) || pfnGetVulkanRequirements == nullptr)
{
ERROR_LOG_FMT(VIDEO, "OpenXR: Could not load xrGetVulkanGraphicsRequirementsKHR.");
return false;
}
XrGraphicsRequirementsVulkanKHR requirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR};
result = pfnGetVulkanRequirements(xr_instance, xr_system, &requirements);
if (XR_FAILED(result))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrGetVulkanGraphicsRequirementsKHR failed ({}).",
static_cast<int>(result));
return false;
}
INFO_LOG_FMT(VIDEO, "OpenXR: Vulkan requirements — min API {}.{}.{}, max API {}.{}.{}",
XR_VERSION_MAJOR(requirements.minApiVersionSupported),
XR_VERSION_MINOR(requirements.minApiVersionSupported),
XR_VERSION_PATCH(requirements.minApiVersionSupported),
XR_VERSION_MAJOR(requirements.maxApiVersionSupported),
XR_VERSION_MINOR(requirements.maxApiVersionSupported),
XR_VERSION_PATCH(requirements.maxApiVersionSupported));
// Log Dolphin's Vulkan API version for comparison.
const u32 dolphin_api = g_vulkan_context->GetDeviceInfo().apiVersion;
INFO_LOG_FMT(VIDEO, "OpenXR: Dolphin VkPhysicalDevice API version {}.{}.{}",
VK_VERSION_MAJOR(dolphin_api), VK_VERSION_MINOR(dolphin_api),
VK_VERSION_PATCH(dolphin_api));
// --- Query required Vulkan instance extensions ---
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Querying required Vulkan instance extensions...");
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetInstanceExts = nullptr;
result = xrGetInstanceProcAddr(xr_instance, "xrGetVulkanInstanceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetInstanceExts));
if (XR_SUCCEEDED(result) && pfnGetInstanceExts != nullptr)
{
uint32_t ext_len = 0;
pfnGetInstanceExts(xr_instance, xr_system, 0, &ext_len, nullptr);
if (ext_len > 0)
{
std::string ext_str(ext_len, '\0');
pfnGetInstanceExts(xr_instance, xr_system, ext_len, &ext_len, ext_str.data());
INFO_LOG_FMT(VIDEO, "OpenXR: Required Vulkan instance extensions: {}", ext_str);
}
else
{
INFO_LOG_FMT(VIDEO, "OpenXR: No additional Vulkan instance extensions required.");
}
}
// --- Query required Vulkan device extensions ---
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Querying required Vulkan device extensions...");
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetDeviceExts = nullptr;
result = xrGetInstanceProcAddr(xr_instance, "xrGetVulkanDeviceExtensionsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetDeviceExts));
if (XR_SUCCEEDED(result) && pfnGetDeviceExts != nullptr)
{
uint32_t ext_len = 0;
pfnGetDeviceExts(xr_instance, xr_system, 0, &ext_len, nullptr);
if (ext_len > 0)
{
std::string ext_str(ext_len, '\0');
pfnGetDeviceExts(xr_instance, xr_system, ext_len, &ext_len, ext_str.data());
INFO_LOG_FMT(VIDEO, "OpenXR: Required Vulkan device extensions: {}", ext_str);
}
else
{
INFO_LOG_FMT(VIDEO, "OpenXR: No additional Vulkan device extensions required.");
}
}
// --- Verify the physical device matches what the runtime expects ---
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Checking physical device...");
PFN_xrGetVulkanGraphicsDeviceKHR pfnGetVulkanDevice = nullptr;
result = xrGetInstanceProcAddr(xr_instance, "xrGetVulkanGraphicsDeviceKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetVulkanDevice));
if (XR_SUCCEEDED(result) && pfnGetVulkanDevice != nullptr)
{
VkPhysicalDevice xr_physical_device = VK_NULL_HANDLE;
result = pfnGetVulkanDevice(xr_instance, xr_system,
g_vulkan_context->GetVulkanInstance(), &xr_physical_device);
if (XR_SUCCEEDED(result))
{
if (xr_physical_device != g_vulkan_context->GetPhysicalDevice())
{
WARN_LOG_FMT(VIDEO,
"OpenXR: Runtime wants a different VkPhysicalDevice than Dolphin selected. "
"VR may not work correctly.");
}
else
{
INFO_LOG_FMT(VIDEO, "OpenXR: VkPhysicalDevice matches runtime expectation.");
}
}
}
// --- Create XrSession bound to the active Vulkan device ---
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Creating XrSession with Vulkan binding...");
INFO_LOG_FMT(VIDEO, " VkInstance={}, VkPhysicalDevice={}, VkDevice={}, queueFamily={}, "
"queueIndex=0",
reinterpret_cast<void*>(g_vulkan_context->GetVulkanInstance()),
reinterpret_cast<void*>(g_vulkan_context->GetPhysicalDevice()),
reinterpret_cast<void*>(g_vulkan_context->GetDevice()),
g_vulkan_context->GetGraphicsQueueFamilyIndex());
XrGraphicsBindingVulkanKHR vk_binding{XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR};
vk_binding.instance = g_vulkan_context->GetVulkanInstance();
vk_binding.physicalDevice = g_vulkan_context->GetPhysicalDevice();
vk_binding.device = g_vulkan_context->GetDevice();
vk_binding.queueFamilyIndex = g_vulkan_context->GetGraphicsQueueFamilyIndex();
vk_binding.queueIndex = 0;
XrSessionCreateInfo session_info{XR_TYPE_SESSION_CREATE_INFO};
session_info.next = &vk_binding;
session_info.systemId = xr_system;
XrSession session = XR_NULL_HANDLE;
result = SafeCreateSession(xr_instance, &session_info, &session);
if (XR_FAILED(result))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrCreateSession failed ({}).", static_cast<int>(result));
return false;
}
VR::g_openxr->SetSession(session);
INFO_LOG_FMT(VIDEO, "OpenXR Vulkan: Session created successfully.");
return true;
}
bool VulkanOpenXR::CreateSwapchains()
{
ASSERT(VR::g_openxr != nullptr);
const auto& view_cfgs = VR::g_openxr->GetViewConfigViews();
int64_t swapchain_format = 0;
if (!SelectSwapchainFormat(VR::g_openxr->GetSession(), &swapchain_format))
return false;
const AbstractTextureFormat abstract_format =
VkFormatToAbstractFormat(static_cast<VkFormat>(swapchain_format));
for (uint32_t eye = 0; eye < 2; ++eye)
{
auto& sc = m_eye_swapchains[eye];
sc.width = view_cfgs[eye].recommendedImageRectWidth;
sc.height = view_cfgs[eye].recommendedImageRectHeight;
// Format must come from xrEnumerateSwapchainFormats.
XrSwapchainCreateInfo info{XR_TYPE_SWAPCHAIN_CREATE_INFO};
info.arraySize = 1;
info.format = swapchain_format;
info.width = sc.width;
info.height = sc.height;
info.mipCount = 1;
info.faceCount = 1;
info.sampleCount = 1;
info.usageFlags =
XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT;
XrResult result = xrCreateSwapchain(VR::g_openxr->GetSession(), &info, &sc.swapchain);
if (XR_FAILED(result))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrCreateSwapchain failed for eye {} ({}).", eye,
static_cast<int>(result));
return false;
}
// Enumerate the Vulkan images backing this swapchain.
uint32_t image_count = 0;
xrEnumerateSwapchainImages(sc.swapchain, 0, &image_count, nullptr);
std::vector<XrSwapchainImageVulkanKHR> images(image_count,
{XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR});
xrEnumerateSwapchainImages(sc.swapchain, image_count, &image_count,
reinterpret_cast<XrSwapchainImageBaseHeader*>(images.data()));
sc.textures.resize(image_count);
sc.framebuffers.resize(image_count);
for (uint32_t i = 0; i < image_count; ++i)
{
// Build a TextureConfig matching the swapchain image properties.
TextureConfig tex_config(sc.width, sc.height, /*levels=*/1, /*layers=*/1, /*samples=*/1,
abstract_format, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2D);
// Adopt the runtime-owned VkImage. VKTexture::CreateAdopted wraps the image
// without allocating device memory (the runtime owns it).
sc.textures[i] = VKTexture::CreateAdopted(tex_config, images[i].image,
VK_IMAGE_VIEW_TYPE_2D,
VK_IMAGE_LAYOUT_UNDEFINED);
if (!sc.textures[i])
{
ERROR_LOG_FMT(VIDEO, "OpenXR: VKTexture::CreateAdopted failed for eye {}, image {}.", eye,
i);
return false;
}
sc.framebuffers[i] = VKFramebuffer::Create(sc.textures[i].get(), nullptr, {});
if (!sc.framebuffers[i])
{
ERROR_LOG_FMT(VIDEO, "OpenXR: VKFramebuffer::Create failed for eye {}, image {}.", eye, i);
return false;
}
}
INFO_LOG_FMT(VIDEO, "OpenXR: Eye {} swapchain ready: {}x{}, {} images.", eye, sc.width,
sc.height, image_count);
}
return true;
}
void VulkanOpenXR::DestroySwapchains()
{
// Wait for the GPU to finish all pending work before destroying resources.
if (g_vulkan_context)
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
for (uint32_t eye = 0; eye < 2; ++eye)
{
auto& sc = m_eye_swapchains[eye];
if (m_image_acquired[eye] && sc.swapchain != XR_NULL_HANDLE)
{
XrSwapchainImageReleaseInfo release_info{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
const XrResult release_result = xrReleaseSwapchainImage(sc.swapchain, &release_info);
if (XR_FAILED(release_result))
{
WARN_LOG_FMT(VIDEO,
"OpenXR: xrReleaseSwapchainImage during shutdown failed for eye {} ({}).",
eye, static_cast<int>(release_result));
}
m_image_acquired[eye] = false;
}
// Release Dolphin wrappers before destroying the swapchain so the
// runtime's VkImages are only freed after our views are gone.
sc.framebuffers.clear();
sc.textures.clear();
if (sc.swapchain != XR_NULL_HANDLE)
{
const XrResult destroy_result = xrDestroySwapchain(sc.swapchain);
if (XR_FAILED(destroy_result))
{
WARN_LOG_FMT(VIDEO, "OpenXR: xrDestroySwapchain failed for eye {} ({}).", eye,
static_cast<int>(destroy_result));
}
sc.swapchain = XR_NULL_HANDLE;
}
}
}
AbstractFramebuffer* VulkanOpenXR::AcquireEyeFramebuffer(uint32_t eye_index)
{
ASSERT(eye_index < 2);
auto& sc = m_eye_swapchains[eye_index];
XrSwapchainImageAcquireInfo acquire_info{XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
if (XR_FAILED(
xrAcquireSwapchainImage(sc.swapchain, &acquire_info, &m_acquired_image_index[eye_index])))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrAcquireSwapchainImage failed for eye {}.", eye_index);
return nullptr;
}
m_image_acquired[eye_index] = true;
// Block until the acquired image is safe to write.
XrSwapchainImageWaitInfo wait_info{XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
wait_info.timeout = XR_INFINITE_DURATION;
if (XR_FAILED(xrWaitSwapchainImage(sc.swapchain, &wait_info)))
{
ERROR_LOG_FMT(VIDEO, "OpenXR: xrWaitSwapchainImage failed for eye {}.", eye_index);
// Ensure we don't leak an acquired image if waiting fails.
XrSwapchainImageReleaseInfo release_info{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
const XrResult release_result = xrReleaseSwapchainImage(sc.swapchain, &release_info);
if (XR_FAILED(release_result))
{
WARN_LOG_FMT(VIDEO,
"OpenXR: xrReleaseSwapchainImage after wait failure failed for eye {} ({}).",
eye_index, static_cast<int>(release_result));
}
m_image_acquired[eye_index] = false;
return nullptr;
}
// Reset the image layout to UNDEFINED — the runtime may have changed it since last release,
// and we're about to clear the image anyway via SetAndClearFramebuffer.
const uint32_t idx = m_acquired_image_index[eye_index];
sc.textures[idx]->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
return sc.framebuffers[idx].get();
}
void VulkanOpenXR::ReleaseEyeTexture(uint32_t eye_index)
{
ASSERT(eye_index < 2);
if (!m_image_acquired[eye_index])
return;
// End any active render pass so the image layout transitions are recorded
// and the image is no longer being written to.
StateTracker::GetInstance()->EndRenderPass();
XrSwapchainImageReleaseInfo release_info{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
const XrResult result =
xrReleaseSwapchainImage(m_eye_swapchains[eye_index].swapchain, &release_info);
if (XR_FAILED(result))
{
WARN_LOG_FMT(VIDEO, "OpenXR: xrReleaseSwapchainImage failed for eye {} ({}).", eye_index,
static_cast<int>(result));
}
m_image_acquired[eye_index] = false;
}
bool VulkanOpenXR::SubmitFrame()
{
ASSERT(VR::g_openxr != nullptr);
const auto& eye_views = VR::g_openxr->GetEyeViews();
for (uint32_t eye = 0; eye < 2; ++eye)
{
auto& pv = m_projection_views[eye];
pv = {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW};
pv.pose = eye_views[eye].pose;
pv.fov = eye_views[eye].fov;
pv.subImage.swapchain = m_eye_swapchains[eye].swapchain;
pv.subImage.imageArrayIndex = 0;
pv.subImage.imageRect = {
{0, 0},
{static_cast<int32_t>(m_eye_swapchains[eye].width),
static_cast<int32_t>(m_eye_swapchains[eye].height)}};
}
m_projection_layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
m_projection_layer.space = VR::g_openxr->GetReferenceSpace();
m_projection_layer.viewCount = 2;
m_projection_layer.views = m_projection_views.data();
const std::vector<XrCompositionLayerBaseHeader*> layers = {
reinterpret_cast<XrCompositionLayerBaseHeader*>(&m_projection_layer)};
return VR::g_openxr->EndFrame(layers);
}
} // namespace Vulkan
#endif // ENABLE_VR