1738 lines
63 KiB
C++
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
|