#include "ixr.h" #include "openxr/openxr.h" #include "vulkan/vulkan_core.h" #undef __cplusplus #include "vulkan/vk_enum_string_helper.h" #define __cplusplus 202400L #define XR_USE_GRAPHICS_API_VULKAN #include "openxr/openxr_platform.h" #include "tier0/lib.h" #include "tier1/utlvector.h" #include "materialsystem/imaterialsystem.h" #include "tier1/utlstring.h" #include "tier2/fileformats/json.h" #include "tier2/ifilesystem.h" static XrPath OpenXrGetPath( XrInstance i, const char *szString ) { XrPath xrPath; xrStringToPath(i, szString, &xrPath); return xrPath; } static CUtlString OpenXrGetString( XrInstance i, XrPath path ) { uint32_t l; char t[XR_MAX_PATH_LENGTH]; XrResult r = xrPathToString(i, path, XR_MAX_PATH_LENGTH, &l, t); if (r) return NULL; return t; } struct EyeSwapchain_t { XrSwapchain m_swapchain; uint32_t m_uWidth; uint32_t m_uHeight; uint32_t m_uImageIndex; CUtlVector m_pImages; IImage *m_pUserImage; }; class COpenXRManager; class COpenXRHeadset; struct OpenXRAction { CUtlString m_szName; XrAction m_action; XrActionType m_eType; XrSpace m_space; EXRInputType_t m_eUserInputType; }; class COpenXRController: public IXRController { public: virtual EXRControllerType_t GetControllerType() override; virtual EXRControllerSide_t GetControllerSide() override; virtual IXRHeadset *GetHeadset() override; virtual void SetInputCallback( XRInputCallbackFn fn ) override; void Frame(); void ConfigureActionSets( const char *szDevice, const char *szProfile ); COpenXRHeadset *m_pHeadset; EXRControllerType_t m_type; EXRControllerSide_t m_side; CUtlVector m_actions; XrActionSet m_actionSet = XR_NULL_HANDLE; XRInputCallbackFn m_callback = NULL; }; class COpenXRHeadset: public IXRHeadset, public IAppSystem { public: virtual void Init() override; virtual void Shutdown() override; virtual uint32_t GetSurfaceCount() override; virtual XRRenderSurface_t GetSurface( uint32_t i ) override; virtual void SetSurfaceImage( uint32_t i, IImage *pImage ) override; virtual uint32_t GetControllerCount() override; virtual IXRController *GetController( uint32_t i ) override; void Frame(); void BeforeRender(); void AfterRender(); void CopySwapchain(); void CreateSwapchainForEye( XrViewConfigurationView *pConf, VkFormat eVkFormat, EyeSwapchain_t *pEye ); COpenXRManager *m_pManager; XrSystemId m_systemId; XrSession m_session; XrSpace m_space; CUtlVector m_eyeSwapchains = {}; CUtlVector m_projviews = {}; CUtlVector m_views = {}; float m_fPredictedTime; COpenXRController *m_pLeftHand; COpenXRController *m_pRightHand; }; enum EOpenXRAPI { OPENXR_API_VULKAN, OPENXR_API_VULKAN2, }; class COpenXRManager: public IXRManager { public: virtual void Init() override; virtual void Shutdown() override; virtual void ConnectInterface( void *pIface, const char *szName ) override; virtual void Frame() override; virtual void PreRender() override; virtual void PostRender() override; virtual void CopySwapchain() override; virtual uint32_t GetHeadsetCount() override; virtual IXRHeadset *GetHeadset( uint32_t i ) override; bool bHasXR = false; IRenderContext *m_pRenderContext; COpenXRHeadset m_headset; XrInstance m_instance; bool m_bIsDebugUtilsSupported = false; bool m_bIsVulkanSupported = false; bool m_bIsVulkan2Supported = false; EOpenXRAPI m_gpuAPI; }; EXPOSE_INTERFACE(COpenXRManager, IXRManager, OPEN_XR_INTERFACE_VERSION); EXRControllerType_t COpenXRController::GetControllerType() { return m_type; } EXRControllerSide_t COpenXRController::GetControllerSide() { return m_side; } IXRHeadset *COpenXRController::GetHeadset() { return m_pHeadset; } void COpenXRController::SetInputCallback( XRInputCallbackFn fn ) { m_callback = fn; } void COpenXRController::Frame() { for (auto &a: m_actions) { switch(a.m_eType) { case XR_ACTION_TYPE_BOOLEAN_INPUT: { XrActionStateGetInfo gi = {}; gi.type = XR_TYPE_ACTION_STATE_GET_INFO; gi.action = a.m_action; XrActionStateBoolean b = {}; b.type = XR_TYPE_ACTION_STATE_BOOLEAN; xrGetActionStateBoolean(m_pHeadset->m_session, &gi, &b); EXRInputValue_t v; v.type = k_EXRValue_Bool; v.b.value = b.currentState; if (m_callback) m_callback( this, a.m_eUserInputType, NULL, k_EXRInputAction_Value, v ); break; } case XR_ACTION_TYPE_FLOAT_INPUT: { XrActionStateGetInfo gi = {}; gi.type = XR_TYPE_ACTION_STATE_GET_INFO; gi.action = a.m_action; XrActionStateFloat f32 = {}; f32.type = XR_TYPE_ACTION_STATE_FLOAT; xrGetActionStateFloat(m_pHeadset->m_session, &gi, &f32); EXRInputValue_t v; v.type = k_EXRValue_Float; v.f32.value = f32.currentState; if (m_callback) m_callback( this, a.m_eUserInputType, NULL, k_EXRInputAction_Value, v ); break; } case XR_ACTION_TYPE_POSE_INPUT: { XrActionStateGetInfo gi = {}; gi.type = XR_TYPE_ACTION_STATE_GET_INFO; gi.action = a.m_action; XrActionStatePose pose = {}; pose.type = XR_TYPE_ACTION_STATE_POSE; xrGetActionStatePose(m_pHeadset->m_session, &gi, &pose); if (pose.isActive) { XrSpaceLocation s = {}; s.type = XR_TYPE_SPACE_LOCATION; xrLocateSpace(a.m_space, m_pHeadset->m_space, m_pHeadset->m_fPredictedTime, &s); EXRInputValue_t v; v.type = k_EXRValue_Pose; v.pose.pos = {s.pose.position.x, s.pose.position.y, s.pose.position.z}; v.pose.rot = {s.pose.orientation.x, s.pose.orientation.y, s.pose.orientation.z, s.pose.orientation.w}; if (m_callback) m_callback( this, a.m_eUserInputType, NULL, k_EXRInputAction_Pose, v ); } break; } default: break; } }; } void COpenXRController::ConfigureActionSets( const char *szDevice, const char *szProfile ) { XrActionSetCreateInfo setci = {}; setci.type = XR_TYPE_ACTION_SET_CREATE_INFO; strcpy(setci.actionSetName, "funny"); strcpy(setci.localizedActionSetName, "funny"); xrCreateActionSet(m_pHeadset->m_pManager->m_instance, &setci, &m_actionSet); XrSessionActionSetsAttachInfo attachi = {}; attachi.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO; attachi.actionSets = &m_actionSet; attachi.countActionSets = 1; xrAttachSessionActionSets(m_pHeadset->m_session, &attachi); CUtlString szPath = CUtlString("game/core/openxr%s.json", szProfile); IFileHandle *pHandle = filesystem->Open(szPath, FILEMODE_READ); const char *sz = filesystem->ReadString(pHandle); IJSONValue *pVal = JSONManager()->ReadString(sz); IJSONValue *pDevice = (*pVal)[szDevice]; IJSONValue *pInputs = (*pDevice)["inputs"]; if (!pInputs) return; for (uint32_t i = 0; i < pInputs->GetArray()->GetCount(); i++ ) { IJSONValue *pInput = (*pInputs)[i]; CUtlString szInputName = (*pInput)["name"]->GetStringValue(); EXRInputType_t eInputType = k_EXRInput_FromString; if (szInputName == "trackpad") eInputType = k_EXRInput_Trackpad; if (szInputName == "thumbstick") eInputType = k_EXRInput_Thumbstick; if (szInputName == "joystick") eInputType = k_EXRInput_Joystick; if (szInputName == "trigger") eInputType = k_EXRInput_Trigger; if (szInputName == "throttle") eInputType = k_EXRInput_Throttle; if (szInputName == "trackball") eInputType = k_EXRInput_Trackball; if (szInputName == "pedal") eInputType = k_EXRInput_Pedal; if (szInputName == "system") eInputType = k_EXRInput_System; if (szInputName == "dpad_up") eInputType = k_EXRInput_Dpad_Up; if (szInputName == "dpad_down") eInputType = k_EXRInput_Dpad_Down; if (szInputName == "dpad_left") eInputType = k_EXRInput_Dpad_Left; if (szInputName == "dpad_right") eInputType = k_EXRInput_Dpad_Right; if (szInputName == "diamond_up") eInputType = k_EXRInput_Diamond_Up; if (szInputName == "diamond_down") eInputType = k_EXRInput_Diamond_Down; if (szInputName == "diamond_left") eInputType = k_EXRInput_Diamond_Left; if (szInputName == "diamond_right") eInputType = k_EXRInput_Diamond_Right; if (szInputName == "a") eInputType = k_EXRInput_A; if (szInputName == "b") eInputType = k_EXRInput_B; if (szInputName == "x") eInputType = k_EXRInput_X; if (szInputName == "y") eInputType = k_EXRInput_Y; if (szInputName == "start") eInputType = k_EXRInput_Start; if (szInputName == "home") eInputType = k_EXRInput_Home; if (szInputName == "end") eInputType = k_EXRInput_End; if (szInputName == "select") eInputType = k_EXRInput_Select; if (szInputName == "volume_up") eInputType = k_EXRInput_Volume_Up; if (szInputName == "volume_down") eInputType = k_EXRInput_Volume_Down; if (szInputName == "mute_mic") eInputType = k_EXRInput_Mute_Mic; if (szInputName == "play_pause") eInputType = k_EXRInput_Play_Pause; if (szInputName == "menu") eInputType = k_EXRInput_Menu; if (szInputName == "view") eInputType = k_EXRInput_View; if (szInputName == "back") eInputType = k_EXRInput_Back; if (szInputName == "thumbrest") eInputType = k_EXRInput_ThumbRest; if (szInputName == "shoulder") eInputType = k_EXRInput_Shoulder; if (szInputName == "squeeze") eInputType = k_EXRInput_Squeeze; if (szInputName == "wheel") eInputType = k_EXRInput_Wheel; if (szInputName == "grip") eInputType = k_EXRInput_Grip; if (szInputName == "aim") eInputType = k_EXRInput_Aim; IJSONValue *pActions = (*pInput)["actions"]; for (uint32_t y = 0; y < pActions->GetArray()->GetCount(); y++ ) { IJSONValue *pAction = (*pActions)[y]; CUtlString szType = pAction->GetStringValue(); XrActionType eType = XR_ACTION_TYPE_MAX_ENUM; if (szType == "click") eType = XR_ACTION_TYPE_BOOLEAN_INPUT; if (szType == "touch") eType = XR_ACTION_TYPE_BOOLEAN_INPUT; if (szType == "force") eType = XR_ACTION_TYPE_FLOAT_INPUT; if (szType == "value") eType = XR_ACTION_TYPE_FLOAT_INPUT; if (szType == "x") eType = XR_ACTION_TYPE_FLOAT_INPUT; if (szType == "y") eType = XR_ACTION_TYPE_FLOAT_INPUT; if (szType == "twist") eType = XR_ACTION_TYPE_FLOAT_INPUT; if (szType == "pose") eType = XR_ACTION_TYPE_POSE_INPUT; XrAction a = XR_NULL_HANDLE; XrActionCreateInfo aci = {}; aci.type = XR_TYPE_ACTION_CREATE_INFO; aci.actionType = eType; CUtlString szName = CUtlString("%s/input/%s/%s", szDevice, szInputName.GetString(), szType.GetString()); V_strncpy(aci.actionName, szName.GetString(), XR_MAX_ACTION_NAME_SIZE); V_strncpy(aci.localizedActionName, szName.GetString(), XR_MAX_LOCALIZED_ACTION_NAME_SIZE); XrResult r = xrCreateAction(m_actionSet, &aci, &a); OpenXRAction action = {}; action.m_szName = szName; action.m_action = a; action.m_eType = eType; action.m_eUserInputType = eInputType; if (eType == XR_ACTION_TYPE_POSE_INPUT) { XrActionSpaceCreateInfo asci = {}; asci.type = XR_TYPE_ACTION_SPACE_CREATE_INFO; asci.poseInActionSpace = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}}; asci.action = a; XrResult r = xrCreateActionSpace(m_pHeadset->m_session, &asci, &action.m_space); } m_actions.AppendTail(action); } } CUtlVector suggestions = {}; suggestions.Reserve(m_actions.GetSize()); for ( auto &a: m_actions ) { suggestions.AppendTail({a.m_action, OpenXrGetPath(m_pHeadset->m_pManager->m_instance, a.m_szName)}); } XrInteractionProfileSuggestedBinding binding = {}; binding.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; binding.interactionProfile = OpenXrGetPath(m_pHeadset->m_pManager->m_instance, szProfile); binding.countSuggestedBindings = suggestions.GetSize(); binding.suggestedBindings = suggestions.GetData(); xrSuggestInteractionProfileBindings(m_pHeadset->m_pManager->m_instance, &binding); JSONManager()->FreeValue(pVal); V_free((void*)sz); } void COpenXRManager::Init() { PFN_xrInitializeLoaderKHR fnInitLoader = NULL; XrResult r = xrGetInstanceProcAddr(NULL, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&fnInitLoader); if (r != XR_SUCCESS) { V_printf("Failed to load OpenXR, we are not running OpenXR\n"); return; } uint32_t nInstanceExtension = 0; xrEnumerateInstanceExtensionProperties(NULL, 0, &nInstanceExtension, NULL); CUtlVector availableExts(nInstanceExtension); for (auto &e: availableExts) e.type = XR_TYPE_EXTENSION_PROPERTIES; xrEnumerateInstanceExtensionProperties(NULL, nInstanceExtension, &nInstanceExtension, availableExts.GetData()); CUtlVector extensions = {}; for ( auto &e: availableExts ) { if (!V_strncmp(XR_EXT_DEBUG_UTILS_EXTENSION_NAME, e.extensionName, XR_MAX_EXTENSION_NAME_SIZE)) { extensions.AppendTail(XR_EXT_DEBUG_UTILS_EXTENSION_NAME); m_bIsDebugUtilsSupported = false; } if (!V_strncmp(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME, e.extensionName, XR_MAX_EXTENSION_NAME_SIZE)) { extensions.AppendTail(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME); m_bIsVulkanSupported = true; } if (!V_strncmp(XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME, e.extensionName, XR_MAX_EXTENSION_NAME_SIZE)) { extensions.AppendTail(XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME); m_bIsVulkan2Supported = true; } } if (m_pRenderContext->GetVulkanInstance() != NULL) { if (m_bIsVulkan2Supported) { if (m_bIsVulkanSupported) m_gpuAPI = OPENXR_API_VULKAN; else m_gpuAPI = OPENXR_API_VULKAN2; } if (m_bIsVulkanSupported) { m_gpuAPI = OPENXR_API_VULKAN; } } for (auto &e: extensions) V_printf("%s\n", e); const char *layers[] = { "XR_APILAYER_LUNARG_core_validation" }; XrApplicationInfo appInfo = {}; appInfo.apiVersion = XR_API_VERSION_1_1; V_strncpy(appInfo.applicationName, "funny", XR_MAX_APPLICATION_NAME_SIZE); V_strncpy(appInfo.engineName, "funny", XR_MAX_ENGINE_NAME_SIZE); XrInstanceCreateInfo createInfo = {}; createInfo.type = XR_TYPE_INSTANCE_CREATE_INFO; createInfo.applicationInfo = appInfo; createInfo.enabledExtensionCount = extensions.GetSize(); createInfo.enabledExtensionNames = extensions.GetData(); createInfo.enabledApiLayerCount = sizeof(layers)/sizeof(*layers); createInfo.enabledApiLayerNames = layers; r = xrCreateInstance(&createInfo, &m_instance); if (r != XR_SUCCESS) { V_printf("xrCreateInstance failed, we are not running OpenXR\n"); return; } m_headset.m_pManager = this; m_headset.Init(); XrInstanceProperties props = {}; props.type = XR_TYPE_INSTANCE_PROPERTIES; xrGetInstanceProperties(m_instance, &props); V_printf("OpenXR runtime: %s\n", props.runtimeName); bHasXR = true; } void COpenXRManager::Shutdown() { } void COpenXRManager::Frame() { if (bHasXR) m_headset.Frame(); } void COpenXRManager::PreRender() { if (bHasXR) m_headset.BeforeRender(); } void COpenXRManager::PostRender() { if (bHasXR) m_headset.AfterRender(); } void COpenXRManager::CopySwapchain() { if (bHasXR) m_headset.CopySwapchain(); } struct OpenXRProfile_t { const char *m_szName; EXRControllerType_t m_eType; }; const static CUtlVector s_interactionProfiles = { {"/interaction_profiles/khr/simple_controller", k_EXR_HandController}, {"/interaction_profiles/oculus/touch_controller", k_EXR_HandController}, }; void COpenXRHeadset::Init() { XrSystemGetInfo sgi = {}; XrResult r; sgi.type = XR_TYPE_SYSTEM_GET_INFO; sgi.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; r = xrGetSystem(m_pManager->m_instance, &sgi, &m_systemId); if (r != XR_SUCCESS) Plat_FatalErrorFunc("xrGetSystem\n"); /* each backend requires its own shit */ switch (m_pManager->m_gpuAPI) { case OPENXR_API_VULKAN: { XrGraphicsRequirementsVulkanKHR requirements = {}; requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR; PFN_xrGetVulkanGraphicsRequirementsKHR fnVulkanRequirements = NULL; xrGetInstanceProcAddr(m_pManager->m_instance, "xrGetVulkanGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&fnVulkanRequirements); if (r != XR_SUCCESS) Plat_FatalErrorFunc("Failed to get xrGetVulkanGraphicsRequirementsKHR\n"); fnVulkanRequirements( m_pManager->m_instance, m_systemId, &requirements); PFN_xrGetVulkanGraphicsDeviceKHR fnVulkanDevice = NULL; xrGetInstanceProcAddr(m_pManager->m_instance, "xrGetVulkanGraphicsDeviceKHR", (PFN_xrVoidFunction*)&fnVulkanDevice); if (r != XR_SUCCESS) Plat_FatalErrorFunc("Failed to get xrGetVulkanGraphicsDeviceKHR\n"); VkPhysicalDevice d = NULL; fnVulkanDevice(m_pManager->m_instance, m_systemId, (VkInstance)m_pManager->m_pRenderContext->GetVulkanInstance(), &d); XrGraphicsBindingVulkan2KHR vkBinding = {}; vkBinding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR; vkBinding.instance = (VkInstance)m_pManager->m_pRenderContext->GetVulkanInstance(); vkBinding.physicalDevice = (VkPhysicalDevice)m_pManager->m_pRenderContext->GetVulkanPhysicalDevice(); vkBinding.device = (VkDevice)m_pManager->m_pRenderContext->GetVulkanDevice(); XrSessionCreateInfo sessionCreateInfo = {}; sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO; sessionCreateInfo.next = &vkBinding; sessionCreateInfo.systemId = m_systemId; r = xrCreateSession(m_pManager->m_instance, &sessionCreateInfo, &m_session); if (r != XR_SUCCESS) Plat_FatalErrorFunc("xrCreateSession\n"); } break; case OPENXR_API_VULKAN2: { XrGraphicsRequirementsVulkanKHR requirements = {}; requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR; PFN_xrGetVulkanGraphicsRequirements2KHR fnVulkanRequirements = NULL; xrGetInstanceProcAddr(m_pManager->m_instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction*)&fnVulkanRequirements); if (r != XR_SUCCESS) Plat_FatalErrorFunc("Failed to get xrGetVulkanGraphicsRequirements2KHR\n"); fnVulkanRequirements( m_pManager->m_instance, m_systemId, &requirements); PFN_xrGetVulkanGraphicsDevice2KHR fnVulkanDevice = NULL; xrGetInstanceProcAddr(m_pManager->m_instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&fnVulkanDevice); if (r != XR_SUCCESS) Plat_FatalErrorFunc("Failed to get xrGetVulkanGraphicsDevice2KHR\n"); VkPhysicalDevice d = NULL; XrVulkanGraphicsDeviceGetInfoKHR dgi = {}; dgi.type = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR; dgi.systemId = m_systemId; dgi.vulkanInstance = (VkInstance)m_pManager->m_pRenderContext->GetVulkanInstance(); fnVulkanDevice(m_pManager->m_instance, &dgi, &d); XrGraphicsBindingVulkan2KHR vkBinding = {}; vkBinding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR; vkBinding.instance = (VkInstance)m_pManager->m_pRenderContext->GetVulkanInstance(); vkBinding.physicalDevice = (VkPhysicalDevice)m_pManager->m_pRenderContext->GetVulkanPhysicalDevice(); vkBinding.device = (VkDevice)m_pManager->m_pRenderContext->GetVulkanDevice(); XrSessionCreateInfo sessionCreateInfo = {}; sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO; sessionCreateInfo.next = &vkBinding; sessionCreateInfo.systemId = m_systemId; r = xrCreateSession(m_pManager->m_instance, &sessionCreateInfo, &m_session); if (r != XR_SUCCESS) Plat_FatalErrorFunc("xrCreateSession\n"); } break; default: break; } /* create swapchains for rendering pipeline */ uint32_t formatCount = 0; xrEnumerateSwapchainFormats(m_session, 0, &formatCount, NULL); CUtlVector formats = formatCount; xrEnumerateSwapchainFormats(m_session, formatCount, &formatCount, formats.GetData()); const VkFormat preferedSurfaceFormats[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB, }; VkFormat eSelectedFormat = preferedSurfaceFormats[0]; for (auto &format: formats) { for (int i = 0; i < sizeof(preferedSurfaceFormats)/sizeof(VkFormat); i++) { if (format == preferedSurfaceFormats[i]) { eSelectedFormat = preferedSurfaceFormats[i]; goto formatPicked; } } } formatPicked: uint32_t viewCount = 0; xrEnumerateViewConfigurationViews(m_pManager->m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, NULL); CUtlVector views = viewCount; for ( auto &v: views ) { v.type = XR_TYPE_VIEW_CONFIGURATION_VIEW; } xrEnumerateViewConfigurationViews(m_pManager->m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, views.GetData()); for ( auto &v: views ) { m_eyeSwapchains.AppendTail((EyeSwapchain_t){}); CreateSwapchainForEye(&v, eSelectedFormat, m_eyeSwapchains.GetData() + m_eyeSwapchains.GetSize() - 1); } XrReferenceSpaceCreateInfo spaceInfo = {}; spaceInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; spaceInfo.poseInReferenceSpace = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}}; xrCreateReferenceSpace(m_session, &spaceInfo, &m_space); m_fPredictedTime = 0; m_views = m_eyeSwapchains.GetSize(); m_projviews = m_eyeSwapchains.GetSize(); for ( auto &v: m_views ) { v = {}; v.type = XR_TYPE_VIEW; } for ( auto &v: m_projviews ) { v = {}; v.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; } XrViewState viewState = {}; viewState.type = XR_TYPE_VIEW_STATE; XrViewLocateInfo viewLocale = {}; viewLocale.type = XR_TYPE_VIEW_LOCATE_INFO; viewLocale.displayTime = m_fPredictedTime; viewLocale.space = m_space; viewLocale.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; uint32_t vc = m_views.GetSize(); xrLocateViews(m_session, &viewLocale, &viewState, vc, &vc, m_views.GetData()); for (auto &p: s_interactionProfiles) { /* load profile */ XrInteractionProfileSuggestedBinding bindings = {}; bindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; bindings.interactionProfile = OpenXrGetPath(m_pManager->m_instance, p.m_szName); xrSuggestInteractionProfileBindings(m_pManager->m_instance, &bindings); } CUtlVector possibleControllers = { "/user/hand/left", "/user/hand/right", }; XrActionSet quetySet = {}; XrActionSetCreateInfo setci = {}; setci.type = XR_TYPE_ACTION_SET_CREATE_INFO; strcpy(setci.actionSetName, "funny"); strcpy(setci.localizedActionSetName, "funny"); xrCreateActionSet(m_pManager->m_instance, &setci, &quetySet); XrSessionActionSetsAttachInfo attachi = {}; attachi.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO; attachi.actionSets = &quetySet; attachi.countActionSets = 1; xrAttachSessionActionSets(m_session, &attachi); for ( auto &c: possibleControllers ) { XrPath path = OpenXrGetPath(m_pManager->m_instance, c); XrInteractionProfileState profile = {}; profile.type = XR_TYPE_INTERACTION_PROFILE_STATE; XrResult r = xrGetCurrentInteractionProfile(m_session, path, &profile); if (r) { V_printf("Controller not found: %s %i\n", c, r); } if (profile.interactionProfile == XR_NULL_PATH) continue; CUtlString s = OpenXrGetString(m_pManager->m_instance, profile.interactionProfile); if (!V_strcmp(c, "/user/hand/left")) { m_pLeftHand = new COpenXRController; m_pLeftHand->m_side = k_EXRController_Left; m_pLeftHand->m_pHeadset = this; for ( auto &p: s_interactionProfiles ) if (s == p.m_szName) { m_pLeftHand->m_type = p.m_eType; m_pLeftHand->ConfigureActionSets(c, p.m_szName); } } if (!V_strcmp(c, "/user/hand/right")) { m_pRightHand = new COpenXRController; m_pRightHand->m_side = k_EXRController_Right; m_pRightHand->m_pHeadset = this; for ( auto &p: s_interactionProfiles ) if (s == p.m_szName) { m_pRightHand->m_type = p.m_eType; m_pRightHand->ConfigureActionSets(c, p.m_szName); } } } xrDestroyActionSet(quetySet); } void COpenXRHeadset::Shutdown() { } void COpenXRHeadset::Frame() { while (true) { XrEventDataBuffer event = {}; event.type = XR_TYPE_EVENT_DATA_BUFFER; XrResult r = xrPollEvent(m_pManager->m_instance, &event); if ( r != XR_SUCCESS ) break; switch (event.type) { case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { XrEventDataSessionStateChanged *e = (XrEventDataSessionStateChanged*)&event; if ( e->state == XR_SESSION_STATE_READY ) { XrSessionBeginInfo beginInfo = {}; beginInfo.type = XR_TYPE_SESSION_BEGIN_INFO; beginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; xrBeginSession(m_session, &beginInfo); } if ( e->state == XR_SESSION_STATE_STOPPING ) { xrEndSession(m_session); } break; } default: break; } } XrFrameWaitInfo frameWait = {}; XrFrameState frameState = {}; frameState.type = XR_TYPE_FRAME_STATE; frameWait.type = XR_TYPE_FRAME_WAIT_INFO; xrWaitFrame(m_session, &frameWait, &frameState); m_fPredictedTime = frameState.predictedDisplayTime; XrActiveActionSet aas[2] = {}; aas[0].actionSet = m_pLeftHand->m_actionSet; aas[1].actionSet = m_pRightHand->m_actionSet; XrActionsSyncInfo si = {}; si.type = XR_TYPE_ACTIONS_SYNC_INFO; si.activeActionSets = aas; si.countActiveActionSets = 2; xrSyncActions(m_session, &si); m_views = m_eyeSwapchains.GetSize(); m_projviews = m_eyeSwapchains.GetSize(); for ( auto &v: m_views ) { v = {}; v.type = XR_TYPE_VIEW; } for ( auto &v: m_projviews ) { v = {}; v.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; } XrViewState viewState = {}; viewState.type = XR_TYPE_VIEW_STATE; XrViewLocateInfo viewLocale = {}; viewLocale.type = XR_TYPE_VIEW_LOCATE_INFO; viewLocale.displayTime = m_fPredictedTime; viewLocale.space = m_space; viewLocale.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; uint32_t viewCount = m_views.GetSize(); xrLocateViews(m_session, &viewLocale, &viewState, viewCount, &viewCount, m_views.GetData()); if (m_pLeftHand) m_pLeftHand->Frame(); if (m_pRightHand) m_pRightHand->Frame(); } void COpenXRHeadset::BeforeRender() { XrFrameBeginInfo begin = {}; begin.type = XR_TYPE_FRAME_BEGIN_INFO; xrBeginFrame(m_session, &begin); for ( uint32_t i = 0; i < m_views.GetSize(); i++ ) { XrSwapchainImageAcquireInfo acq = {}; acq.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO; uint32_t idx = 0; XrResult r = xrAcquireSwapchainImage(m_eyeSwapchains[i].m_swapchain, &acq, &idx); m_eyeSwapchains[i].m_uImageIndex = idx; if (r) return; XrSwapchainImageWaitInfo wait = {}; wait.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO; wait.timeout = XR_INFINITE_DURATION; xrWaitSwapchainImage(m_eyeSwapchains[i].m_swapchain, &wait); m_projviews[i].fov = m_views[i].fov; m_projviews[i].pose = m_views[i].pose; m_projviews[i].subImage.swapchain = m_eyeSwapchains[i].m_swapchain; m_projviews[i].subImage.imageRect.extent.height = m_eyeSwapchains[i].m_pImages[idx]->GetImageWidth(); m_projviews[i].subImage.imageRect.extent.width = m_eyeSwapchains[i].m_pImages[idx]->GetImageHeight(); m_projviews[i].subImage.imageArrayIndex = 0; } } void COpenXRHeadset::CopySwapchain() { IRenderContext *pCtx = m_pManager->m_pRenderContext; IRenderCommandList *pList = pCtx->CreateCommandList(); pList->StartRecording(); for ( auto &eye: m_eyeSwapchains) { ImageSector_t sector = {}; sector.m_iWidth = eye.m_uWidth; sector.m_iHeight = eye.m_uHeight; if (eye.m_pUserImage) pList->BlitImageToImage(eye.m_pUserImage, sector, eye.m_pImages[eye.m_uImageIndex], sector); } pList->EndRecording(); pCtx->SubmitCommandList(pList); pCtx->DestroyCommandList(pList); } void COpenXRHeadset::AfterRender() { for ( uint32_t i = 0; i < m_views.GetSize(); i++ ) { XrSwapchainImageReleaseInfo rel = {}; rel.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO; xrReleaseSwapchainImage(m_eyeSwapchains[i].m_swapchain, &rel); } XrCompositionLayerProjection proj = {}; proj.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; proj.space = m_space; proj.viewCount = m_projviews.GetSize(); proj.views = m_projviews.GetData(); XrCompositionLayerBaseHeader *compositions[] = { (XrCompositionLayerBaseHeader*)&proj }; XrFrameEndInfo end = {}; end.type = XR_TYPE_FRAME_END_INFO; end.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; end.layerCount = 1; end.layers = compositions; end.displayTime = m_fPredictedTime; xrEndFrame(m_session, &end); } uint32_t COpenXRHeadset::GetSurfaceCount() { return m_eyeSwapchains.GetSize(); } XRRenderSurface_t COpenXRHeadset::GetSurface( uint32_t i ) { XRRenderSurface_t surface = {}; surface.m_fFov = m_views[i].fov.angleLeft-m_views[i].fov.angleRight; surface.m_uWidth = m_eyeSwapchains[i].m_uWidth; surface.m_uHeight = m_eyeSwapchains[i].m_uHeight; surface.m_vPosition = (Vector){ m_views[i].pose.position.x, m_views[i].pose.position.y, m_views[i].pose.position.z }; surface.m_vRotation = (Quat){ m_views[i].pose.orientation.x, m_views[i].pose.orientation.y, m_views[i].pose.orientation.z, m_views[i].pose.orientation.w, }; return surface; } void COpenXRHeadset::SetSurfaceImage( uint32_t i, IImage *pImage ) { m_eyeSwapchains[i].m_pUserImage = pImage; V_printf("%p\n", pImage); } uint32_t COpenXRHeadset::GetControllerCount() { return 2; } IXRController *COpenXRHeadset::GetController( uint32_t i ) { if (i == 0) return m_pLeftHand; if (i == 1) return m_pRightHand; return NULL; } void COpenXRHeadset::CreateSwapchainForEye( XrViewConfigurationView *pConf, VkFormat eVkFormat, EyeSwapchain_t *pEye ) { XrSwapchainCreateInfo createInfo = {}; createInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO; createInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT; createInfo.format = eVkFormat; createInfo.sampleCount = pConf->recommendedSwapchainSampleCount; createInfo.width = pConf->recommendedImageRectWidth; createInfo.height = pConf->recommendedImageRectHeight; createInfo.faceCount = 1; createInfo.arraySize = 1; createInfo.mipCount = 1; XrResult r = xrCreateSwapchain(m_session, &createInfo, &pEye->m_swapchain); if (r != XR_SUCCESS) Plat_FatalErrorFunc("xrCreateSwapchain\n"); pEye->m_uWidth = pConf->recommendedImageRectWidth; pEye->m_uHeight = pConf->recommendedImageRectHeight; uint32_t imageCount = 0; CUtlVector images; switch (m_pManager->m_gpuAPI) { case OPENXR_API_VULKAN: { xrEnumerateSwapchainImages(pEye->m_swapchain, 0, &imageCount, NULL); images = imageCount; pEye->m_pImages = imageCount; for ( auto &i: images ) { i.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; } xrEnumerateSwapchainImages(pEye->m_swapchain, imageCount, &imageCount, (XrSwapchainImageBaseHeader*)images.GetData()); pEye->m_pImages = imageCount; } break; case OPENXR_API_VULKAN2: { xrEnumerateSwapchainImages(pEye->m_swapchain, 0, &imageCount, NULL); images = imageCount; pEye->m_pImages = imageCount; for ( auto &i: images ) { i.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR; } xrEnumerateSwapchainImages(pEye->m_swapchain, imageCount, &imageCount, (XrSwapchainImageBaseHeader*)images.GetData()); for ( auto &i: images ) V_printf("VkImage %p\n", i.image); } } EImageFormat eFormat = IMAGE_FORMAT_RGBA8_UNORM; switch(eVkFormat) { case VK_FORMAT_R8G8B8A8_UNORM: eFormat = IMAGE_FORMAT_RGBA8_UNORM; break; case VK_FORMAT_B8G8R8A8_UNORM: eFormat = IMAGE_FORMAT_BGRA8_UNORM; break; case VK_FORMAT_R8G8B8A8_SRGB: eFormat = IMAGE_FORMAT_RGBA8_SRGB; break; default: break; } for ( int i = 0; i < imageCount; i++ ) { pEye->m_pImages[i] = m_pManager->m_pRenderContext->CreateImageFromVkImage( images[i].image, createInfo.width, createInfo.height, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, eFormat, MULTISAMPLE_TYPE_1_SAMPLES); } } uint32_t COpenXRManager::GetHeadsetCount() { return 1; } IXRHeadset *COpenXRManager::GetHeadset( uint32_t i ) { if (i) return 0; if(!bHasXR) return 0; return &m_headset; } void COpenXRManager::ConnectInterface( void *pIface, const char *szName ) { if (!V_strcmp(szName, RENDER_CONTEXT_INTERFACE_VERSION)) m_pRenderContext = (IRenderContext*)pIface; }