#include "Common.h" #include "Compositor.h" #include "Config.h" #include "Runtime.h" #include #include #include #include #include #include #define XRSIM_UNSUPPORTED( NAME ) \ do \ { \ Log( "%s called (not implemented yet)", NAME ); \ return XR_ERROR_FUNCTION_UNSUPPORTED; \ } while ( 0 ) namespace { constexpr float kPi = 3.14159265358979323846f; std::mutex g_mutex; std::unordered_map g_instances; std::unordered_map g_sessions; std::unordered_map g_swapchains; std::unordered_map g_spaces; struct ActionSetObj { xrsim::RuntimeInstance * instance = nullptr; }; struct ActionObj { XrActionSet set = XR_NULL_HANDLE; XrActionType type = XR_ACTION_TYPE_BOOLEAN_INPUT; std::string name; }; std::unordered_map g_actionSets; std::unordered_map g_actions; ActionObj * GetAction( XrAction handle ) { std::lock_guard lock( g_mutex ); const auto it = g_actions.find( handle ); return it == g_actions.end() ? nullptr : it->second; } int HandFromSubaction( XrPath subactionPath ) { static const XrPath left = xrsim::HashPath( "/user/hand/left" ); static const XrPath right = xrsim::HashPath( "/user/hand/right" ); if ( subactionPath == left ) { return 0; } if ( subactionPath == right ) { return 1; } return -1; } // Paths are interned as stable 64-bit hashes (never 0). std::unordered_map g_pathStrings; xrsim::TrackingState g_tracking; xrsim::InputState g_input; bool EndsWith( const std::string & value, const char * suffix ) { const size_t length = strlen( suffix ); return value.size() >= length && value.compare( value.size() - length, length, suffix ) == 0; } /** * @brief Classified control for an action, derived from its name suffix. */ enum class ControlKind { Other, Trigger, Grip, Thumbstick, StickClick, ButtonA, ButtonB, ButtonX, ButtonY, Menu, Touch, PoseAim, PoseGrip, Haptic, }; ControlKind ClassifyAction( const std::string & name, XrActionType type ) { if ( EndsWith( name, "haptic" ) || EndsWith( name, "/haptic" ) ) return ControlKind::Haptic; if ( EndsWith( name, "aim_pose" ) ) return ControlKind::PoseAim; if ( EndsWith( name, "grip_pose" ) ) return ControlKind::PoseGrip; if ( EndsWith( name, "thumbstick_left_click" ) || EndsWith( name, "thumbstick_right_click" ) || EndsWith( name, "thumbstick_click" ) || EndsWith( name, "trackpad_click" ) ) return ControlKind::StickClick; if ( EndsWith( name, "thumbstick_left" ) || EndsWith( name, "thumbstick_right" ) || EndsWith( name, "thumbstick" ) || EndsWith( name, "trackpad" ) ) return type == XR_ACTION_TYPE_VECTOR2F_INPUT ? ControlKind::Thumbstick : ControlKind::StickClick; if ( EndsWith( name, "trigger_value" ) || EndsWith( name, "trigger_left_value" ) || EndsWith( name, "trigger_right_value" ) ) return ControlKind::Trigger; if ( EndsWith( name, "trigger_click" ) || EndsWith( name, "trigger_touch" ) || EndsWith( name, "trigger_proximity" ) ) return ControlKind::Trigger; if ( EndsWith( name, "select" ) || EndsWith( name, "select_click" ) ) return ControlKind::Trigger; if ( EndsWith( name, "grip_value" ) || EndsWith( name, "grip_force" ) ) return ControlKind::Grip; if ( EndsWith( name, "grip_click" ) || EndsWith( name, "grip_touch" ) || EndsWith( name, "squeeze_click" ) ) return ControlKind::Grip; if ( EndsWith( name, "_touch" ) || EndsWith( name, "_proximity" ) ) return ControlKind::Touch; if ( EndsWith( name, "_a_button" ) || EndsWith( name, "_a_click" ) || EndsWith( name, "_a" ) ) return ControlKind::ButtonA; if ( EndsWith( name, "_b_button" ) || EndsWith( name, "_b_click" ) || EndsWith( name, "_b" ) ) return ControlKind::ButtonB; if ( EndsWith( name, "_x_button" ) || EndsWith( name, "_x_click" ) || EndsWith( name, "_x" ) ) return ControlKind::ButtonX; if ( EndsWith( name, "_y_button" ) || EndsWith( name, "_y_click" ) || EndsWith( name, "_y" ) ) return ControlKind::ButtonY; if ( EndsWith( name, "menu" ) ) return ControlKind::Menu; return ControlKind::Other; } int64_t NowNs() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch() ) .count(); } template Handle ToHandle( Object * object ) { return reinterpret_cast( object ); } XrResult CopyString( char * destination, size_t destinationSize, const char * source ) { if ( destination == nullptr || destinationSize == 0 ) { return XR_ERROR_VALIDATION_FAILURE; } strncpy( destination, source, destinationSize - 1 ); destination[destinationSize - 1] = '\0'; return XR_SUCCESS; } const XrGraphicsBindingVulkan2KHR * FindVulkanBinding( const void * next ) { for ( const void * node = next; node != nullptr; ) { const auto * base = reinterpret_cast( node ); if ( base->type == XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR ) { return reinterpret_cast( node ); } node = base->next; } return nullptr; } uint32_t FindMemoryType( VkPhysicalDevice physicalDevice, uint32_t typeBits, VkMemoryPropertyFlags properties ) { VkPhysicalDeviceMemoryProperties memoryProperties{}; vkGetPhysicalDeviceMemoryProperties( physicalDevice, &memoryProperties ); for ( uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i ) { if ( ( typeBits & ( 1u << i ) ) != 0 && ( memoryProperties.memoryTypes[i].propertyFlags & properties ) == properties ) { return i; } } return UINT32_MAX; } bool ImmediateSubmit( xrsim::RuntimeSession * session, const std::function & fn ) { VkCommandBufferAllocateInfo allocInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; allocInfo.commandPool = session->commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer = VK_NULL_HANDLE; if ( vkAllocateCommandBuffers( session->vkDevice, &allocInfo, &commandBuffer ) != VK_SUCCESS ) { return false; } VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer( commandBuffer, &beginInfo ); fn( commandBuffer ); vkEndCommandBuffer( commandBuffer ); VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; vkQueueSubmit( session->queue, 1, &submitInfo, VK_NULL_HANDLE ); vkQueueWaitIdle( session->queue ); vkFreeCommandBuffers( session->vkDevice, session->commandPool, 1, &commandBuffer ); return true; } XrQuaternionf MultiplyQuat( const XrQuaternionf & a, const XrQuaternionf & b ) { return XrQuaternionf{ a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z, }; } } // namespace namespace xrsim { TrackingState & GetTracking() { return g_tracking; } InputState & GetInputState() { return g_input; } XrQuaternionf GetHeadOrientation() { const float halfYaw = g_tracking.yaw * 0.5f; const float halfPitch = g_tracking.pitch * 0.5f; const XrQuaternionf qYaw{ 0.0f, std::sin( halfYaw ), 0.0f, std::cos( halfYaw ) }; const XrQuaternionf qPitch{ std::sin( halfPitch ), 0.0f, 0.0f, std::cos( halfPitch ) }; return MultiplyQuat( qYaw, qPitch ); } XrVector3f GetHeadPosition() { return XrVector3f{ g_tracking.posX, g_tracking.posY, g_tracking.posZ }; } XrVector3f RotateByQuaternion( const XrQuaternionf & q, const XrVector3f & v ) { // v' = v + 2 * cross(q.xyz, cross(q.xyz, v) + q.w * v) const XrVector3f u{ q.x, q.y, q.z }; const XrVector3f uv{ u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x }; const XrVector3f uuv{ u.y * uv.z - u.z * uv.y, u.z * uv.x - u.x * uv.z, u.x * uv.y - u.y * uv.x }; return XrVector3f{ v.x + 2.0f * ( q.w * uv.x + uuv.x ), v.y + 2.0f * ( q.w * uv.y + uuv.y ), v.z + 2.0f * ( q.w * uv.z + uuv.z ), }; } XrPath HashPath( const char * path ) { uint64_t hash = 1469598103934665603ULL; for ( const char * c = path; c != nullptr && *c != '\0'; ++c ) { hash ^= static_cast( *c ); hash *= 1099511628211ULL; } if ( hash == XR_NULL_PATH ) { hash = 1; } if ( path != nullptr ) { std::lock_guard lock( g_mutex ); g_pathStrings[hash] = path; } return hash; } RuntimeInstance * GetInstance( XrInstance handle ) { std::lock_guard lock( g_mutex ); const auto it = g_instances.find( handle ); return it == g_instances.end() ? nullptr : it->second; } RuntimeSession * GetSession( XrSession handle ) { std::lock_guard lock( g_mutex ); const auto it = g_sessions.find( handle ); return it == g_sessions.end() ? nullptr : it->second; } RuntimeSwapchain * GetSwapchain( XrSwapchain handle ) { std::lock_guard lock( g_mutex ); const auto it = g_swapchains.find( handle ); return it == g_swapchains.end() ? nullptr : it->second; } RuntimeSpace * GetSpace( XrSpace handle ) { std::lock_guard lock( g_mutex ); const auto it = g_spaces.find( handle ); return it == g_spaces.end() ? nullptr : it->second; } bool ResolveSubImage( XrSwapchain swapchain, const XrSwapchainSubImage & subImage, VkImage * outImage, uint32_t * outArrayLayer, uint32_t * outWidth, uint32_t * outHeight ) { RuntimeSwapchain * runtime = GetSwapchain( swapchain ); if ( runtime == nullptr || subImage.swapchain != swapchain ) { return false; } // imageArrayIndex selects the array layer (eye) within the swapchain image. The image is // the one currently acquired, or the last released one when called after // xrReleaseSwapchainImage (the engine presents asynchronously). const int32_t imageIndexSigned = runtime->acquiredIndex >= 0 ? runtime->acquiredIndex : runtime->lastReleasedIndex; const uint32_t imageIndex = static_cast( imageIndexSigned ); if ( imageIndex >= runtime->images.size() ) { return false; } uint32_t arrayLayer = subImage.imageArrayIndex; if ( arrayLayer >= runtime->arraySize ) { arrayLayer = 0; } *outImage = runtime->images[imageIndex]; *outArrayLayer = arrayLayer; *outWidth = static_cast( subImage.imageRect.extent.width ); *outHeight = static_cast( subImage.imageRect.extent.height ); return true; } void PushSessionStateEvent( XrInstance instance, XrSession session, XrSessionState state ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr ) { return; } XrEventDataBuffer buffer{}; buffer.type = XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED; auto * event = reinterpret_cast( &buffer ); event->session = session; event->state = state; event->time = NowNs(); std::lock_guard lock( g_mutex ); runtime->events.push_back( buffer ); } // ---- global functions ------------------------------------------------- XrResult XRAPI_CALL EnumerateApiLayerProperties( uint32_t /*propertyCapacityInput*/, uint32_t * propertyCountOutput, XrApiLayerProperties * /*properties*/ ) { if ( propertyCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } *propertyCountOutput = 0; return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateInstanceExtensionProperties( const char * layerName, uint32_t propertyCapacityInput, uint32_t * propertyCountOutput, XrExtensionProperties * properties ) { if ( propertyCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( layerName != nullptr && layerName[0] != '\0' ) { *propertyCountOutput = 0; return XR_SUCCESS; } struct ExtensionDef { const char * name; uint32_t version; }; static const ExtensionDef kExtensions[] = { { XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME, XR_KHR_vulkan_enable2_SPEC_VERSION }, { XR_KHR_VULKAN_ENABLE_EXTENSION_NAME, XR_KHR_vulkan_enable_SPEC_VERSION }, { XR_EXT_DEBUG_UTILS_EXTENSION_NAME, XR_EXT_debug_utils_SPEC_VERSION }, }; constexpr uint32_t kExtensionCount = sizeof( kExtensions ) / sizeof( kExtensions[0] ); *propertyCountOutput = kExtensionCount; if ( propertyCapacityInput == 0 ) { return XR_SUCCESS; } if ( properties == nullptr || propertyCapacityInput < kExtensionCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } for ( uint32_t i = 0; i < kExtensionCount; ++i ) { properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; properties[i].next = nullptr; properties[i].extensionVersion = kExtensions[i].version; CopyString( properties[i].extensionName, XR_MAX_EXTENSION_NAME_SIZE, kExtensions[i].name ); } return XR_SUCCESS; } XrResult XRAPI_CALL CreateInstance( const XrInstanceCreateInfo * createInfo, XrInstance * instance ) { if ( createInfo == nullptr || instance == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * runtime = new RuntimeInstance(); runtime->applicationName = createInfo->applicationInfo.applicationName; runtime->engineName = createInfo->applicationInfo.engineName; for ( uint32_t i = 0; i < createInfo->enabledExtensionCount; ++i ) { runtime->enabledExtensions.emplace_back( createInfo->enabledExtensionNames[i] ); } runtime->handle = ToHandle( runtime ); runtime->valid = true; { std::lock_guard lock( g_mutex ); g_instances[runtime->handle] = runtime; } *instance = runtime->handle; Log( "Created XrInstance (app='%s')", runtime->applicationName.c_str() ); return XR_SUCCESS; } // ---- instance functions ----------------------------------------------- XrResult XRAPI_CALL DestroyInstance( XrInstance instance ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } if ( runtime->compositor ) { runtime->compositor->Shutdown(); runtime->compositor.reset(); } { std::lock_guard lock( g_mutex ); g_instances.erase( instance ); } delete runtime; Log( "Destroyed XrInstance" ); return XR_SUCCESS; } XrResult XRAPI_CALL GetInstanceProperties( XrInstance instance, XrInstanceProperties * properties ) { if ( GetInstance( instance ) == nullptr || properties == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } properties->runtimeVersion = XR_MAKE_VERSION( 0, 1, 0 ); CopyString( properties->runtimeName, XR_MAX_RUNTIME_NAME_SIZE, "OpenXR Simulator" ); return XR_SUCCESS; } XrResult XRAPI_CALL GetSystem( XrInstance instance, const XrSystemGetInfo * getInfo, XrSystemId * systemId ) { if ( GetInstance( instance ) == nullptr || getInfo == nullptr || systemId == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( getInfo->formFactor != XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY ) { return XR_ERROR_FORM_FACTOR_UNSUPPORTED; } *systemId = 1; return XR_SUCCESS; } XrResult XRAPI_CALL GetSystemProperties( XrInstance instance, XrSystemId /*systemId*/, XrSystemProperties * properties ) { if ( GetInstance( instance ) == nullptr || properties == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } const SimulatorConfig & config = GetConfig(); CopyString( properties->systemName, XR_MAX_SYSTEM_NAME_SIZE, config.systemName.c_str() ); properties->vendorId = config.vendorId; properties->graphicsProperties.maxSwapchainImageWidth = config.eyeWidth; properties->graphicsProperties.maxSwapchainImageHeight = config.eyeHeight; properties->graphicsProperties.maxLayerCount = 1; properties->trackingProperties.orientationTracking = XR_TRUE; properties->trackingProperties.positionTracking = XR_TRUE; return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateViewConfigurations( XrInstance instance, XrSystemId /*systemId*/, uint32_t viewConfigurationTypeCapacityInput, uint32_t * viewConfigurationTypeCountOutput, XrViewConfigurationType * viewConfigurationTypes ) { if ( GetInstance( instance ) == nullptr || viewConfigurationTypeCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } constexpr uint32_t kCount = 1; *viewConfigurationTypeCountOutput = kCount; if ( viewConfigurationTypeCapacityInput == 0 ) { return XR_SUCCESS; } if ( viewConfigurationTypes == nullptr || viewConfigurationTypeCapacityInput < kCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } viewConfigurationTypes[0] = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateViewConfigurationViews( XrInstance instance, XrSystemId /*systemId*/, XrViewConfigurationType viewConfigurationType, uint32_t viewCapacityInput, uint32_t * viewCountOutput, XrViewConfigurationView * views ) { if ( GetInstance( instance ) == nullptr || viewCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( viewConfigurationType != XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO ) { return XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED; } constexpr uint32_t kEyeCount = 2; *viewCountOutput = kEyeCount; if ( viewCapacityInput == 0 ) { return XR_SUCCESS; } if ( views == nullptr || viewCapacityInput < kEyeCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } const SimulatorConfig & config = GetConfig(); for ( uint32_t i = 0; i < kEyeCount; ++i ) { views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; views[i].next = nullptr; views[i].maxImageRectWidth = config.eyeWidth; views[i].maxImageRectHeight = config.eyeHeight; views[i].maxSwapchainSampleCount = 1; views[i].recommendedImageRectWidth = config.eyeWidth; views[i].recommendedImageRectHeight = config.eyeHeight; views[i].recommendedSwapchainSampleCount = 1; } return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateEnvironmentBlendModes( XrInstance instance, XrSystemId /*systemId*/, XrViewConfigurationType viewConfigurationType, uint32_t environmentBlendModeCapacityInput, uint32_t * environmentBlendModeCountOutput, XrEnvironmentBlendMode * environmentBlendModes ) { if ( GetInstance( instance ) == nullptr || environmentBlendModeCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( viewConfigurationType != XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO ) { return XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED; } constexpr uint32_t kCount = 1; *environmentBlendModeCountOutput = kCount; if ( environmentBlendModeCapacityInput == 0 ) { return XR_SUCCESS; } if ( environmentBlendModes == nullptr || environmentBlendModeCapacityInput < kCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } environmentBlendModes[0] = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateSwapchainFormats( XrSession session, uint32_t formatCapacityInput, uint32_t * formatCountOutput, int64_t * formats ) { if ( GetSession( session ) == nullptr || formatCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } static const int64_t kFormats[] = { static_cast( VK_FORMAT_R8G8B8A8_SRGB ), static_cast( VK_FORMAT_R8G8B8A8_UNORM ), }; constexpr uint32_t kCount = sizeof( kFormats ) / sizeof( kFormats[0] ); *formatCountOutput = kCount; if ( formatCapacityInput == 0 ) { return XR_SUCCESS; } if ( formats == nullptr || formatCapacityInput < kCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } for ( uint32_t i = 0; i < kCount; ++i ) { formats[i] = kFormats[i]; } return XR_SUCCESS; } // ---- XR_KHR_vulkan_enable2 -------------------------------------------- XrResult XRAPI_CALL GetVulkanGraphicsRequirements2KHR( XrInstance instance, XrSystemId /*systemId*/, XrGraphicsRequirementsVulkanKHR * graphicsRequirements ) { if ( GetInstance( instance ) == nullptr || graphicsRequirements == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } graphicsRequirements->minApiVersionSupported = XR_MAKE_VERSION( 1, 0, 0 ); graphicsRequirements->maxApiVersionSupported = XR_MAKE_VERSION( 1, 4, 0 ); return XR_SUCCESS; } XrResult XRAPI_CALL CreateVulkanInstanceKHR( XrInstance instance, const XrVulkanInstanceCreateInfoKHR * createInfo, VkInstance * vulkanInstance, VkResult * vulkanResult ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr || createInfo == nullptr || createInfo->pfnGetInstanceProcAddr == nullptr || createInfo->vulkanCreateInfo == nullptr || vulkanInstance == nullptr || vulkanResult == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto vkCreateInstance = reinterpret_cast( createInfo->pfnGetInstanceProcAddr( VK_NULL_HANDLE, "vkCreateInstance" ) ); if ( vkCreateInstance == nullptr ) { return XR_ERROR_GRAPHICS_DEVICE_INVALID; } // The compositor needs surface extensions on the instance; ensure they are present. const VkInstanceCreateInfo & requested = *createInfo->vulkanCreateInfo; std::vector extensions( requested.ppEnabledExtensionNames, requested.ppEnabledExtensionNames + requested.enabledExtensionCount ); auto addUnique = [&extensions]( const char * name ) { if ( std::find( extensions.begin(), extensions.end(), name ) == extensions.end() ) { extensions.push_back( name ); } }; addUnique( VK_KHR_SURFACE_EXTENSION_NAME ); #ifdef _WIN32 addUnique( "VK_KHR_win32_surface" ); #endif VkInstanceCreateInfo augmented = requested; augmented.enabledExtensionCount = static_cast( extensions.size() ); augmented.ppEnabledExtensionNames = extensions.data(); runtime->vkGetInstanceProcAddr = createInfo->pfnGetInstanceProcAddr; *vulkanResult = vkCreateInstance( &augmented, createInfo->vulkanAllocator, vulkanInstance ); if ( *vulkanResult != VK_SUCCESS ) { Log( "vkCreateInstance failed (%d)", static_cast( *vulkanResult ) ); return XR_ERROR_RUNTIME_FAILURE; } runtime->vkInstance = *vulkanInstance; Log( "Created VkInstance on behalf of the application" ); return XR_SUCCESS; } XrResult XRAPI_CALL GetVulkanGraphicsDevice2KHR( XrInstance instance, const XrVulkanGraphicsDeviceGetInfoKHR * getInfo, VkPhysicalDevice * vulkanPhysicalDevice ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr || getInfo == nullptr || vulkanPhysicalDevice == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( runtime->vkGetInstanceProcAddr == nullptr ) { return XR_ERROR_RUNTIME_FAILURE; } auto vkEnumeratePhysicalDevices = reinterpret_cast( runtime->vkGetInstanceProcAddr( getInfo->vulkanInstance, "vkEnumeratePhysicalDevices" ) ); auto vkGetPhysicalDeviceProperties = reinterpret_cast( runtime->vkGetInstanceProcAddr( getInfo->vulkanInstance, "vkGetPhysicalDeviceProperties" ) ); if ( vkEnumeratePhysicalDevices == nullptr ) { return XR_ERROR_RUNTIME_FAILURE; } uint32_t deviceCount = 0; vkEnumeratePhysicalDevices( getInfo->vulkanInstance, &deviceCount, nullptr ); if ( deviceCount == 0 ) { return XR_ERROR_GRAPHICS_DEVICE_INVALID; } std::vector devices( deviceCount ); vkEnumeratePhysicalDevices( getInfo->vulkanInstance, &deviceCount, devices.data() ); VkPhysicalDevice chosen = devices[0]; if ( vkGetPhysicalDeviceProperties != nullptr ) { for ( VkPhysicalDevice device : devices ) { VkPhysicalDeviceProperties props{}; vkGetPhysicalDeviceProperties( device, &props ); if ( props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ) { chosen = device; break; } } } runtime->vkPhysicalDevice = chosen; *vulkanPhysicalDevice = chosen; return XR_SUCCESS; } XrResult XRAPI_CALL CreateVulkanDeviceKHR( XrInstance instance, const XrVulkanDeviceCreateInfoKHR * createInfo, VkDevice * vulkanDevice, VkResult * vulkanResult ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr || createInfo == nullptr || createInfo->vulkanCreateInfo == nullptr || vulkanDevice == nullptr || vulkanResult == nullptr || createInfo->pfnGetInstanceProcAddr == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto vkCreateDevice = reinterpret_cast( createInfo->pfnGetInstanceProcAddr( runtime->vkInstance, "vkCreateDevice" ) ); if ( vkCreateDevice == nullptr ) { return XR_ERROR_GRAPHICS_DEVICE_INVALID; } // The compositor needs VK_KHR_swapchain on the device; ensure it is present. const VkDeviceCreateInfo & requested = *createInfo->vulkanCreateInfo; std::vector extensions( requested.ppEnabledExtensionNames, requested.ppEnabledExtensionNames + requested.enabledExtensionCount ); if ( std::find( extensions.begin(), extensions.end(), VK_KHR_SWAPCHAIN_EXTENSION_NAME ) == extensions.end() ) { extensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); } VkDeviceCreateInfo augmented = requested; augmented.enabledExtensionCount = static_cast( extensions.size() ); augmented.ppEnabledExtensionNames = extensions.data(); *vulkanResult = vkCreateDevice( createInfo->vulkanPhysicalDevice, &augmented, createInfo->vulkanAllocator, vulkanDevice ); if ( *vulkanResult != VK_SUCCESS ) { Log( "vkCreateDevice failed (%d)", static_cast( *vulkanResult ) ); return XR_ERROR_RUNTIME_FAILURE; } runtime->vkPhysicalDevice = createInfo->vulkanPhysicalDevice; runtime->vkDevice = *vulkanDevice; Log( "Created VkDevice on behalf of the application" ); return XR_SUCCESS; } // ---- session ---------------------------------------------------------- XrResult XRAPI_CALL CreateSession( XrInstance instance, const XrSessionCreateInfo * createInfo, XrSession * session ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr || createInfo == nullptr || session == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * newSession = new RuntimeSession(); newSession->instance = runtime; newSession->systemId = createInfo->systemId; newSession->handle = ToHandle( newSession ); newSession->valid = true; newSession->state = XR_SESSION_STATE_IDLE; const SimulatorConfig & config = GetConfig(); g_tracking.ipd = config.ipdMeters; newSession->framePeriodNs = 1000000000LL / ( config.refreshRateHz == 0 ? 90u : config.refreshRateHz ); newSession->nextDisplayTime = NowNs(); if ( const XrGraphicsBindingVulkan2KHR * binding = FindVulkanBinding( createInfo->next ) ) { newSession->vkInstance = binding->instance; newSession->vkPhysicalDevice = binding->physicalDevice; newSession->vkDevice = binding->device; newSession->queueFamilyIndex = binding->queueFamilyIndex; newSession->queueIndex = binding->queueIndex; vkGetDeviceQueue( binding->device, binding->queueFamilyIndex, binding->queueIndex, &newSession->queue ); } if ( newSession->vkDevice != VK_NULL_HANDLE ) { VkCommandPoolCreateInfo poolInfo{ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; poolInfo.queueFamilyIndex = newSession->queueFamilyIndex; vkCreateCommandPool( newSession->vkDevice, &poolInfo, nullptr, &newSession->commandPool ); } { std::lock_guard lock( g_mutex ); g_sessions[newSession->handle] = newSession; runtime->events.clear(); } *session = newSession->handle; Log( "Created XrSession (queue family %u, index %u)", newSession->queueFamilyIndex, newSession->queueIndex ); PushSessionStateEvent( instance, newSession->handle, XR_SESSION_STATE_READY ); if ( runtime->compositor == nullptr ) { runtime->compositor = CreateCompositor(); if ( runtime->compositor && !runtime->compositor->Initialize( runtime, newSession ) ) { Log( "Compositor initialization failed; running without a window" ); runtime->compositor->Shutdown(); runtime->compositor.reset(); } } return XR_SUCCESS; } XrResult XRAPI_CALL DestroySession( XrSession session ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } // Destroy swapchains first. std::vector swapchains = runtime->swapchains; for ( RuntimeSwapchain * swapchain : swapchains ) { DestroySwapchain( swapchain->handle ); } if ( runtime->instance != nullptr && runtime->instance->compositor ) { runtime->instance->compositor->Shutdown(); runtime->instance->compositor.reset(); } if ( runtime->commandPool != VK_NULL_HANDLE ) { vkDestroyCommandPool( runtime->vkDevice, runtime->commandPool, nullptr ); } { std::lock_guard lock( g_mutex ); g_sessions.erase( session ); } delete runtime; Log( "Destroyed XrSession" ); return XR_SUCCESS; } XrResult XRAPI_CALL BeginSession( XrSession session, const XrSessionBeginInfo * /*beginInfo*/ ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } runtime->running = true; runtime->state = XR_SESSION_STATE_SYNCHRONIZED; PushSessionStateEvent( runtime->instance->handle, session, XR_SESSION_STATE_SYNCHRONIZED ); PushSessionStateEvent( runtime->instance->handle, session, XR_SESSION_STATE_VISIBLE ); PushSessionStateEvent( runtime->instance->handle, session, XR_SESSION_STATE_FOCUSED ); runtime->state = XR_SESSION_STATE_FOCUSED; Log( "BeginSession" ); return XR_SUCCESS; } XrResult XRAPI_CALL EndSession( XrSession session ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } runtime->running = false; runtime->state = XR_SESSION_STATE_IDLE; PushSessionStateEvent( runtime->instance->handle, session, XR_SESSION_STATE_STOPPING ); PushSessionStateEvent( runtime->instance->handle, session, XR_SESSION_STATE_IDLE ); Log( "EndSession" ); return XR_SUCCESS; } XrResult XRAPI_CALL PollEvent( XrInstance instance, XrEventDataBuffer * eventData ) { RuntimeInstance * runtime = GetInstance( instance ); if ( runtime == nullptr || eventData == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } std::lock_guard lock( g_mutex ); if ( runtime->events.empty() ) { return XR_EVENT_UNAVAILABLE; } *eventData = runtime->events.front(); runtime->events.pop_front(); return XR_SUCCESS; } XrResult XRAPI_CALL CreateReferenceSpace( XrSession session, const XrReferenceSpaceCreateInfo * createInfo, XrSpace * space ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr || createInfo == nullptr || space == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * newSpace = new RuntimeSpace(); newSpace->session = runtime; newSpace->refType = createInfo->referenceSpaceType; newSpace->handle = ToHandle( newSpace ); newSpace->valid = true; { std::lock_guard lock( g_mutex ); g_spaces[newSpace->handle] = newSpace; } *space = newSpace->handle; return XR_SUCCESS; } XrResult XRAPI_CALL DestroySpace( XrSpace space ) { RuntimeSpace * runtime = GetSpace( space ); if ( runtime == nullptr ) { return XR_SUCCESS; } { std::lock_guard lock( g_mutex ); g_spaces.erase( space ); } delete runtime; return XR_SUCCESS; } // ---- swapchain -------------------------------------------------------- XrResult XRAPI_CALL CreateSwapchain( XrSession session, const XrSwapchainCreateInfo * createInfo, XrSwapchain * swapchain ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr || createInfo == nullptr || swapchain == nullptr || runtime->vkDevice == VK_NULL_HANDLE ) { return XR_ERROR_VALIDATION_FAILURE; } auto * sc = new RuntimeSwapchain(); sc->session = runtime; sc->createInfo = *createInfo; sc->width = createInfo->width; sc->height = createInfo->height; sc->arraySize = createInfo->arraySize == 0 ? 1 : createInfo->arraySize; sc->format = static_cast( createInfo->format ); VkImageCreateInfo imageInfo{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = sc->format; imageInfo.extent = { sc->width, sc->height, 1 }; imageInfo.mipLevels = 1; imageInfo.arrayLayers = sc->arraySize; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; const uint32_t imageCount = std::clamp( GetConfig().swapchainImageCount, 1u, 8u ); for ( uint32_t i = 0; i < imageCount; ++i ) { VkImage image = VK_NULL_HANDLE; if ( vkCreateImage( runtime->vkDevice, &imageInfo, nullptr, &image ) != VK_SUCCESS ) { delete sc; return XR_ERROR_RUNTIME_FAILURE; } VkMemoryRequirements requirements{}; vkGetImageMemoryRequirements( runtime->vkDevice, image, &requirements ); const uint32_t memoryType = FindMemoryType( runtime->vkPhysicalDevice, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ); if ( memoryType == UINT32_MAX ) { vkDestroyImage( runtime->vkDevice, image, nullptr ); delete sc; return XR_ERROR_RUNTIME_FAILURE; } VkMemoryAllocateInfo allocateInfo{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocateInfo.allocationSize = requirements.size; allocateInfo.memoryTypeIndex = memoryType; VkDeviceMemory memory = VK_NULL_HANDLE; if ( vkAllocateMemory( runtime->vkDevice, &allocateInfo, nullptr, &memory ) != VK_SUCCESS || vkBindImageMemory( runtime->vkDevice, image, memory, 0 ) != VK_SUCCESS ) { vkDestroyImage( runtime->vkDevice, image, nullptr ); delete sc; return XR_ERROR_RUNTIME_FAILURE; } sc->images.push_back( image ); sc->memories.push_back( memory ); } // Move all images into COLOR_ATTACHMENT_OPTIMAL (the layout the app expects after acquire). ImmediateSubmit( runtime, [&]( VkCommandBuffer cmd ) { std::vector barriers( imageCount ); for ( uint32_t i = 0; i < imageCount; ++i ) { barriers[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barriers[i].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barriers[i].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barriers[i].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barriers[i].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barriers[i].image = sc->images[i]; barriers[i].subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, sc->arraySize }; barriers[i].srcAccessMask = 0; barriers[i].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; } vkCmdPipelineBarrier( cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, imageCount, barriers.data() ); } ); sc->handle = ToHandle( sc ); { std::lock_guard lock( g_mutex ); g_swapchains[sc->handle] = sc; } runtime->swapchains.push_back( sc ); *swapchain = sc->handle; Log( "Created XrSwapchain %ux%u arraySize=%u format=%d (%u images)", sc->width, sc->height, sc->arraySize, static_cast( sc->format ), imageCount ); return XR_SUCCESS; } XrResult XRAPI_CALL DestroySwapchain( XrSwapchain swapchain ) { RuntimeSwapchain * sc = GetSwapchain( swapchain ); if ( sc == nullptr ) { return XR_ERROR_HANDLE_INVALID; } RuntimeSession * session = sc->session; vkDeviceWaitIdle( session->vkDevice ); for ( size_t i = 0; i < sc->images.size(); ++i ) { vkDestroyImage( session->vkDevice, sc->images[i], nullptr ); vkFreeMemory( session->vkDevice, sc->memories[i], nullptr ); } { std::lock_guard lock( g_mutex ); g_swapchains.erase( swapchain ); auto & list = session->swapchains; list.erase( std::remove( list.begin(), list.end(), sc ), list.end() ); } delete sc; return XR_SUCCESS; } XrResult XRAPI_CALL EnumerateSwapchainImages( XrSwapchain swapchain, uint32_t imageCapacityInput, uint32_t * imageCountOutput, XrSwapchainImageBaseHeader * images ) { RuntimeSwapchain * sc = GetSwapchain( swapchain ); if ( sc == nullptr || imageCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } const uint32_t count = static_cast( sc->images.size() ); *imageCountOutput = count; if ( imageCapacityInput == 0 ) { return XR_SUCCESS; } if ( images == nullptr || imageCapacityInput < count ) { return XR_ERROR_SIZE_INSUFFICIENT; } auto * vkImages = reinterpret_cast( images ); for ( uint32_t i = 0; i < count; ++i ) { vkImages[i].image = sc->images[i]; } return XR_SUCCESS; } XrResult XRAPI_CALL AcquireSwapchainImage( XrSwapchain swapchain, const XrSwapchainImageAcquireInfo * /*acquireInfo*/, uint32_t * index ) { RuntimeSwapchain * sc = GetSwapchain( swapchain ); if ( sc == nullptr || index == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } sc->acquiredIndex = static_cast( sc->nextImageIndex ); sc->nextImageIndex = ( sc->nextImageIndex + 1 ) % static_cast( sc->images.size() ); *index = static_cast( sc->acquiredIndex ); return XR_SUCCESS; } XrResult XRAPI_CALL WaitSwapchainImage( XrSwapchain swapchain, const XrSwapchainImageWaitInfo * /*waitInfo*/ ) { return GetSwapchain( swapchain ) == nullptr ? XR_ERROR_VALIDATION_FAILURE : XR_SUCCESS; } XrResult XRAPI_CALL ReleaseSwapchainImage( XrSwapchain swapchain, const XrSwapchainImageReleaseInfo * /*releaseInfo*/ ) { RuntimeSwapchain * sc = GetSwapchain( swapchain ); if ( sc == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( sc->acquiredIndex >= 0 ) { sc->lastReleasedIndex = sc->acquiredIndex; } sc->acquiredIndex = -1; return XR_SUCCESS; } // ---- frame loop ------------------------------------------------------- XrResult XRAPI_CALL WaitFrame( XrSession session, const XrFrameWaitInfo * /*frameWaitInfo*/, XrFrameState * frameState ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr || frameState == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } const int64_t now = NowNs(); if ( runtime->nextDisplayTime > now ) { std::this_thread::sleep_for( std::chrono::nanoseconds( runtime->nextDisplayTime - now ) ); } runtime->predictedDisplayTime = runtime->nextDisplayTime; runtime->nextDisplayTime += runtime->framePeriodNs; frameState->predictedDisplayTime = runtime->predictedDisplayTime; frameState->predictedDisplayPeriod = runtime->framePeriodNs; frameState->shouldRender = XR_TRUE; // Display work only runs on the window-owning thread; this is a no-op otherwise. if ( runtime->instance != nullptr && runtime->instance->compositor ) { runtime->instance->compositor->Pump( runtime ); } return XR_SUCCESS; } XrResult XRAPI_CALL BeginFrame( XrSession session, const XrFrameBeginInfo * /*frameBeginInfo*/ ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } runtime->frameBegun = true; // The engine calls xrBeginFrame from its main thread, which is the window thread. if ( runtime->instance != nullptr && runtime->instance->compositor ) { runtime->instance->compositor->Pump( runtime ); } return XR_SUCCESS; } XrResult XRAPI_CALL EndFrame( XrSession session, const XrFrameEndInfo * frameEndInfo ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr ) { return XR_ERROR_HANDLE_INVALID; } runtime->frameBegun = false; if ( runtime->instance != nullptr && runtime->instance->compositor ) { const XrCompositionLayerProjection * projectionLayer = nullptr; if ( frameEndInfo != nullptr ) { for ( uint32_t i = 0; i < frameEndInfo->layerCount; ++i ) { const XrCompositionLayerBaseHeader * base = frameEndInfo->layers[i]; if ( base != nullptr && base->type == XR_TYPE_COMPOSITION_LAYER_PROJECTION ) { projectionLayer = reinterpret_cast( base ); break; } } } runtime->instance->compositor->Present( runtime, projectionLayer ); } return XR_SUCCESS; } XrResult XRAPI_CALL LocateViews( XrSession session, const XrViewLocateInfo * /*viewLocateInfo*/, XrViewState * viewState, uint32_t viewCapacityInput, uint32_t * viewCountOutput, XrView * views ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr || viewState == nullptr || viewCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } constexpr uint32_t kEyeCount = 2; *viewCountOutput = kEyeCount; if ( viewCapacityInput == 0 ) { return XR_SUCCESS; } if ( views == nullptr || viewCapacityInput < kEyeCount ) { return XR_ERROR_SIZE_INSUFFICIENT; } const TrackingState & tracking = GetTracking(); const XrQuaternionf orientation = GetHeadOrientation(); const XrVector3f head = GetHeadPosition(); // Right vector = yaw-rotated +X. Eye offset along it. const float rightX = std::cos( tracking.yaw ); const float rightZ = -std::sin( tracking.yaw ); const float halfIpd = tracking.ipd * 0.5f; // XrFovf stores the tangents of the half-angles. const SimulatorConfig & config = GetConfig(); constexpr float deg2rad = kPi / 180.0f; const float tanLeft = std::tan( config.fovLeftDeg * deg2rad ); const float tanRight = std::tan( config.fovRightDeg * deg2rad ); const float tanUp = std::tan( config.fovUpDeg * deg2rad ); const float tanDown = std::tan( config.fovDownDeg * deg2rad ); for ( uint32_t eye = 0; eye < kEyeCount; ++eye ) { const float sign = ( eye == 0 ) ? -1.0f : 1.0f; views[eye].type = XR_TYPE_VIEW; views[eye].next = nullptr; views[eye].pose.orientation = orientation; views[eye].pose.position = XrVector3f{ head.x + sign * halfIpd * rightX, head.y, head.z + sign * halfIpd * rightZ, }; views[eye].fov = XrFovf{ -tanLeft, tanRight, tanUp, -tanDown }; } viewState->viewStateFlags = XR_VIEW_STATE_ORIENTATION_VALID_BIT | XR_VIEW_STATE_POSITION_VALID_BIT | XR_VIEW_STATE_ORIENTATION_TRACKED_BIT | XR_VIEW_STATE_POSITION_TRACKED_BIT; return XR_SUCCESS; } // ---- paths ------------------------------------------------------------ XrResult XRAPI_CALL StringToPath( XrInstance instance, const char * pathString, XrPath * path ) { if ( GetInstance( instance ) == nullptr || pathString == nullptr || path == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } *path = HashPath( pathString ); return XR_SUCCESS; } XrResult XRAPI_CALL PathToString( XrInstance instance, XrPath path, uint32_t bufferCapacityInput, uint32_t * bufferCountOutput, char * buffer ) { if ( GetInstance( instance ) == nullptr || bufferCountOutput == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } std::string value; { std::lock_guard lock( g_mutex ); const auto it = g_pathStrings.find( path ); if ( it != g_pathStrings.end() ) { value = it->second; } } const uint32_t required = static_cast( value.size() + 1 ); *bufferCountOutput = required; if ( bufferCapacityInput == 0 ) { return XR_SUCCESS; } if ( buffer == nullptr || bufferCapacityInput < required ) { return XR_ERROR_SIZE_INSUFFICIENT; } memcpy( buffer, value.c_str(), required ); return XR_SUCCESS; } // ---- actions ---------------------------------------------------------- XrResult XRAPI_CALL CreateActionSet( XrInstance instance, const XrActionSetCreateInfo * createInfo, XrActionSet * actionSet ) { if ( GetInstance( instance ) == nullptr || createInfo == nullptr || actionSet == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * obj = new ActionSetObj(); obj->instance = GetInstance( instance ); const XrActionSet handle = ToHandle( obj ); { std::lock_guard lock( g_mutex ); g_actionSets[handle] = obj; } *actionSet = handle; return XR_SUCCESS; } XrResult XRAPI_CALL DestroyActionSet( XrActionSet actionSet ) { std::lock_guard lock( g_mutex ); const auto it = g_actionSets.find( actionSet ); if ( it == g_actionSets.end() ) { return XR_SUCCESS; } delete it->second; g_actionSets.erase( it ); return XR_SUCCESS; } XrResult XRAPI_CALL CreateAction( XrActionSet actionSet, const XrActionCreateInfo * createInfo, XrAction * action ) { if ( createInfo == nullptr || action == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * obj = new ActionObj(); obj->set = actionSet; obj->type = createInfo->actionType; obj->name = createInfo->actionName; const XrAction handle = ToHandle( obj ); { std::lock_guard lock( g_mutex ); g_actions[handle] = obj; } *action = handle; return XR_SUCCESS; } XrResult XRAPI_CALL DestroyAction( XrAction action ) { std::lock_guard lock( g_mutex ); const auto it = g_actions.find( action ); if ( it == g_actions.end() ) { return XR_SUCCESS; } delete it->second; g_actions.erase( it ); return XR_SUCCESS; } XrResult XRAPI_CALL SuggestInteractionProfileBindings( XrInstance instance, const XrInteractionProfileSuggestedBinding * /*suggestedBindings*/ ) { // Bindings are accepted and ignored; emulated input is provided directly by state queries. return GetInstance( instance ) == nullptr ? XR_ERROR_VALIDATION_FAILURE : XR_SUCCESS; } XrResult XRAPI_CALL AttachSessionActionSets( XrSession session, const XrSessionActionSetsAttachInfo * /*attachInfo*/ ) { return GetSession( session ) == nullptr ? XR_ERROR_HANDLE_INVALID : XR_SUCCESS; } XrResult XRAPI_CALL GetCurrentInteractionProfile( XrSession session, XrPath topLevelUserPath, XrInteractionProfileState * interactionProfile ) { if ( GetSession( session ) == nullptr || interactionProfile == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } // Emulate an Oculus Touch controller for both hands so the app activates that profile and // wires up the aim poses. static const XrPath left = HashPath( "/user/hand/left" ); static const XrPath right = HashPath( "/user/hand/right" ); if ( topLevelUserPath == left || topLevelUserPath == right ) { interactionProfile->interactionProfile = HashPath( "/interaction_profiles/oculus/touch_controller" ); } else { interactionProfile->interactionProfile = XR_NULL_PATH; } return XR_SUCCESS; } XrResult XRAPI_CALL CreateActionSpace( XrSession session, const XrActionSpaceCreateInfo * createInfo, XrSpace * space ) { RuntimeSession * runtime = GetSession( session ); if ( runtime == nullptr || createInfo == nullptr || space == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } auto * newSpace = new RuntimeSpace(); newSpace->session = runtime; newSpace->refType = XR_REFERENCE_SPACE_TYPE_LOCAL; newSpace->actionSpace = true; newSpace->action = createInfo->action; newSpace->subactionPath = createInfo->subactionPath; newSpace->poseInActionSpace = createInfo->poseInActionSpace; newSpace->handle = ToHandle( newSpace ); newSpace->valid = true; { std::lock_guard lock( g_mutex ); g_spaces[newSpace->handle] = newSpace; } *space = newSpace->handle; return XR_SUCCESS; } XrResult XRAPI_CALL LocateSpace( XrSpace space, XrSpace /*baseSpace*/, XrTime /*time*/, XrSpaceLocation * location ) { RuntimeSpace * spaceState = GetSpace( space ); if ( spaceState == nullptr || location == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } if ( spaceState->actionSpace ) { int hand = HandFromSubaction( spaceState->subactionPath ); if ( hand < 0 ) { hand = 0; } const ActionObj * action = GetAction( spaceState->action ); const ControlKind kind = action != nullptr ? ClassifyAction( action->name, action->type ) : ControlKind::Other; location->pose = ( kind == ControlKind::PoseGrip ) ? g_input.hands[hand].gripPose : g_input.hands[hand].aimPose; } else { location->pose.orientation = XrQuaternionf{ 0.0f, 0.0f, 0.0f, 1.0f }; location->pose.position = XrVector3f{ 0.0f, 0.0f, 0.0f }; } location->locationFlags = XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_POSITION_TRACKED_BIT | XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT; return XR_SUCCESS; } XrResult XRAPI_CALL SyncActions( XrSession session, const XrActionsSyncInfo * /*syncInfo*/ ) { return GetSession( session ) == nullptr ? XR_ERROR_HANDLE_INVALID : XR_SUCCESS; } XrResult XRAPI_CALL GetActionStateBoolean( XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateBoolean * state ) { if ( GetSession( session ) == nullptr || getInfo == nullptr || state == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } int hand = HandFromSubaction( getInfo->subactionPath ); if ( hand < 0 ) { hand = 0; } const ActionObj * action = GetAction( getInfo->action ); const ControlKind kind = action != nullptr ? ClassifyAction( action->name, action->type ) : ControlKind::Other; const ControllerState & controller = g_input.hands[hand]; bool value = false; switch ( kind ) { case ControlKind::Trigger: value = controller.trigger > 0.5f; break; case ControlKind::Grip: value = controller.grip > 0.5f; break; case ControlKind::StickClick: value = controller.stickClick; break; case ControlKind::ButtonA: value = controller.a; break; case ControlKind::ButtonB: value = controller.b; break; case ControlKind::ButtonX: value = controller.x; break; case ControlKind::ButtonY: value = controller.y; break; case ControlKind::Menu: value = controller.menu; break; case ControlKind::Touch: value = controller.touch; break; default: break; } state->isActive = XR_TRUE; state->currentState = value ? XR_TRUE : XR_FALSE; state->changedSinceLastSync = XR_FALSE; state->lastChangeTime = 0; return XR_SUCCESS; } XrResult XRAPI_CALL GetActionStateFloat( XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateFloat * state ) { if ( GetSession( session ) == nullptr || getInfo == nullptr || state == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } int hand = HandFromSubaction( getInfo->subactionPath ); if ( hand < 0 ) { hand = 0; } const ActionObj * action = GetAction( getInfo->action ); const ControlKind kind = action != nullptr ? ClassifyAction( action->name, action->type ) : ControlKind::Other; const ControllerState & controller = g_input.hands[hand]; float value = 0.0f; if ( kind == ControlKind::Trigger ) { value = controller.trigger; } else if ( kind == ControlKind::Grip ) { value = controller.grip; } state->isActive = XR_TRUE; state->currentState = value; state->changedSinceLastSync = XR_FALSE; state->lastChangeTime = 0; return XR_SUCCESS; } XrResult XRAPI_CALL GetActionStateVector2f( XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateVector2f * state ) { if ( GetSession( session ) == nullptr || getInfo == nullptr || state == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } int hand = HandFromSubaction( getInfo->subactionPath ); if ( hand < 0 ) { hand = 0; } const ActionObj * action = GetAction( getInfo->action ); const ControlKind kind = action != nullptr ? ClassifyAction( action->name, action->type ) : ControlKind::Other; const ControllerState & controller = g_input.hands[hand]; state->isActive = XR_TRUE; state->currentState = kind == ControlKind::Thumbstick ? controller.thumbstick : XrVector2f{ 0.0f, 0.0f }; state->changedSinceLastSync = XR_FALSE; state->lastChangeTime = 0; return XR_SUCCESS; } XrResult XRAPI_CALL GetActionStatePose( XrSession session, const XrActionStateGetInfo * /*getInfo*/, XrActionStatePose * state ) { if ( GetSession( session ) == nullptr || state == nullptr ) { return XR_ERROR_VALIDATION_FAILURE; } state->isActive = XR_TRUE; return XR_SUCCESS; } XrResult XRAPI_CALL ApplyHapticFeedback( XrSession session, const XrHapticActionInfo * /*hapticActionInfo*/, const XrHapticBaseHeader * /*hapticFeedback*/ ) { return GetSession( session ) == nullptr ? XR_ERROR_HANDLE_INVALID : XR_SUCCESS; } XrResult XRAPI_CALL StopHapticFeedback( XrSession session, const XrHapticActionInfo * /*hapticActionInfo*/ ) { return GetSession( session ) == nullptr ? XR_ERROR_HANDLE_INVALID : XR_SUCCESS; } } // namespace xrsim