Files
samples/openXRComponent.hpp

884 lines
36 KiB
C++
Raw Normal View History

2026-06-23 20:25:40 -04:00
/*
* Copyright (c) 2026 Mykhailo Mamedov. All rights reserved.
*
* RESEARCH PREVIEW / REFERENCE ONLY:
* This source code is provided solely for the purpose of reviewing
* the author's research methods and implementation.
*
* NO LICENSE GRANTED:
* This code is NOT for distribution, modification, or use in any
* project (commercial or otherwise). Unauthorized copying or
* use of this code is strictly prohibited.
*
* For inquiries regarding use or licensing, contact: ua.modin@gmail.com
*
* Description:
*
* The OpenXR Interface serves as an application-to-hardware bridge through OpenXR,
* currently optimized for integration with VR systems like SteamVR.
*
* Current Iteration:
* - Connection via OpenXR, basic session, instance.
* - Provides HMD transforms and hand tracking data.
*
* Future Improvements:
* - Structure changes.
* - Device/API compatibility checking improvements.
*
*/
#ifndef OPENXR_HEADER
#define OPENXR_HEADER
#define XR_USE_PLATFORM_WIN32
#define XR_USE_GRAPHICS_API_VULKAN
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <iostream>
#include <vector>
#include <functional>
namespace cfgXr {
;
}
namespace M_CORE {
struct sem;
struct mDevice;
struct PhyDevice;
}
struct SwapchainResources {
XrSwapchain handle = nullptr;
std::vector<VkImage> images;
std::vector<VkImageView> imageViews;
std::vector<M_CORE::m_texture> m_depthImages;
VkFormat format = VkFormat::VK_FORMAT_UNDEFINED;
};
enum HandIndex {
HAND_LEFT = 0,
HAND_RIGHT = 1,
HAND_COUNT = 2
};
struct HandTrackingState {
XrHandJointLocationEXT jointLocations[2][XR_HAND_JOINT_COUNT_EXT];
XrHandJointLocationsEXT handLocations[2];
};
/**
* CB signature : It passes the views(for camera transforms)
* and the index the swapchain wants app to render to.
*/
using RenderCallback = std::function<void(
const std::vector<XrCompositionLayerProjectionView>& projectionViews,
uint32_t imageIndex
)>;
class OpenXrInterface {
public:
void SetRenderCallback(RenderCallback cb) { m_renderCallback = cb; }
void CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api);
bool checkDeviceCompatible(M_CORE::mDevice& m_device);
void PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources);
void CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device);
void RenderXrScene();
OpenXrInterface() {
PrintInstanceExtensions();
m_currentInstance = createXrInstance();
XrSystemGetInfo info{ XR_TYPE_SYSTEM_GET_INFO };
info.formFactor = XrFormFactor::XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrResult res = xrGetSystem(m_currentInstance, &info, &m_xrSystemId);
assert(!XR_FAILED(res) && "xrGetSystem failed!");
SetRequiredInstanceExtensions();
SetVulkanXrDeviceExtensions();
getVulkanGfxRequirement();
GetViewConfigViews(m_currentInstance, m_xrSystemId);
SetEnvironmentBlendModes();
XrResult result = xrGetInstanceProcAddr(
m_currentInstance, "xrLocateHandJointsEXT", (PFN_xrVoidFunction*)&pfnxrLocateHandJointsEXT);
assert(!XR_FAILED(result));
}
// Example: Get configuration for the primary stereo view
void GetViewConfigViews(XrInstance instance, XrSystemId systemId) {
uint32_t viewCount = 0;
// Get the required count
XrResult result = xrEnumerateViewConfigurationViews(
instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
0, &viewCount, nullptr);
assert(!XR_FAILED(result));
// Allocate and get data
m_views.resize(viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW });
result = xrEnumerateViewConfigurationViews(
instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
viewCount, &viewCount, m_views.data());
assert(!XR_FAILED(result));
// Set defaults:
assert(m_views.size());
m_samples = m_views[0].recommendedSwapchainSampleCount;
m_width = m_views[0].recommendedImageRectWidth;
m_height = m_views[0].recommendedImageRectHeight;
}
XrInstance GetXrInstance() {
assert(m_currentInstance);
return m_currentInstance;
}
void BeginXrSession() {
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
beginInfo.primaryViewConfigurationType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
beginInfo.next = nullptr;
XrResult result = xrBeginSession(m_xrSession, &beginInfo);
assert(XR_SUCCEEDED(result));
m_sessionLaunched = true;
}
XrInstance createXrInstance() {
XrInstanceCreateInfo instanceInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
instanceInfo.applicationInfo = {
.applicationName = "TheApp",
.applicationVersion = 1,
.engineName = "TheEngine",
.engineVersion = 1,
.apiVersion = XR_API_VERSION_1_0 // XR_CURRENT_API_VERSION // Forward compat.
};
std::vector<const char*> enabledExtensions = {
XR_KHR_VULKAN_ENABLE_EXTENSION_NAME,
XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME,
XR_EXT_HAND_TRACKING_EXTENSION_NAME
};
// XR_ERROR_EXTENSION_NOT_PRESENT TODO.
instanceInfo.enabledExtensionCount = enabledExtensions.size();
instanceInfo.enabledExtensionNames = enabledExtensions.data();
XrInstance instance;
XrResult result = xrCreateInstance(&instanceInfo, &instance);
assert(!XR_FAILED(result) && "OpenXR Runtime not found or failed!");
result = xrGetInstanceProperties(instance, &m_instanceProps);
assert(!XR_FAILED(result));
std::cout << "OpenXR Instance created successfully!" << std::endl;
std::cout << "Runtime Name: " << m_instanceProps.runtimeName << std::endl;
std::cout << "Runtime Version: " << XR_VERSION_MAJOR(m_instanceProps.runtimeVersion) << "."
<< XR_VERSION_MINOR(m_instanceProps.runtimeVersion) << "."
<< XR_VERSION_PATCH(m_instanceProps.runtimeVersion) << std::endl;
return instance;
}
void PrintInstanceExtensions() {
uint32_t extensionCount = 0;
xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr);
std::vector<XrExtensionProperties> extensionProperties(extensionCount, { XR_TYPE_EXTENSION_PROPERTIES });
xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data());
std::cout << "Xr Extensions:" << std::endl;
for (const auto& ext : extensionProperties) {
std::cout << std::string(ext.extensionName) << std::endl;
}
}
/**
* @brief Retrieves hand tracking information from the specified context.
*
* This function retrieves details about hand tracking, such as joint locations,
*/
void getHandTrackingInfo(XrHandTrackerEXT hand_tracker, const std::vector<XrView>& views, XrTime predictedDisplayTime, HandIndex hand_idx) {
m_handTrack.handLocations[hand_idx] = { XR_TYPE_HAND_JOINT_LOCATIONS_EXT };
m_handTrack.handLocations[hand_idx].jointCount = XR_HAND_JOINT_COUNT_EXT;
m_handTrack.handLocations[hand_idx].jointLocations = m_handTrack.jointLocations[hand_idx];
XrHandJointsLocateInfoEXT locateInfo{ XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT };
locateInfo.baseSpace = m_appSpace;
locateInfo.time = predictedDisplayTime;
XrResult result = pfnxrLocateHandJointsEXT(hand_tracker, &locateInfo, &m_handTrack.handLocations[hand_idx]);
assert(XR_SUCCEEDED(result));
/*
if (m_jointLocationsInfo.isActive) {
std::cout << "\n=== Hand Tracking Frame Data ===" << std::endl;
std::cout << std::fixed << std::setprecision(4);
for (uint32_t i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) {
constexpr XrSpaceLocationFlags validFlags =
XR_SPACE_LOCATION_POSITION_VALID_BIT |
XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
if ((m_jointLocations[i].locationFlags & validFlags) == validFlags) {
const XrVector3f& pos = m_jointLocations[i].pose.position;
const XrQuaternionf& rot = m_jointLocations[i].pose.orientation;
float radius = m_jointLocations[i].radius;
std::cout << "Joint [" << std::setw(2) << i << "] "
<< "Pos: (" << std::setw(7) << pos.x << ", "
<< std::setw(7) << pos.y << ", "
<< std::setw(7) << pos.z << ") | "
<< "Rot: (" << std::setw(7) << rot.x << ", "
<< std::setw(7) << rot.y << ", "
<< std::setw(7) << rot.z << ", "
<< std::setw(7) << rot.w << ") | "
<< "Rad: " << radius << "\n";
}
else {
std::cout << "Joint [" << std::setw(2) << i << "] Not tracked (invalid flags)\n";
}
}
std::cout << "================================\n" << std::endl;
}
else {
std::cout << "Hand tracker is currently INACTIVE." << std::endl;
}
*/
}
/**
* @brief Creates and initializes hand trackers.
*
* This function creates instances of XrHandTrackerEXT,
* It sets up the necessary configurations to start tracking both left and right hands in the current session.
* The created trackers are stored internally within this class instance.
*
*/
void CreateHandTrackers() {
XrHandTrackerCreateInfoEXT createInfo = { XrStructureType::XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT };
createInfo.hand = XrHandEXT::XR_HAND_LEFT_EXT;
createInfo.handJointSet = XrHandJointSetEXT::XR_HAND_JOINT_SET_DEFAULT_EXT;
PFN_xrCreateHandTrackerEXT pfnxrCreateHandTrackerEXT = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrCreateHandTrackerEXT", (PFN_xrVoidFunction*)&pfnxrCreateHandTrackerEXT);
assert(!XR_FAILED(result));
result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_leftHandTracker);
assert(!XR_FAILED(result));
createInfo.hand = XR_HAND_RIGHT_EXT;
result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_rightHandTracker);
assert(!XR_FAILED(result));
}
void SetEnvironmentBlendModes() {
uint32_t count = 0;
XrViewConfigurationType viewType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
// First call to get the required count
XrResult result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, 0, &count, nullptr);
assert(XR_SUCCEEDED(result));
m_blendModes.resize(count);
// Second call to get the actual data
result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, count, &count, m_blendModes.data());
assert(XR_SUCCEEDED(result));
}
void SetRequiredInstanceExtensions() {
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanInstanceExtensionsKHR);
assert(!XR_FAILED(result));
uint32_t length = 0;
result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &length, nullptr);
assert(!XR_FAILED(result));
if (length == 0) return;
std::string extensions(length, ' ');
result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, length, &length, extensions.data());
assert(!XR_FAILED(result));
// Parse the space-separated string
std::stringstream ss(extensions);
std::string ext;
while (ss >> ext) {
m_requiredVulkanInstanceExtensions.push_back(ext);
}
}
void SetVulkanXrDeviceExtensions() {
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanDeviceExtensionsKHR);
assert(!XR_FAILED(result));
uint32_t count = 0;
// Get the required buffer size
result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &count, nullptr);
assert(!XR_FAILED(result));
if (count == 0) return;
// Allocate buffer
std::string buffer(count, ' ');
// Populate buffer
result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, count, &count, &buffer[0]);
assert(!XR_FAILED(result));
// Parse just like the instance extensions
std::stringstream ss(buffer);
std::string ext;
while (ss >> ext) {
m_requiredVulkanDeviceExtensions.push_back(ext);
}
}
VkPhysicalDevice GetVulkanPhysicalDevice(VkInstance vkInstance) {
assert(m_currentInstance && m_xrSystemId && "Wrong order of execution.");
PFN_xrGetVulkanGraphicsDevice2KHR pfnGetVulkanGraphicsDevice2KHR = nullptr;
XrResult res = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsDevice2KHR);
assert(!XR_FAILED(res) && "xrGetInstanceProcAddr failed!");
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
XrVulkanGraphicsDeviceGetInfoKHR getInfo{ XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR };
getInfo.vulkanInstance = vkInstance;
getInfo.systemId = m_xrSystemId;
res = pfnGetVulkanGraphicsDevice2KHR(m_currentInstance, &getInfo, &physicalDevice);
assert(!XR_FAILED(res) && "pfnGetVulkanGraphicsDevice2 failed!");
return physicalDevice;
}
void getVulkanGfxRequirement(){
PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = nullptr;
XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsRequirementsKHR",
(PFN_xrVoidFunction*)&pfnGetVulkanGraphicsRequirementsKHR);
assert(!XR_FAILED(result) && "xrGetInstanceProcAddr failed!");
assert(pfnGetVulkanGraphicsRequirementsKHR &&
"Extension (xrGetVulkanGraphicsRequirementsKHR) not supported by runtime!");
// (must be done before xrCreateSession)
result = pfnGetVulkanGraphicsRequirementsKHR(m_currentInstance, m_xrSystemId, &m_graphicsRequirements);
assert(!XR_FAILED(result) && "pfnGetVulkanGraphicsRequirementsKHR failed!");
}
void ResetState(XrEventDataBuffer& eventData) {
std::memset(&eventData, 0, sizeof(XrEventDataBuffer));
eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
eventData.next = nullptr;
}
bool IsInstanceStateInvalid() {
XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
if (!m_currentInstance)
return true;
XrResult result = xrGetInstanceProperties(m_currentInstance, &instanceProperties);
assert(XR_SUCCEEDED(result));
if (result == XR_ERROR_HANDLE_INVALID) {
return true;
}
return false;
}
void HandshakeSimulatorSwapchains() { // TODO: Check if necessary.
XrSwapchain sw_chain = swapChainResources->handle;
assert(sw_chain);
uint32_t imageIndex = 0;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
// Check out the texture array from the runtime
XrResult res = xrAcquireSwapchainImage(sw_chain, &acquireInfo, &imageIndex);
if (res == XR_SUCCESS) {
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
waitInfo.timeout = XR_INFINITE_DURATION; // Let it block until ready
res = xrWaitSwapchainImage(sw_chain, &waitInfo);
assert(XR_SUCCEEDED(res));
// Immediately hand it right back to unblock the compositor queue
res = xrReleaseSwapchainImage(sw_chain, &releaseInfo);
assert(XR_SUCCEEDED(res));
}
}
void SetupXrSpace() {
XrReferenceSpaceCreateInfo spaceInfo = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; // XR_REFERENCE_SPACE_TYPE_LOCAL
spaceInfo.poseInReferenceSpace = { {0,0,0,1}, {0,0,0} };
assert(xrCreateReferenceSpace(m_xrSession, &spaceInfo, &m_appSpace) == XR_SUCCESS);
}
~OpenXrInterface() {
if(m_currentInstance)
xrDestroyInstance(m_currentInstance);
if (m_xrSession)
xrDestroySession(m_xrSession);
}
u64 m_scheduledWorkloadCounter = 0;
u32 m_previousIndex = 20;
u32 m_width = 0;
u32 m_height = 0;
u32 m_samples = 0;
u32 m_arraySizeInitial = 2;
u32 m_frame = 0;
bool m_sessionLaunched = false;
bool r_running = true;
bool r_ready = false;
bool r_idle = false;
bool r_isSessionRunning = false;
bool capture_xr = false;
u32 m_captured_frames = 0;
M_CORE::mDevice* m_deviceGfx = nullptr;
RENDERDOC_API_1_6_0* m_rdoc_api;
XrSessionState r_latestState = XR_SESSION_STATE_UNKNOWN;
XrSession m_xrSession = nullptr;
XrSystemId m_xrSystemId = 0;
XrSpace m_appSpace;
XrInstance m_currentInstance = nullptr;
XrInstanceProperties m_instanceProps{ XR_TYPE_INSTANCE_PROPERTIES };
XrHandTrackerEXT m_leftHandTracker;
XrHandTrackerEXT m_rightHandTracker;
XrGraphicsRequirementsVulkanKHR m_graphicsRequirements = { XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR };
XrActionSetCreateInfo m_actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
XrSessionActionSetsAttachInfo m_attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
PFN_xrLocateHandJointsEXT pfnxrLocateHandJointsEXT = nullptr;
std::vector<XrEnvironmentBlendMode> m_blendModes;
std::vector<XrViewConfigurationView> m_views;
std::vector <std::string> m_requiredVulkanInstanceExtensions;
std::vector <std::string> m_requiredVulkanDeviceExtensions;
SwapchainResources* swapChainResources = nullptr;
VkCommandPool xrCommandPool = nullptr;
RenderCallback m_renderCallback;
HandTrackingState m_handTrack{};
};
#endif // OPENXR_HEADER
#ifdef OPENXR_COMPONENT
void OpenXrInterface::CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api) {
XrGraphicsBindingVulkanKHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR };
graphicsBinding.instance = vulkanInstance;
graphicsBinding.physicalDevice = m_device.phyDeviceData.m_physDevice;
graphicsBinding.device = m_device.virtualDevice;
graphicsBinding.queueFamilyIndex = queueFamilyIndex;
graphicsBinding.queueIndex = 0; // ?
XrSessionCreateInfo sessionCreateInfo = { XR_TYPE_SESSION_CREATE_INFO };
sessionCreateInfo.next = &graphicsBinding;
sessionCreateInfo.systemId = m_xrSystemId;
XrResult res = xrCreateSession(m_currentInstance, &sessionCreateInfo, &m_xrSession);
assert(res == XR_SUCCESS && m_xrSession);
SetupXrSpace();
// Create action set (TEST)
strcpy(m_actionSetInfo.actionSetName, "gameplay");
strcpy(m_actionSetInfo.localizedActionSetName, "Gameplay");
XrActionSet actionSet;
res = xrCreateActionSet(m_currentInstance, &m_actionSetInfo, &actionSet);
assert(XR_SUCCEEDED(res));
// Attach to the session
m_attachInfo.countActionSets = 1;
m_attachInfo.actionSets = &actionSet;
res = xrAttachSessionActionSets(m_xrSession, &m_attachInfo);
assert(XR_SUCCEEDED(res));
m_deviceGfx = &m_device;
m_rdoc_api = rdoc_api;
}
bool OpenXrInterface::checkDeviceCompatible(M_CORE::mDevice& m_device) {
// Ensure the device supports at least the minimum version
uint32_t xrMinMajor = (m_graphicsRequirements.minApiVersionSupported >> 48) & 0xFFFF;
uint32_t xrMinMinor = (m_graphicsRequirements.minApiVersionSupported >> 32) & 0xFFFF;
uint32_t vkMajor = (m_device.phyDeviceData.m_devProps.apiVersion >> 22) & 0x3FF;
uint32_t vkMinor = (m_device.phyDeviceData.m_devProps.apiVersion >> 12) & 0x3FF;
bool versionOk = vkMajor >= xrMinMajor && vkMinor >= xrMinMinor;
if (!versionOk) {
auto err_string = std::format("Api version {} not supported on {}, required {}",
m_device.phyDeviceData.m_devProps.apiVersion,
m_device.phyDeviceData.m_devProps.deviceName,
m_graphicsRequirements.minApiVersionSupported
);
assert(versionOk && err_string.c_str());
}
return true;
}
/**
* Create XR swap-chain.
*/
void OpenXrInterface::CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device) {
assert(m_currentInstance && m_xrSession);
// Create Swapchain
swapChainResources = new SwapchainResources();
swapChainResources->format = VK_FORMAT_R8G8B8A8_SRGB;
XrSwapchainCreateInfo swapchainInfo = { XR_TYPE_SWAPCHAIN_CREATE_INFO };
swapchainInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT; // Adding XR_SWAPCHAIN_USAGE_SAMPLED_BIT
swapchainInfo.format = swapChainResources->format;
swapchainInfo.sampleCount = 1; // m_samples
swapchainInfo.width = m_width;
swapchainInfo.height = m_height;
swapchainInfo.faceCount = 1;
swapchainInfo.arraySize = m_arraySizeInitial; // Stereo
swapchainInfo.mipCount = 1;
swapchainInfo.createFlags = 0;
XrResult res = xrCreateSwapchain(m_xrSession, &swapchainInfo, &swapChainResources->handle);
assert(XR_SUCCEEDED(res));
// Prepare views
PrepareSwapchainImageViews(core, device, *swapChainResources);
}
/**
* Function to prepare Vulkan Image and Depth Views.
*/
void OpenXrInterface::PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources) {
u32 imageCount = 0;
XrResult res = xrEnumerateSwapchainImages(resources.handle, 0, &imageCount, nullptr);
assert(XR_SUCCEEDED(res));
std::vector<XrSwapchainImageVulkanKHR> swapchainImages(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR });
res = xrEnumerateSwapchainImages(
resources.handle, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(swapchainImages.data()));
assert(XR_SUCCEEDED(res));
resources.images.clear();
resources.imageViews.clear();
resources.m_depthImages.resize(imageCount);
u32 counter = 0;
for (int i = 0; i < imageCount; i++) {
auto& image = swapchainImages[i];
resources.images.push_back(image.image);
VkImageViewCreateInfo viewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
viewInfo.image = image.image;
viewInfo.viewType = VkImageViewType::VK_IMAGE_VIEW_TYPE_2D_ARRAY;
viewInfo.format = resources.format;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = m_arraySizeInitial;
VkImageView imageView;
if (vkCreateImageView(device.virtualDevice, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
throw std::runtime_error("failed to create image view!");
}
core.setObjectName(device.virtualDevice, imageView, std::format("xrImage_{}", counter).c_str());
resources.imageViews.push_back(imageView);
VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout NewLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
core.TransitionImageLayout(viewInfo.image, resources.format, OldLayout, NewLayout, 1);
vkDeviceWaitIdle(device.virtualDevice);
counter++;
}
// Depth resources maintained by us, not XR api.
VkFormat DepthFormat = device.phyDeviceData.m_depthFormat;
for (int i = 0; i < imageCount; i++) {
M_CORE::ImageParams parms{
.device = device,
.Tex = resources.m_depthImages[i],
.ImageWidth = m_width,
.ImageHeight = m_height,
.TexFormat = DepthFormat,
.UsageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.PropertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.ImageDepth = 1,
.name = std::format("xrDepthImage_{}", i).c_str(),
.shareMode = VK_SHARING_MODE_EXCLUSIVE,
.sharedQueueFamilies = {0, 0},
.IsCubemap = false,
.Is3dImage = false,
.IsStereo = true
};
core.CreateImage(parms);
VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageLayout NewLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
M_CORE::ImageViewParams params{};
params.Device = device.virtualDevice;
params.Image = resources.m_depthImages[i].m_image;
params.Format = DepthFormat;
params.AspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT;
params.IsStereo = true;
resources.m_depthImages[i].m_view = core.CreateImageView(params);
core.setObjectName(device.virtualDevice,
resources.m_depthImages[i].m_view, std::format("xrDepthImage_{}", i).c_str());
core.TransitionImageLayout(resources.m_depthImages[i].m_image, DepthFormat, OldLayout, NewLayout, 1);
}
}
/**
* @brief RenderXrScene is the function responsible for rendering scenes within an OpenXR application context,
* facilitating interaction between VR and graphics pipelines.
*
* @details It processes:
* - Camera transforms to ensure accurate alignment of virtual content with user's field of view.
* - Hand tracking data for realistic interactions in VR scenarios.
* - Swapchain images captured during each frame cycle to provide real-time visual feedback.
* This function integrates hardware inputs like camera movements, hand positions,
* orientations, and joint locations into the rendering process.
*/
void OpenXrInterface::RenderXrScene() {
XrResult result = XrResult::XR_SUCCESS;
assert(swapChainResources);
if (!r_running)
return;
while (true) {
if (IsInstanceStateInvalid()) {
r_running = false;
break;
}
// --- Event Handling ---
XrEventDataBuffer eventData = { XR_TYPE_EVENT_DATA_BUFFER, nullptr };
while (true) {
result = xrPollEvent(m_currentInstance, &eventData);
if (result != XR_SUCCESS) { // XR_ERROR_VALIDATION_FAILURE
if (result == XR_EVENT_UNAVAILABLE) {
ResetState(eventData);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
break;
}
char errorString[XR_MAX_RESULT_STRING_SIZE];
XrResult result2 = xrResultToString(m_currentInstance, result, errorString);
assert(XR_SUCCEEDED(result2));
printf(std::format("xrPollEvent throws: {}\n", errorString).c_str());
ResetState(eventData);
break;
}
if (eventData.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) {
auto* stateChanged = reinterpret_cast<XrEventDataSessionStateChanged*>(&eventData);
r_latestState = stateChanged->state;
switch (stateChanged->state) {
case XR_SESSION_STATE_READY: {
BeginXrSession();
HandshakeSimulatorSwapchains();
ResetState(eventData);
break;
}
case XR_SESSION_STATE_SYNCHRONIZED: {
printf(std::format("XR_SESSION_STATE_SYNCHRONIZED\n").c_str());
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_VISIBLE: {
printf(std::format("XR_SESSION_STATE_VISIBLE\n").c_str());
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_FOCUSED: {
r_isSessionRunning = true;
ResetState(eventData);
break;
}
case XR_SESSION_STATE_IDLE: {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
ResetState(eventData);
break;
}
case XR_SESSION_STATE_STOPPING: {
r_isSessionRunning = false;
m_sessionLaunched = false;
if (stateChanged->state == XR_SESSION_STATE_STOPPING) {
xrEndSession(m_xrSession); // Clean up if required by spec
}
break;
}
}
}
else {
printf(std::format("Unhandled other event: {}\n", (int)eventData.type).c_str());
}
}
if (!m_sessionLaunched) {
break;
}
if (m_rdoc_api && capture_xr) m_rdoc_api->StartFrameCapture(m_deviceGfx->virtualDevice, nullptr);
// --- Frame Synchronization ---
XrFrameWaitInfo waitInfo = { XR_TYPE_FRAME_WAIT_INFO };
XrFrameState frameState = { XR_TYPE_FRAME_STATE };
result = xrWaitFrame(m_xrSession, &waitInfo, &frameState);
assert(!XR_FAILED(result));
XrFrameBeginInfo beginInfo = { XR_TYPE_FRAME_BEGIN_INFO };
result = xrBeginFrame(m_xrSession, &beginInfo);
assert(!XR_FAILED(result));
// --- Camera Pose Location ---
XrViewLocateInfo viewLocateInfo = { XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; // Assuming Stereo
viewLocateInfo.displayTime = frameState.predictedDisplayTime;
viewLocateInfo.space = m_appSpace;
XrViewState viewState = { XR_TYPE_VIEW_STATE };
u32 viewCount{ 0 };
std::vector<XrView> views;
result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, nullptr);
assert(!XR_FAILED(result));
assert(viewCount==2);
views.resize(viewCount, { XR_TYPE_VIEW });
result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data());
assert(!XR_FAILED(result));
bool invalid_view = false;
if ((viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) {
invalid_view = true; // Pose is invalid, do not submit this layer
}
if ((viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) {
invalid_view = true;
}
// --- Pull hand tracking.
getHandTrackingInfo(m_leftHandTracker, views, frameState.predictedDisplayTime, HAND_LEFT);
getHandTrackingInfo(m_rightHandTracker, views, frameState.predictedDisplayTime, HAND_RIGHT);
// --- Rendering ---
XrCompositionLayerProjection projectionLayer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION };
std::vector<XrCompositionLayerProjectionView> projectionViews(viewCount, { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW });
if (frameState.shouldRender) {
XrRect2Di singleEyeRect;
singleEyeRect.offset.x = 0;
singleEyeRect.offset.y = 0;
singleEyeRect.extent.width = m_width;
singleEyeRect.extent.height = m_height;
if(true) {
u32 imageIndex;
XrSwapchainImageAcquireInfo acquireInfo = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
result = xrAcquireSwapchainImage(swapChainResources->handle, &acquireInfo, &imageIndex);
assert(!XR_FAILED(result));
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
waitInfo.timeout = XR_INFINITE_DURATION;
if (xrWaitSwapchainImage(swapChainResources->handle, &waitInfo) == XR_SUCCESS) {
// Prepare projection views.
for (uint32_t i = 0; i < viewCount; ++i) {
projectionViews[i].pose = views[i].pose;
projectionViews[i].fov = views[i].fov;
projectionViews[i].subImage.swapchain = swapChainResources->handle;
projectionViews[i].subImage.imageArrayIndex = i;
projectionViews[i].subImage.imageRect = singleEyeRect;
}
// Use registered CB, inherited from the parent instance.
// See who called SetRenderCallback() declared above.
assert(m_renderCallback);
m_renderCallback(projectionViews, 0);
m_frame++;
}
else {
throw std::runtime_error("xrWaitSwapchainImage failed!"); // TODO Check handling.
}
XrSwapchainImageReleaseInfo releaseInfo = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
result = xrReleaseSwapchainImage(swapChainResources->handle, &releaseInfo);
assert(!XR_FAILED(result));
}
// End Frame
projectionLayer.space = m_appSpace;
projectionLayer.viewCount = viewCount;
projectionLayer.views = projectionViews.data();
const XrCompositionLayerBaseHeader* layers[] = { (XrCompositionLayerBaseHeader*)&projectionLayer };
XrFrameEndInfo endInfo = { XR_TYPE_FRAME_END_INFO };
endInfo.displayTime = frameState.predictedDisplayTime;
endInfo.layerCount = 1;
endInfo.layers = layers;
endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; // XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
assert(!not_contains(endInfo.environmentBlendMode, m_blendModes));
if (invalid_view || !r_isSessionRunning || !frameState.shouldRender) {
endInfo.layers = nullptr;
endInfo.layerCount = 0;
}
result = xrEndFrame(m_xrSession, &endInfo);
if (m_rdoc_api && capture_xr) m_rdoc_api->EndFrameCapture(m_deviceGfx->virtualDevice, nullptr);
m_captured_frames++;
if (m_captured_frames > 3) {
m_captured_frames = 0;
capture_xr = false;
}
if (result == XR_SESSION_LOSS_PENDING) {
printf(std::format("Session loss pending\n").c_str());
r_running = false;
}
if (result == XR_ERROR_SESSION_NOT_RUNNING) {
printf(std::format("Session is not running. Pausing frame loop.\n").c_str());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (XR_FAILED(result)) {
char errorString[XR_MAX_RESULT_STRING_SIZE];
result = xrResultToString(m_currentInstance, result, errorString);
assert(XR_SUCCEEDED(result));
printf(std::format("xrEndFrame throws: {}\n", errorString).c_str());
}
}
else {
printf(std::format("Skipping render.\n").c_str());
}
break; // Not a loop.
}
// --- Cleanup ---
// Destroy resources here as this is the end of the thread - TODO
}
#endif