diff --git a/src/mods/VR.cpp b/src/mods/VR.cpp index eb4b8690..c5973520 100644 --- a/src/mods/VR.cpp +++ b/src/mods/VR.cpp @@ -286,6 +286,7 @@ std::optional VR::initialize_openxr() { const std::unordered_set wanted_extensions { XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME, + XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME // To be seen if we need more! }; @@ -294,7 +295,6 @@ std::optional VR::initialize_openxr() { spdlog::info("[VR] Enabling {} extension", extension_property.extensionName); m_openxr->enabled_extensions.insert(extension_property.extensionName); extensions.push_back(extension_property.extensionName); - break; } } } diff --git a/src/mods/vr/D3D11Component.cpp b/src/mods/vr/D3D11Component.cpp index 5a13fa18..619405d7 100644 --- a/src/mods/vr/D3D11Component.cpp +++ b/src/mods/vr/D3D11Component.cpp @@ -443,18 +443,18 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { } LOG_VERBOSE("Ending frame"); - std::vector quad_layers{}; + std::vector quad_layers{}; auto& openxr_overlay = vr->get_overlay_component().get_openxr(); - const auto slate_quad = openxr_overlay.generate_slate_quad(); - if (slate_quad) { - quad_layers.push_back(*slate_quad); + const auto slate_layer = openxr_overlay.generate_slate_layer(); + if (slate_layer) { + quad_layers.push_back(&slate_layer->get()); } const auto framework_quad = openxr_overlay.generate_framework_ui_quad(); if (framework_quad) { - quad_layers.push_back(*framework_quad); + quad_layers.push_back((XrCompositionLayerBaseHeader*)&framework_quad->get()); } auto result = vr->m_openxr->end_frame(quad_layers, scene_depth_tex != nullptr); @@ -1524,7 +1524,7 @@ std::optional D3D11Component::OpenXR::create_swapchains() { } // Depth textures - if (vr->get_openxr_runtime()->enabled_extensions.contains(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME)) { + if (vr->get_openxr_runtime()->is_depth_allowed()) { // Even when using AFR, the depth tex is always the size of a double wide. // That's kind of unfortunate in terms of how many copies we have to do but whatever. auto depth_swapchain_create_info = standard_swapchain_create_info; diff --git a/src/mods/vr/D3D12Component.cpp b/src/mods/vr/D3D12Component.cpp index 944a1194..bd2bbe15 100644 --- a/src/mods/vr/D3D12Component.cpp +++ b/src/mods/vr/D3D12Component.cpp @@ -417,7 +417,7 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { vr->m_openxr->begin_frame(); } - std::vector quad_layers{}; + std::vector quad_layers{}; auto& openxr_overlay = vr->get_overlay_component().get_openxr(); @@ -426,23 +426,23 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { const auto right_quad = openxr_overlay.generate_slate_quad(runtimes::OpenXR::SwapchainIndex::UI_RIGHT, XrEyeVisibility::XR_EYE_VISIBILITY_RIGHT); if (left_quad) { - quad_layers.push_back(*left_quad); + quad_layers.push_back((XrCompositionLayerBaseHeader*)&left_quad->get()); } if (right_quad) { - quad_layers.push_back(*right_quad); + quad_layers.push_back((XrCompositionLayerBaseHeader*)&right_quad->get()); } } else { - const auto slate_quad = openxr_overlay.generate_slate_quad(); + const auto slate_layer = openxr_overlay.generate_slate_layer(); - if (slate_quad) { - quad_layers.push_back(*slate_quad); + if (slate_layer) { + quad_layers.push_back(&slate_layer->get()); } } const auto framework_quad = openxr_overlay.generate_framework_ui_quad(); if (framework_quad) { - quad_layers.push_back(*framework_quad); + quad_layers.push_back((XrCompositionLayerBaseHeader*)&framework_quad); } auto result = vr->m_openxr->end_frame(quad_layers, scene_depth_tex.Get() != nullptr); diff --git a/src/mods/vr/OverlayComponent.cpp b/src/mods/vr/OverlayComponent.cpp index c16f7e3d..5f92fc53 100644 --- a/src/mods/vr/OverlayComponent.cpp +++ b/src/mods/vr/OverlayComponent.cpp @@ -91,6 +91,14 @@ void OverlayComponent::on_config_load(const utility::Config& cfg, bool set_defau void OverlayComponent::on_draw_ui() { ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Overlay Options")) { + if (VR::get()->get_runtime()->is_cylinder_layer_allowed()) { + m_slate_overlay_type->draw("Overlay Type"); + + if ((OverlayType)m_slate_overlay_type->value() == OverlayType::CYLINDER) { + m_slate_cylinder_angle->draw("UI Cylinder Angle"); + } + } + float ui_offset[] { m_slate_x_offset->value(), m_slate_y_offset->value(), m_slate_distance->value() }; if (ImGui::SliderFloat3("UI Offset", ui_offset, -10.0f, 10.0f)) { @@ -670,7 +678,108 @@ std::optional> OverlayComponent:: return layer; } -std::optional> OverlayComponent::OpenXR::generate_framework_ui_quad() { +std::optional> OverlayComponent::OpenXR::generate_slate_cylinder( + runtimes::OpenXR::SwapchainIndex swapchain, + XrEyeVisibility eye) +{ + auto& vr = VR::get(); + + if (!vr->is_gui_enabled()) { + return std::nullopt; + } + + if (!vr->m_openxr->swapchains.contains((uint32_t)swapchain)) { + return std::nullopt; + } + + const auto is_left_eye = eye == XR_EYE_VISIBILITY_BOTH || eye == XR_EYE_VISIBILITY_LEFT; + + auto& layer = is_left_eye ? this->m_slate_layer_cylinder : this->m_slate_layer_cylinder_right; + + layer.type = XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR; + const auto& ui_swapchain = vr->m_openxr->swapchains[(uint32_t)swapchain]; + layer.subImage.swapchain = ui_swapchain.handle; + layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; + layer.subImage.imageRect.offset.x = 0; + layer.subImage.imageRect.offset.y = 0; + layer.subImage.imageRect.extent.width = ui_swapchain.width; + layer.subImage.imageRect.extent.height = ui_swapchain.height; + layer.eyeVisibility = eye; + + auto glm_matrix = glm::identity(); + + if (vr->m_overlay_component.m_ui_follows_view->value()) { + layer.space = vr->m_openxr->view_space; + } else { + auto rotation_offset = glm::inverse(vr->get_rotation_offset()); + + if (vr->is_decoupled_pitch_enabled() && vr->is_decoupled_pitch_ui_adjust_enabled()) { + const auto pre_flat_rotation = vr->get_pre_flattened_rotation(); + const auto pre_flat_pitch = utility::math::pitch_only(pre_flat_rotation); + + // Add the inverse of the pitch rotation to the rotation offset + rotation_offset = glm::normalize(glm::inverse(pre_flat_pitch * vr->get_rotation_offset())); + } + + glm_matrix = Matrix4x4f{rotation_offset}; + glm_matrix[3] += vr->get_standing_origin(); + layer.space = vr->m_openxr->stage_space; + } + + const auto size_meters = m_parent->m_slate_size->value(); + const auto meters_w = (float)ui_swapchain.width / (float)ui_swapchain.height * size_meters; + const auto meters_h = size_meters; + + // OpenXR Docs: + // radius is the non-negative radius of the cylinder. Values of zero or floating point positive infinity are treated as an infinite cylinder. + // centralAngle is the angle of the visible section of the cylinder, based at 0 radians, in the range of [0, 2π). It grows symmetrically around the 0 radian angle. + // aspectRatio is the ratio of the visible cylinder section width / height. The height of the cylinder is given by: (cylinder radius × cylinder angle) / aspectRatio. + layer.radius = meters_w / 2.0f; + layer.centralAngle = glm::radians(m_parent->m_slate_cylinder_angle->value()); + layer.aspectRatio = meters_w / meters_h; + + glm_matrix[3] -= glm_matrix[2] * m_parent->m_slate_distance->value(); + glm_matrix[3] += m_parent->m_slate_x_offset->value() * glm_matrix[0]; + glm_matrix[3] += m_parent->m_slate_y_offset->value() * glm_matrix[1]; + glm_matrix[3].w = 1.0f; + + layer.pose.orientation = runtimes::OpenXR::to_openxr(glm::quat_cast(glm_matrix)); + layer.pose.position = runtimes::OpenXR::to_openxr(glm_matrix[3]); + + return layer; +} + +std::optional> OverlayComponent::OpenXR::generate_slate_layer( + runtimes::OpenXR::SwapchainIndex swapchain, + XrEyeVisibility eye) +{ + switch ((OverlayComponent::OverlayType)m_parent->m_slate_overlay_type->value()) { + default: + case OverlayComponent::OverlayType::QUAD: + if (auto result = generate_slate_quad(swapchain, eye); result.has_value()) { + return *(XrCompositionLayerBaseHeader*)&result.value().get(); + } + + return std::nullopt; + case OverlayComponent::OverlayType::CYLINDER: + if (!VR::get()->get_runtime()->is_cylinder_layer_allowed()) { + if (auto result = generate_slate_quad(swapchain, eye); result.has_value()) { + return *(XrCompositionLayerBaseHeader*)&result.value().get(); + } + + return std::nullopt; + } + + if (auto result = generate_slate_cylinder(swapchain, eye); result.has_value()) { + return *(XrCompositionLayerBaseHeader*)&result.value().get(); + } + + return std::nullopt; + }; +} + + +std::optional> OverlayComponent::OpenXR::generate_framework_ui_quad() { if (!g_framework->is_drawing_anything()) { return std::nullopt; } diff --git a/src/mods/vr/OverlayComponent.hpp b/src/mods/vr/OverlayComponent.hpp index 8f2c81f3..4fe81557 100644 --- a/src/mods/vr/OverlayComponent.hpp +++ b/src/mods/vr/OverlayComponent.hpp @@ -68,10 +68,24 @@ class OverlayComponent : public ModComponent { bool m_just_closed_ui{false}; bool m_just_opened_ui{false}; + enum OverlayType { + DEFAULT = 0, + QUAD = 0, + CYLINDER = 1, + MAX + }; + + static const inline std::vector s_overlay_type_names{ + "Quad", + "Cylinder" + }; + + const ModCombo::Ptr m_slate_overlay_type{ ModCombo::create("UI_OverlayType", s_overlay_type_names) }; const ModSlider::Ptr m_slate_distance{ ModSlider::create("UI_Distance", 0.5f, 10.0f, 2.0f) }; const ModSlider::Ptr m_slate_x_offset{ ModSlider::create("UI_X_Offset", -10.0f, 10.0f, 0.0f) }; const ModSlider::Ptr m_slate_y_offset{ ModSlider::create("UI_Y_Offset", -10.0f, 10.0f, 0.0f) }; const ModSlider::Ptr m_slate_size{ ModSlider::create("UI_Size", 0.5f, 10.0f, 2.0f) }; + const ModSlider::Ptr m_slate_cylinder_angle{ ModSlider::create("UI_Cylinder_Angle", 0.0f, 360.0f, 90.0f) }; const ModToggle::Ptr m_ui_follows_view{ ModToggle::create("UI_FollowView", false) }; const ModSlider::Ptr m_framework_distance{ ModSlider::create("UI_Framework_Distance", 0.5f, 10.0f, 1.75f) }; @@ -80,10 +94,12 @@ class OverlayComponent : public ModComponent { const ModToggle::Ptr m_framework_wrist_ui{ ModToggle::create("UI_Framework_WristUI", false) }; Mod::ValueList m_options{ + *m_slate_overlay_type, *m_slate_x_offset, *m_slate_y_offset, *m_slate_distance, *m_slate_size, + *m_slate_cylinder_angle, *m_ui_follows_view, *m_framework_distance, *m_framework_size, @@ -105,11 +121,21 @@ class OverlayComponent : public ModComponent { runtimes::OpenXR::SwapchainIndex swapchain = runtimes::OpenXR::SwapchainIndex::UI, XrEyeVisibility eye = XR_EYE_VISIBILITY_BOTH ); + std::optional> generate_slate_cylinder( + runtimes::OpenXR::SwapchainIndex swapchain = runtimes::OpenXR::SwapchainIndex::UI, + XrEyeVisibility eye = XR_EYE_VISIBILITY_BOTH + ); + std::optional> generate_slate_layer( + runtimes::OpenXR::SwapchainIndex swapchain = runtimes::OpenXR::SwapchainIndex::UI, + XrEyeVisibility eye = XR_EYE_VISIBILITY_BOTH + ); std::optional> generate_framework_ui_quad(); private: XrCompositionLayerQuad m_slate_layer{}; XrCompositionLayerQuad m_slate_layer_right{}; + XrCompositionLayerCylinderKHR m_slate_layer_cylinder{}; + XrCompositionLayerCylinderKHR m_slate_layer_cylinder_right{}; XrCompositionLayerQuad m_framework_ui_layer{}; OverlayComponent* m_parent{ nullptr }; diff --git a/src/mods/vr/runtimes/OpenXR.cpp b/src/mods/vr/runtimes/OpenXR.cpp index 58be1fa6..3881c3dc 100644 --- a/src/mods/vr/runtimes/OpenXR.cpp +++ b/src/mods/vr/runtimes/OpenXR.cpp @@ -1572,7 +1572,7 @@ XrResult OpenXR::begin_frame() { return result; } -XrResult OpenXR::end_frame(const std::vector& quad_layers, bool has_depth) { +XrResult OpenXR::end_frame(const std::vector& quad_layers, bool has_depth) { std::scoped_lock _{sync_mtx}; if (!this->ready() || !this->got_first_poses || !this->frame_synced) { @@ -1761,7 +1761,7 @@ XrResult OpenXR::end_frame(const std::vector& quad_layer } for (auto& l : quad_layers) { - layers.push_back((XrCompositionLayerBaseHeader*)&l); + layers.push_back(l); } } diff --git a/src/mods/vr/runtimes/OpenXR.hpp b/src/mods/vr/runtimes/OpenXR.hpp index 2fa3a217..8714b008 100644 --- a/src/mods/vr/runtimes/OpenXR.hpp +++ b/src/mods/vr/runtimes/OpenXR.hpp @@ -53,6 +53,10 @@ struct OpenXR final : public VRRuntime { return this->enabled_extensions.contains(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME); } + bool is_cylinder_layer_allowed() const override { + return this->enabled_extensions.contains(XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME); + } + void on_system_properties_acquired(const XrSystemProperties& props); void on_config_load(const utility::Config& cfg, bool set_defaults) override; @@ -132,7 +136,7 @@ struct OpenXR final : public VRRuntime { std::optional initialize_actions(const std::string& json_string); XrResult begin_frame(); - XrResult end_frame(const std::vector& quad_layers, bool has_depth = false); + XrResult end_frame(const std::vector& quad_layers, bool has_depth = false); void begin_profile() { if (!this->profile_calls) { diff --git a/src/mods/vr/runtimes/VRRuntime.hpp b/src/mods/vr/runtimes/VRRuntime.hpp index cfc9f09a..5ef9a08f 100644 --- a/src/mods/vr/runtimes/VRRuntime.hpp +++ b/src/mods/vr/runtimes/VRRuntime.hpp @@ -111,6 +111,10 @@ struct VRRuntime { return false; } + virtual bool is_cylinder_layer_allowed() const { + return false; + } + virtual void on_config_load(const utility::Config& cfg, bool set_defaults) {} virtual void on_config_save(utility::Config& cfg) {} virtual void on_draw_ui() {}