Files
xr-simulator/src/Runtime.cpp
T

1738 lines
63 KiB
C++

#include "Common.h"
#include "Compositor.h"
#include "Config.h"
#include "Runtime.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <functional>
#include <thread>
#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<XrInstance, xrsim::RuntimeInstance *> g_instances;
std::unordered_map<XrSession, xrsim::RuntimeSession *> g_sessions;
std::unordered_map<XrSwapchain, xrsim::RuntimeSwapchain *> g_swapchains;
std::unordered_map<XrSpace, xrsim::RuntimeSpace *> 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<XrActionSet, ActionSetObj *> g_actionSets;
std::unordered_map<XrAction, ActionObj *> g_actions;
ActionObj * GetAction( XrAction handle )
{
std::lock_guard<std::mutex> 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<XrPath, std::string> 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::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()
)
.count();
}
template <typename Handle, typename Object>
Handle ToHandle( Object * object )
{
return reinterpret_cast<Handle>( 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<const XrBaseInStructure *>( node );
if ( base->type == XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR )
{
return reinterpret_cast<const XrGraphicsBindingVulkan2KHR *>( 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<void( VkCommandBuffer )> & 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<uint8_t>( *c );
hash *= 1099511628211ULL;
}
if ( hash == XR_NULL_PATH )
{
hash = 1;
}
if ( path != nullptr )
{
std::lock_guard<std::mutex> lock( g_mutex );
g_pathStrings[hash] = path;
}
return hash;
}
RuntimeInstance * GetInstance( XrInstance handle )
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<uint32_t>( 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<uint32_t>( subImage.imageRect.extent.width );
*outHeight = static_cast<uint32_t>( 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<XrEventDataSessionStateChanged *>( &buffer );
event->session = session;
event->state = state;
event->time = NowNs();
std::lock_guard<std::mutex> 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<XrInstance>( runtime );
runtime->valid = true;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<int64_t>( VK_FORMAT_R8G8B8A8_SRGB ),
static_cast<int64_t>( 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<PFN_vkCreateInstance>(
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<const char *> 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<uint32_t>( 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<int>( *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<PFN_vkEnumeratePhysicalDevices>(
runtime->vkGetInstanceProcAddr( getInfo->vulkanInstance, "vkEnumeratePhysicalDevices" )
);
auto vkGetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
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<VkPhysicalDevice> 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<PFN_vkCreateDevice>(
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<const char *> 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<uint32_t>( extensions.size() );
augmented.ppEnabledExtensionNames = extensions.data();
*vulkanResult = vkCreateDevice(
createInfo->vulkanPhysicalDevice, &augmented, createInfo->vulkanAllocator, vulkanDevice
);
if ( *vulkanResult != VK_SUCCESS )
{
Log( "vkCreateDevice failed (%d)", static_cast<int>( *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<XrSession>( 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<std::mutex> 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<RuntimeSwapchain *> 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<std::mutex> 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<std::mutex> 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<XrSpace>( newSpace );
newSpace->valid = true;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<VkFormat>( 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<VkImageMemoryBarrier> 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<XrSwapchain>( sc );
{
std::lock_guard<std::mutex> 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<int>( 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<std::mutex> 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<uint32_t>( 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<XrSwapchainImageVulkan2KHR *>( 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<int32_t>( sc->nextImageIndex );
sc->nextImageIndex = ( sc->nextImageIndex + 1 ) % static_cast<uint32_t>( sc->images.size() );
*index = static_cast<uint32_t>( 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<const XrCompositionLayerProjection *>( 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<std::mutex> lock( g_mutex );
const auto it = g_pathStrings.find( path );
if ( it != g_pathStrings.end() )
{
value = it->second;
}
}
const uint32_t required = static_cast<uint32_t>( 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<XrActionSet>( obj );
{
std::lock_guard<std::mutex> lock( g_mutex );
g_actionSets[handle] = obj;
}
*actionSet = handle;
return XR_SUCCESS;
}
XrResult XRAPI_CALL DestroyActionSet( XrActionSet actionSet )
{
std::lock_guard<std::mutex> 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<XrAction>( obj );
{
std::lock_guard<std::mutex> lock( g_mutex );
g_actions[handle] = obj;
}
*action = handle;
return XR_SUCCESS;
}
XrResult XRAPI_CALL DestroyAction( XrAction action )
{
std::lock_guard<std::mutex> 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<XrSpace>( newSpace );
newSpace->valid = true;
{
std::lock_guard<std::mutex> 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