From 8e044f9fcd1c9adea3e2bd25a65448d2c6a3d2b4 Mon Sep 17 00:00:00 2001 From: Simon <6168240+DrBeef@users.noreply.github.com> Date: Sun, 28 Jan 2024 22:59:28 +0000 Subject: [PATCH 1/4] Pass the vr_use_immersive_mode as a Vertex Shader Uniform This means the same shaders can be used for both immersive and non-immersive as the uniform itself it the toggle in the GLSL rather than generating two different instances of the shader. Furthermore, only one place where the gl_Position was being modified was actually needed; the other two seem to be able to be removed without it preventing immersive mode from working. --- src/video_core/rasterizer_accelerated.cpp | 7 +++++ src/video_core/rasterizer_accelerated.h | 3 +++ .../shader/generator/glsl_shader_gen.cpp | 27 ++++++------------- .../shader/generator/shader_gen.cpp | 4 --- src/video_core/shader/generator/shader_gen.h | 3 --- .../shader/generator/shader_uniforms.h | 1 + 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index b6e8bb4fa..f695d39f0 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -4,6 +4,7 @@ #include #include "common/alignment.h" +#include "common/settings.h" #include "core/memory.h" #include "video_core/pica_state.h" #include "video_core/rasterizer_accelerated.h" @@ -136,6 +137,7 @@ void RasterizerAccelerated::SyncEntireState() { // Sync uniforms SyncClipPlane(); + SyncVRImmersive(); SyncDepthScale(); SyncDepthOffset(); SyncAlphaTest(); @@ -860,4 +862,9 @@ void RasterizerAccelerated::SyncClipPlane() { } } +void RasterizerAccelerated::SyncVRImmersive() { + vs_uniform_block_data.data.vr_use_immersive_mode = Settings::values.vr_use_immersive_mode.GetValue(); + vs_uniform_block_data.dirty = true; +} + } // namespace VideoCore diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h index 0bef2c537..ffcfb1483 100644 --- a/src/video_core/rasterizer_accelerated.h +++ b/src/video_core/rasterizer_accelerated.h @@ -103,6 +103,9 @@ protected: /// Syncs the clip plane state to match the PICA register void SyncClipPlane(); + /// Syncs the VR immersive flag + void SyncVRImmersive(); + protected: /// Structure that keeps tracks of the vertex shader uniform state struct VSUniformBlockData { diff --git a/src/video_core/shader/generator/glsl_shader_gen.cpp b/src/video_core/shader/generator/glsl_shader_gen.cpp index 0b9acf22f..e4c2866ed 100644 --- a/src/video_core/shader/generator/glsl_shader_gen.cpp +++ b/src/video_core/shader/generator/glsl_shader_gen.cpp @@ -43,6 +43,7 @@ layout (set = 0, binding = 1, std140) uniform vs_data { #else layout (binding = 1, std140) uniform vs_data { #endif + bool vr_use_immersive_mode; bool enable_clip1; vec4 clip_coef; }; @@ -1681,12 +1682,7 @@ void main() { } )"; - if (!Settings::values.vr_use_immersive_mode.GetValue()) - { - out+= "\ngl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; - } else { - out+= "\ngl_Position = vec4(vtx_pos.x / 3.0, vtx_pos.y / 3.0, -vtx_pos.z, vtx_pos.w);\n"; - } + out+= "\ngl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; if (use_clip_planes) { out += R"( @@ -1807,14 +1803,7 @@ std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const P out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - if (!Settings::values.vr_use_immersive_mode.GetValue()) - { - out+= " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; - } - else - { - out+= " gl_Position = vec4(vtx_pos.x / 3.0, vtx_pos.y / 3.0, -vtx_pos.z, vtx_pos.w);\n"; - } + out+= " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; if (config.state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 @@ -1913,11 +1902,11 @@ struct Vertex { out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - if (!Settings::values.vr_use_immersive_mode.GetValue()) { - out+= " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; - } else { - out+= " gl_Position = vec4(vtx_pos.x / 3.0, vtx_pos.y / 3.0, -vtx_pos.z, vtx_pos.w);\n"; - } + out += " if (vr_use_immersive_mode) {\n"; + out += " gl_Position = vec4(vtx_pos.x / 3.0, vtx_pos.y / 3.0, -vtx_pos.z, vtx_pos.w);\n"; + out += " } else {\n"; + out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; + out += " }\n\n"; if (state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 diff --git a/src/video_core/shader/generator/shader_gen.cpp b/src/video_core/shader/generator/shader_gen.cpp index 4b7f056ef..2ed7ff006 100644 --- a/src/video_core/shader/generator/shader_gen.cpp +++ b/src/video_core/shader/generator/shader_gen.cpp @@ -21,8 +21,6 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, bool has_fragment_shader_inte ? regs.framebuffer.output_merger.alpha_test.func.Value() : Pica::FramebufferRegs::CompareFunc::Always); - state.vr_use_immersive_mode.Assign(Settings::values.vr_use_immersive_mode.GetValue()); - state.texture0_type.Assign(regs.texturing.texture0.type); state.texture2_use_coord1.Assign(regs.texturing.main_config.texture2_use_coord1 != 0); @@ -270,8 +268,6 @@ void PicaVSConfigState::Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& if (!use_geometry_shader_) { gs_state.Init(regs, use_clip_planes_); } - - vr_use_immersive_mode = Settings::values.vr_use_immersive_mode.GetValue(); } PicaVSConfig::PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup, diff --git a/src/video_core/shader/generator/shader_gen.h b/src/video_core/shader/generator/shader_gen.h index 0bc416195..8ac1c4c0b 100644 --- a/src/video_core/shader/generator/shader_gen.h +++ b/src/video_core/shader/generator/shader_gen.h @@ -60,7 +60,6 @@ struct PicaFSConfigState { BitField<28, 1, u32> shadow_texture_orthographic; BitField<29, 1, u32> use_fragment_shader_interlock; BitField<30, 1, u32> use_custom_normal_map; - BitField<31, 1, u32> vr_use_immersive_mode; }; union { @@ -217,8 +216,6 @@ struct PicaVSConfigState { PicaGSConfigState gs_state; - bool vr_use_immersive_mode; - }; /** diff --git a/src/video_core/shader/generator/shader_uniforms.h b/src/video_core/shader/generator/shader_uniforms.h index b37083687..2ddd8fd32 100644 --- a/src/video_core/shader/generator/shader_uniforms.h +++ b/src/video_core/shader/generator/shader_uniforms.h @@ -89,6 +89,7 @@ struct PicaUniformsData { }; struct VSUniformData { + bool vr_use_immersive_mode; bool enable_clip1; alignas(16) Common::Vec4f clip_coef; }; From d4515d120f2ce17f63ab8539c0d135bc69e1270a Mon Sep 17 00:00:00 2001 From: Simon <6168240+DrBeef@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:48:43 +0000 Subject: [PATCH 2/4] X/Y Positional Movement Changes: - use headset X and Y position to move camera by pushing them to the shader in VS Uniforms - Hide the lower panel when not looking at it as the positional movement royally screws it up - Add a slider to allow user to select how much of an impact positional movement makes - Added back in the original Lubos immersive mode scale --- .../ui/SettingsFragmentPresenter.java | 4 +- .../features/settings/utils/SettingsFile.java | 3 ++ src/android/app/src/main/jni/config.cpp | 15 +++---- src/android/app/src/main/jni/vr/OpenXR.h | 3 ++ .../main/jni/vr/layers/GameSurfaceLayer.cpp | 3 +- .../src/main/jni/vr/layers/GameSurfaceLayer.h | 3 +- src/android/app/src/main/jni/vr/vr_main.cpp | 41 ++++++++++++++++++- .../app/src/main/res/values/arrays.xml | 12 ++++++ .../app/src/main/res/values/strings.xml | 2 + src/common/settings.h | 3 +- src/video_core/rasterizer_accelerated.cpp | 18 ++++++-- src/video_core/rasterizer_accelerated.h | 6 ++- src/video_core/rasterizer_interface.h | 4 ++ .../renderer_opengl/gl_rasterizer.cpp | 3 ++ .../shader/generator/glsl_shader_gen.cpp | 14 +++---- .../shader/generator/shader_uniforms.h | 7 +++- 16 files changed, 114 insertions(+), 27 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java index 7719ab1c9..1e8798fec 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java @@ -433,9 +433,11 @@ public final class SettingsFragmentPresenter { Setting vrExtraPerformanceMode = vrSection.getSetting(SettingsFile.KEY_VR_EXTRA_PERFORMANCE_MODE); Setting vrCpuLevel = vrSection.getSetting(SettingsFile.KEY_VR_CPU_LEVEL); Setting vrImmersiveMode = vrSection.getSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE); + Setting vrImmersivePositionalFactor = vrSection.getSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_FACTOR); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_ENVIRONMENT, Settings.SECTION_VR, R.string.vr_background, 0, R.array.vrBackgroundNames, R.array.vrBackgroundValues, VRUtils.getHMDType() == VRUtils.HMDType.QUEST3.getValue() ? 1 : 2, vrEnvironment)); sl.add(new CheckBoxSetting(SettingsFile.KEY_VR_EXTRA_PERFORMANCE_MODE, Settings.SECTION_VR, R.string.vr_extra_performance_mode, R.string.vr_extra_performance_mode_description, false, vrExtraPerformanceMode)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_CPU_LEVEL, Settings.SECTION_VR, R.string.vr_cpu_level, R.string.vr_cpu_level_description, R.array.vrCpuLevelNames, R.array.vrCpuLevelValues, 3, vrCpuLevel)); - sl.add(new CheckBoxSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE, Settings.SECTION_VR, R.string.vr_immersive_mode_title, R.string.vr_immersive_mode_description, false, vrImmersiveMode, false, true, mView)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE, Settings.SECTION_VR, R.string.vr_immersive_mode_title, R.string.vr_immersive_mode_description, R.array.vrImmersiveModeNames, R.array.vrImmersiveModeValues, 0, vrImmersiveMode)); + sl.add(new SliderSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_FACTOR, Settings.SECTION_VR, R.string.vr_immersive_pos_factor_title, R.string.vr_immersive_pos_factor_description, 0, 40, "X", 0, vrImmersivePositionalFactor)); } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java index 767bf9504..994ad37f4 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java @@ -136,6 +136,9 @@ public final class SettingsFile { public static final String KEY_VR_CPU_LEVEL = "vr_cpu_level"; public static final String KEY_VR_IMMERSIVE_MODE = "vr_immersive_mode"; + + public static final String KEY_VR_IMMERSIVE_POSITIONAL_FACTOR = "vr_immersive_positional_factor"; + public static final String KEY_LOG_FILTER = "log_filter"; private static BiMap sectionsMap = new BiMap<>(); diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index c965f5490..14cd0837b 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -304,19 +304,20 @@ void Config::ReadValues() { VRSettings::values.cpu_level = VRSettings::values.extra_performance_mode_enabled ? XR_HIGHEST_CPU_PERF_LEVEL : VRSettings::CPUPrefToPerfSettingsLevel(sdl2_config->GetInteger( - "VR", "vr_cpu_level", 3)); - Settings::values.vr_use_immersive_mode = sdl2_config->GetBoolean( - "VR", "vr_immersive_mode", false); + "VR", "vr_cpu_level", 3)); + VRSettings::values.vr_immersive_mode = sdl2_config->GetInteger( + "VR", "vr_immersive_mode", 0); + Settings::values.vr_immersive_mode = VRSettings::values.vr_immersive_mode; + VRSettings::values.vr_immersive_positional_factor = sdl2_config->GetInteger( + "VR", "vr_immersive_positional_factor", 0); + Settings::values.vr_immersive_positional_factor = VRSettings::values.vr_immersive_positional_factor; - if (Settings::values.vr_use_immersive_mode) { + if (Settings::values.vr_immersive_mode.GetValue() > 0) { LOG_INFO(Config, "VR immersive mode enabled"); // no point rendering passthrough in immersive mode VRSettings::values.vr_environment = static_cast(VRSettings::VREnvironmentType::VOID); - // We originally had two immersive modes, but I cut them down to fit in - // the shader map's bitfield. - VRSettings::values.vr_immersive_mode = 2; // When immersive mode is enabled, only OpenGL is supported. Settings::values.graphics_api = Settings::GraphicsAPI::OpenGL; } diff --git a/src/android/app/src/main/jni/vr/OpenXR.h b/src/android/app/src/main/jni/vr/OpenXR.h index 400331acc..41303ff65 100644 --- a/src/android/app/src/main/jni/vr/OpenXR.h +++ b/src/android/app/src/main/jni/vr/OpenXR.h @@ -44,8 +44,11 @@ public: static constexpr XrViewConfigurationType VIEW_CONFIG_TYPE = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; XrSpace headSpace_ = XR_NULL_HANDLE; + XrSpace viewSpace_ = XR_NULL_HANDLE; XrSpace forwardDirectionSpace_ = XR_NULL_HANDLE; + XrSpaceLocation headLocation = {}; + XrSpace localSpace_ = XR_NULL_HANDLE; XrSpace stageSpace_ = XR_NULL_HANDLE; size_t maxLayerCount_ = 0; diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 2cbea29f5..849839a48 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -255,7 +255,7 @@ void GameSurfaceLayer::SetSurface() const { } void GameSurfaceLayer::Frame(const XrSpace& space, std::vector& layers, - uint32_t& layerCount) const + uint32_t& layerCount, const bool showLowerPanel) const { const uint32_t panelWidth = swapchain_.Width / 2; @@ -342,6 +342,7 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector& layers, - uint32_t& layerCount) const; + uint32_t& layerCount, const bool showLowerPanel) const; /** Given an origin, direction of a ray, * returns the coordinates of where the ray will intersects diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index ff86ce002..e360e64cc 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -35,6 +35,9 @@ License : Licensed under GPLv3 or any later version. #include #include +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" + #if defined(DEBUG_INPUT_VERBOSE) #define ALOG_INPUT_VERBOSE(...) ALOGI(__VA_ARGS__) #else @@ -457,6 +460,14 @@ private: &gOpenXr->forwardDirectionSpace_)); } + { + const XrReferenceSpaceCreateInfo sci = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + nullptr, XR_REFERENCE_SPACE_TYPE_VIEW, + XrMath::Posef::Identity()}; + OXR(xrCreateReferenceSpace(gOpenXr->session_, &sci, + &gOpenXr->viewSpace_)); + } + // Get the pose of the local space. XrSpaceLocation lsl = {XR_TYPE_SPACE_LOCATION}; OXR(xrLocateSpace(gOpenXr->forwardDirectionSpace_, gOpenXr->localSpace_, @@ -473,6 +484,34 @@ private: OXR(xrCreateReferenceSpace(gOpenXr->session_, &sci, &gOpenXr->headSpace_)); } + gOpenXr->headLocation = {XR_TYPE_SPACE_LOCATION}; + OXR(xrLocateSpace(gOpenXr->viewSpace_, gOpenXr->headSpace_, frameState.predictedDisplayTime, &gOpenXr->headLocation)); + + bool showLowerPanel = false; + // Push the HMD position through to the Rasterizer to pass on to the VS Uniform + if (VideoCore::g_renderer && VideoCore::g_renderer->Rasterizer()) + { + //I am confused... but GetRollInRadians appears to return pitch + float pitch = XrMath::Quatf::GetRollInRadians(gOpenXr->headLocation.pose.orientation); + + //Only use position if it is enabled, and the user is not looking at the lower panel + if ((VRSettings::values.vr_immersive_positional_factor > 0) && (pitch > -0.35f)) + { + Common::Vec3f position{-gOpenXr->headLocation.pose.position.y, + gOpenXr->headLocation.pose.position.x, + 0.0f}; //gOpenXr->headLocation.pose.position.z}; - For some reason Z doesn't affect the actual Z position! + position *= VRSettings::values.vr_immersive_positional_factor; + VideoCore::g_renderer->Rasterizer()->SetVRData(position); + } + else + { + //Disable position when looking down at the lower panel + Common::Vec3f position = {}; + VideoCore::g_renderer->Rasterizer()->SetVRData(position); + showLowerPanel = true; + } + } + mInputStateFrame.SyncHandPoses(gOpenXr->session_, mInputStateStatic, gOpenXr->localSpace_, frameState.predictedDisplayTime); @@ -491,7 +530,7 @@ private: layers[layerCount++].Passthrough = passthroughLayer; } - mGameSurfaceLayer->Frame(gOpenXr->localSpace_, layers, layerCount); + mGameSurfaceLayer->Frame(gOpenXr->localSpace_, layers, layerCount, showLowerPanel); #if defined(UI_LAYER) if (gShouldShowErrorMessage) { XrCompositionLayerQuad quadLayer = {}; diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 2973920e3..88c998e0c 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -224,4 +224,16 @@ 3 4 + + + Off + Large - Low Resolution + Higher Resolution + + + + 0 + 1 + 2 + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 48bd9481e..23d46640a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -302,6 +302,8 @@ Higher levels may improve audio/emulation performance, but will drain the battery faster [WARNING: Graphics Artifacts] Immersive Mode (Experimental) Extends the top screen around a cylinder for immersive gameplay + Immersive Mode Positional Movement Factor + Adjusts how much movement left/right/up/down affects the game camera (0 = disabled) DANGER: not all games/levels look good in immersive mode. Large visual artifacts are common when this option is selected WARNING: this WILL cause visual artifacting in most content diff --git a/src/common/settings.h b/src/common/settings.h index 2552d86d9..fa9bb0c20 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -527,7 +527,8 @@ struct Values { u64 audio_bitrate; // VR - Setting vr_use_immersive_mode{false, "vr_immersive_mode"}; + Setting vr_immersive_mode{0, "vr_immersive_mode"}; + Setting vr_immersive_positional_factor{0, "vr_immersive_positional_factor"}; }; extern Values values; diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index f695d39f0..61cc60246 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -13,6 +13,8 @@ namespace VideoCore { using Pica::f24; +const std::vector immersiveLevelFactor = {1.0f, 5.0f, 3.0f}; + static Common::Vec4f ColorRGBA8(const u32 color) { const auto rgba = Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF}; @@ -137,7 +139,7 @@ void RasterizerAccelerated::SyncEntireState() { // Sync uniforms SyncClipPlane(); - SyncVRImmersive(); + SyncVRData(); SyncDepthScale(); SyncDepthOffset(); SyncAlphaTest(); @@ -862,8 +864,18 @@ void RasterizerAccelerated::SyncClipPlane() { } } -void RasterizerAccelerated::SyncVRImmersive() { - vs_uniform_block_data.data.vr_use_immersive_mode = Settings::values.vr_use_immersive_mode.GetValue(); +void RasterizerAccelerated::SyncVRData() { + if (vs_uniform_block_data.data.vr_immersive_mode_factor != immersiveLevelFactor[Settings::values.vr_immersive_mode.GetValue()]) + { + vs_uniform_block_data.data.vr_immersive_mode_factor = immersiveLevelFactor[Settings::values.vr_immersive_mode.GetValue()]; + vs_uniform_block_data.dirty = true; + } +} + +void RasterizerAccelerated::SetVRData(Common::Vec3f position) +{ + //Positional Tinkering + vs_uniform_block_data.data.vr_position = position; vs_uniform_block_data.dirty = true; } diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h index ffcfb1483..eede3828d 100644 --- a/src/video_core/rasterizer_accelerated.h +++ b/src/video_core/rasterizer_accelerated.h @@ -29,6 +29,8 @@ public: void NotifyPicaRegisterChanged(u32 id) override; void SyncEntireState() override; + void SetVRData(Common::Vec3f position) override; + protected: /// Sync fixed-function pipeline state virtual void SyncFixedState() = 0; @@ -103,8 +105,8 @@ protected: /// Syncs the clip plane state to match the PICA register void SyncClipPlane(); - /// Syncs the VR immersive flag - void SyncVRImmersive(); + /// Syncs the VR data + void SyncVRData(); protected: /// Structure that keeps tracks of the vertex shader uniform state diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 5bb55b7bb..ba5c66e2f 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -7,6 +7,7 @@ #include #include #include "common/common_types.h" +#include "common/vector_math.h" #include "core/hw/gpu.h" namespace Pica::Shader { @@ -81,5 +82,8 @@ public: [[maybe_unused]] const DiskResourceLoadCallback& callback) {} virtual void SyncEntireState() {} + + /// Set VR position data on the rasterizer + virtual void SetVRData(Common::Vec3f position) {} }; } // namespace VideoCore diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index a5bed59ef..8818820a9 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -434,6 +434,9 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { SyncAndUploadLUTs(); SyncAndUploadLUTsLF(); + //Sync VR data if needed + SyncVRData(); + // Sync the uniform data UploadUniforms(accelerate); diff --git a/src/video_core/shader/generator/glsl_shader_gen.cpp b/src/video_core/shader/generator/glsl_shader_gen.cpp index e4c2866ed..a9738f853 100644 --- a/src/video_core/shader/generator/glsl_shader_gen.cpp +++ b/src/video_core/shader/generator/glsl_shader_gen.cpp @@ -43,9 +43,11 @@ layout (set = 0, binding = 1, std140) uniform vs_data { #else layout (binding = 1, std140) uniform vs_data { #endif - bool vr_use_immersive_mode; bool enable_clip1; vec4 clip_coef; + + float vr_immersive_mode_factor; + vec3 vr_position; }; )"; @@ -1682,7 +1684,7 @@ void main() { } )"; - out+= "\ngl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; + out+= "\ngl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; if (use_clip_planes) { out += R"( @@ -1803,7 +1805,7 @@ std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const P out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - out+= " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; + out+= " gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; if (config.state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 @@ -1902,11 +1904,7 @@ struct Vertex { out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - out += " if (vr_use_immersive_mode) {\n"; - out += " gl_Position = vec4(vtx_pos.x / 3.0, vtx_pos.y / 3.0, -vtx_pos.z, vtx_pos.w);\n"; - out += " } else {\n"; - out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; - out += " }\n\n"; + out += " gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; if (state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 diff --git a/src/video_core/shader/generator/shader_uniforms.h b/src/video_core/shader/generator/shader_uniforms.h index 2ddd8fd32..64cd8386d 100644 --- a/src/video_core/shader/generator/shader_uniforms.h +++ b/src/video_core/shader/generator/shader_uniforms.h @@ -89,11 +89,14 @@ struct PicaUniformsData { }; struct VSUniformData { - bool vr_use_immersive_mode; bool enable_clip1; alignas(16) Common::Vec4f clip_coef; + + //VR data at the end to ensure it doesn't interfere with existing uniforms' alignment + alignas(4) float vr_immersive_mode_factor; + alignas(16) Common::Vec3f vr_position; }; -static_assert(sizeof(VSUniformData) == 32, +static_assert(sizeof(VSUniformData) == 64, "The size of the VSUniformData does not match the structure in the shader"); static_assert(sizeof(VSUniformData) < 16384, "VSUniformData structure must be less than 16kb as per the OpenGL spec"); From e97ca06b3d298c89518ce6779fa073e943577535 Mon Sep 17 00:00:00 2001 From: Simon <6168240+DrBeef@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:37:05 +0000 Subject: [PATCH 3/4] Got proper 6DoF working.. - also allowed depth slider to go to 200% (for 1st person in MK7) - added in the "look down to show" for the lower panel --- .../ui/SettingsFragmentPresenter.java | 3 +- .../main/jni/vr/layers/GameSurfaceLayer.cpp | 42 +++++++++++-- .../src/main/jni/vr/layers/GameSurfaceLayer.h | 9 ++- src/android/app/src/main/jni/vr/vr_main.cpp | 31 ++++++---- src/android/app/src/main/jni/vr/vr_settings.h | 1 + .../app/src/main/res/values/strings.xml | 1 + .../shader/generator/glsl_shader_gen.cpp | 59 ++++++++----------- 7 files changed, 91 insertions(+), 55 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java index 1e8798fec..dc4fa2d86 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java @@ -387,7 +387,7 @@ public final class SettingsFragmentPresenter { sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_DISK_SHADER_CACHE, Settings.SECTION_RENDERER, R.string.use_disk_shader_cache, R.string.use_disk_shader_cache_description, true, useDiskShaderCache)); sl.add(new HeaderSetting(null, null, R.string.stereoscopy, 0)); - sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, Settings.SECTION_RENDERER, R.string.factor3d, R.string.factor3d_description, 0, 100, "%", 50, factor3d)); + sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, Settings.SECTION_RENDERER, R.string.factor3d, R.string.factor3d_description, 0, 200, "%", 50, factor3d)); sl.add(new HeaderSetting(null, null, R.string.utility, 0)); sl.add(new CheckBoxSetting(SettingsFile.KEY_DUMP_TEXTURES, Settings.SECTION_UTILITY, R.string.dump_textures, R.string.dump_textures_description, false, dumpTextures)); @@ -437,6 +437,7 @@ public final class SettingsFragmentPresenter { sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_ENVIRONMENT, Settings.SECTION_VR, R.string.vr_background, 0, R.array.vrBackgroundNames, R.array.vrBackgroundValues, VRUtils.getHMDType() == VRUtils.HMDType.QUEST3.getValue() ? 1 : 2, vrEnvironment)); sl.add(new CheckBoxSetting(SettingsFile.KEY_VR_EXTRA_PERFORMANCE_MODE, Settings.SECTION_VR, R.string.vr_extra_performance_mode, R.string.vr_extra_performance_mode_description, false, vrExtraPerformanceMode)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_CPU_LEVEL, Settings.SECTION_VR, R.string.vr_cpu_level, R.string.vr_cpu_level_description, R.array.vrCpuLevelNames, R.array.vrCpuLevelValues, 3, vrCpuLevel)); + sl.add(new HeaderSetting(null, null, R.string.immersive_mode, 0)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE, Settings.SECTION_VR, R.string.vr_immersive_mode_title, R.string.vr_immersive_mode_description, R.array.vrImmersiveModeNames, R.array.vrImmersiveModeValues, 0, vrImmersiveMode)); sl.add(new SliderSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_FACTOR, Settings.SECTION_VR, R.string.vr_immersive_pos_factor_title, R.string.vr_immersive_pos_factor_description, 0, 40, "X", 0, vrImmersivePositionalFactor)); } diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 849839a48..45413583c 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -30,7 +30,7 @@ License : Licensed under GPLv3 or any later version. namespace { -constexpr float lowerPanelScaleFactor = 0.75f; +constexpr float defaultLowerPanelScaleFactor = 0.75f; const std::vector immersiveLevelFactor = {1.0f, 5.0f, 3.0f}; @@ -232,11 +232,10 @@ GameSurfaceLayer::GameSurfaceLayer(const XrVector3f&& position, JNIEnv* env, job env_(env), activityObject_(activityObject) { - assert(immersiveMode_ == 0 || immersiveMode_ == 1); if (immersiveMode_ > 0) { ALOGI("Using VR immersive mode {}", immersiveMode_); topPanelFromWorld_.position.z = lowerPanelFromWorld_.position.z; - lowerPanelFromWorld_.position.y = -1.0f - (0.5f * (immersiveMode_ - 1)); + lowerPanelFromWorld_.position.y = -1.0f; } const int32_t initializationStatus = Init(activityObject, position, session); if (initializationStatus < 0) { @@ -335,6 +334,37 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector 0.0f) + { + lowerPanelScaleFactor /= 1.0f + panelZoomSpeed; + if (lowerPanelScaleFactor < panelZoomSpeed) + { + lowerPanelScaleFactor = 0.0f; + } + } + + // Create the Lower Display Panel (flat touchscreen) // When citra is in stereo mode, this panel is also rendered in stereo (i.e. // twice), but the image is mono. Therefore, take the right half of the @@ -342,7 +372,7 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector 0.0f) { const uint32_t cropHoriz = 90 * resolutionFactor_; XrCompositionLayerQuad layer = {}; @@ -371,8 +401,8 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector& layers, - uint32_t& layerCount, const bool showLowerPanel) const; + uint32_t& layerCount, const bool visibleLowerPanel) const; /** Given an origin, direction of a ray, * returns the coordinates of where the ray will intersects @@ -171,6 +171,11 @@ private: // - Rendering the top-screen and bottom screen separately. const uint32_t immersiveMode_; + // Used to nicely present the lower panel when in toggleable mode. + // Instead of it just appearing instantly, it emerges in a hopefully pleasant + // fashion + mutable float lowerPanelScaleFactor = 0.0f; + //============================ // JNI objects JNIEnv* env_ = nullptr; diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index e360e64cc..e50d5e1bd 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -487,28 +487,35 @@ private: gOpenXr->headLocation = {XR_TYPE_SPACE_LOCATION}; OXR(xrLocateSpace(gOpenXr->viewSpace_, gOpenXr->headSpace_, frameState.predictedDisplayTime, &gOpenXr->headLocation)); - bool showLowerPanel = false; + bool showLowerPanel = true; // Push the HMD position through to the Rasterizer to pass on to the VS Uniform if (VideoCore::g_renderer && VideoCore::g_renderer->Rasterizer()) { //I am confused... but GetRollInRadians appears to return pitch float pitch = XrMath::Quatf::GetRollInRadians(gOpenXr->headLocation.pose.orientation); - //Only use position if it is enabled, and the user is not looking at the lower panel - if ((VRSettings::values.vr_immersive_positional_factor > 0) && (pitch > -0.35f)) - { - Common::Vec3f position{-gOpenXr->headLocation.pose.position.y, - gOpenXr->headLocation.pose.position.x, - 0.0f}; //gOpenXr->headLocation.pose.position.z}; - For some reason Z doesn't affect the actual Z position! - position *= VRSettings::values.vr_immersive_positional_factor; - VideoCore::g_renderer->Rasterizer()->SetVRData(position); - } - else + if ((VRSettings::values.vr_immersive_positional_factor == 0) || + (pitch < -0.35f)) { //Disable position when looking down at the lower panel Common::Vec3f position = {}; VideoCore::g_renderer->Rasterizer()->SetVRData(position); - showLowerPanel = true; + } + else + { + //Only use position if it is enabled, and the user is not looking at the lower panel + Common::Vec3f position{-gOpenXr->headLocation.pose.position.y, + gOpenXr->headLocation.pose.position.x, + -gOpenXr->headLocation.pose.position.z};// - For some reason Z doesn't affect the actual Z position! + + ALOGI("gOpenXr->headLocation Position : {}.{}.{}", + position.x, + position.y, + position.z); + + position *= VRSettings::values.vr_immersive_positional_factor; + VideoCore::g_renderer->Rasterizer()->SetVRData(position); + showLowerPanel = false; } } diff --git a/src/android/app/src/main/jni/vr/vr_settings.h b/src/android/app/src/main/jni/vr/vr_settings.h index 4ddee96f6..a6773a5fe 100644 --- a/src/android/app/src/main/jni/vr/vr_settings.h +++ b/src/android/app/src/main/jni/vr/vr_settings.h @@ -42,6 +42,7 @@ struct Values { uint32_t resolution_factor = 0; int32_t vr_environment = 0; int32_t vr_immersive_mode = 0; + int32_t vr_immersive_positional_factor = 0; bool extra_performance_mode_enabled = false; } extern values; diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 23d46640a..aaa581c51 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -300,6 +300,7 @@ VR Extra Performance Mode CPU level Higher levels may improve audio/emulation performance, but will drain the battery faster + Immersive Mode [WARNING: Graphics Artifacts] Immersive Mode (Experimental) Extends the top screen around a cylinder for immersive gameplay Immersive Mode Positional Movement Factor diff --git a/src/video_core/shader/generator/glsl_shader_gen.cpp b/src/video_core/shader/generator/glsl_shader_gen.cpp index a9738f853..c42666d1a 100644 --- a/src/video_core/shader/generator/glsl_shader_gen.cpp +++ b/src/video_core/shader/generator/glsl_shader_gen.cpp @@ -1642,6 +1642,28 @@ do { return out; } + +static std::string GenerateGLPositionAndGLClipDistanceBlock(bool use_clip_planes) +{ + std::string out; + + out+= "\n gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w - vr_position.z);\n"; + + if (use_clip_planes) + { + out += R"( + gl_ClipDistance[0] = -vtx_pos.z; // fixed PICA clipping plane z <= 0 + if (enable_clip1) { + gl_ClipDistance[1] = dot(clip_coef, vtx_pos); + } else { + gl_ClipDistance[1] = 0.0; + } + )"; + } + + return out; +} + std::string GenerateTrivialVertexShader(bool use_clip_planes, bool separable_shader) { std::string out; if (separable_shader) { @@ -1684,26 +1706,13 @@ void main() { } )"; - out+= "\ngl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; - - if (use_clip_planes) { - out += R"( - gl_ClipDistance[0] = -vtx_pos.z; // fixed PICA clipping plane z <= 0 - if (enable_clip1) { - gl_ClipDistance[1] = dot(clip_coef, vtx_pos); - } else { - gl_ClipDistance[1] = 0.0; - } - )"; - } + out += GenerateGLPositionAndGLClipDistanceBlock(use_clip_planes); out += "}\n"; return out; } - - std::string_view MakeLoadPrefix(AttribLoadFlags flag) { if (True(flag & AttribLoadFlags::Float)) { return ""; @@ -1805,16 +1814,7 @@ std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const P out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - out+= " gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; - - if (config.state.use_clip_planes) { - out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 - out += " if (enable_clip1) {\n"; - out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n"; - out += " } else {\n"; - out += " gl_ClipDistance[1] = 0.0;\n"; - out += " }\n\n"; - } + out += GenerateGLPositionAndGLClipDistanceBlock(config.state.use_clip_planes); out += " normquat = GetVertexQuaternion();\n"; out += " vec4 vtx_color = vec4(" + semantic(VSOutputAttributes::COLOR_R) + ", " + @@ -1904,16 +1904,7 @@ struct Vertex { out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - out += " gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w);\n"; - - if (state.use_clip_planes) { - out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 - out += " if (enable_clip1) {\n"; - out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n"; - out += " } else {\n"; - out += " gl_ClipDistance[1] = 0.0;\n"; - out += " }\n\n"; - } + out += GenerateGLPositionAndGLClipDistanceBlock(state.use_clip_planes); out += " vec4 vtx_quat = GetVertexQuaternion(vtx);\n"; out += " normquat = mix(vtx_quat, -vtx_quat, bvec4(quats_opposite));\n\n"; From a20a05f1183a43279f95dbd7bd0545e706b68d58 Mon Sep 17 00:00:00 2001 From: Simon <6168240+DrBeef@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:46:56 +0000 Subject: [PATCH 4/4] Super Immersive Mode True 6DoF at last! --- .../ui/SettingsFragmentPresenter.java | 6 +- .../features/settings/utils/SettingsFile.java | 3 + src/android/app/src/main/jni/config.cpp | 6 + .../main/jni/vr/layers/GameSurfaceLayer.cpp | 116 +++++++----- .../src/main/jni/vr/layers/GameSurfaceLayer.h | 4 +- .../app/src/main/jni/vr/utils/XrMath.h | 165 ++++++++++++++++++ src/android/app/src/main/jni/vr/vr_main.cpp | 111 +++++++++--- src/android/app/src/main/jni/vr/vr_settings.h | 2 + .../app/src/main/res/values/arrays.xml | 16 ++ .../app/src/main/res/values/strings.xml | 4 + src/common/settings.h | 2 + src/video_core/rasterizer_accelerated.cpp | 105 +++++++++-- src/video_core/rasterizer_accelerated.h | 14 +- src/video_core/rasterizer_interface.h | 2 +- .../renderer_opengl/gl_rasterizer.cpp | 6 +- .../renderer_vulkan/vk_rasterizer.cpp | 3 + .../shader/generator/glsl_shader_gen.cpp | 42 ++++- .../shader/generator/shader_uniforms.h | 3 +- 18 files changed, 505 insertions(+), 105 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java index dc4fa2d86..4a18da234 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java @@ -387,7 +387,7 @@ public final class SettingsFragmentPresenter { sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_DISK_SHADER_CACHE, Settings.SECTION_RENDERER, R.string.use_disk_shader_cache, R.string.use_disk_shader_cache_description, true, useDiskShaderCache)); sl.add(new HeaderSetting(null, null, R.string.stereoscopy, 0)); - sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, Settings.SECTION_RENDERER, R.string.factor3d, R.string.factor3d_description, 0, 200, "%", 50, factor3d)); + sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, Settings.SECTION_RENDERER, R.string.factor3d, R.string.factor3d_description, 0, 400, "%", 50, factor3d)); sl.add(new HeaderSetting(null, null, R.string.utility, 0)); sl.add(new CheckBoxSetting(SettingsFile.KEY_DUMP_TEXTURES, Settings.SECTION_UTILITY, R.string.dump_textures, R.string.dump_textures_description, false, dumpTextures)); @@ -434,11 +434,15 @@ public final class SettingsFragmentPresenter { Setting vrCpuLevel = vrSection.getSetting(SettingsFile.KEY_VR_CPU_LEVEL); Setting vrImmersiveMode = vrSection.getSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE); Setting vrImmersivePositionalFactor = vrSection.getSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_FACTOR); + Setting vrImmersivePositionalGameScaler = vrSection.getSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_GAME_SCALER); + Setting vrSIModeRegisterOffset = vrSection.getSetting(SettingsFile.KEY_VR_SI_MODE_REGISTER_OFFSET); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_ENVIRONMENT, Settings.SECTION_VR, R.string.vr_background, 0, R.array.vrBackgroundNames, R.array.vrBackgroundValues, VRUtils.getHMDType() == VRUtils.HMDType.QUEST3.getValue() ? 1 : 2, vrEnvironment)); sl.add(new CheckBoxSetting(SettingsFile.KEY_VR_EXTRA_PERFORMANCE_MODE, Settings.SECTION_VR, R.string.vr_extra_performance_mode, R.string.vr_extra_performance_mode_description, false, vrExtraPerformanceMode)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_CPU_LEVEL, Settings.SECTION_VR, R.string.vr_cpu_level, R.string.vr_cpu_level_description, R.array.vrCpuLevelNames, R.array.vrCpuLevelValues, 3, vrCpuLevel)); sl.add(new HeaderSetting(null, null, R.string.immersive_mode, 0)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_IMMERSIVE_MODE, Settings.SECTION_VR, R.string.vr_immersive_mode_title, R.string.vr_immersive_mode_description, R.array.vrImmersiveModeNames, R.array.vrImmersiveModeValues, 0, vrImmersiveMode)); + sl.add(new SliderSetting(SettingsFile.KEY_VR_SI_MODE_REGISTER_OFFSET, Settings.SECTION_VR, R.string.vr_si_mode_register_offset_title, R.string.vr_si_mode_register_offset_description, -1, 92, "", -1, vrSIModeRegisterOffset)); sl.add(new SliderSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_FACTOR, Settings.SECTION_VR, R.string.vr_immersive_pos_factor_title, R.string.vr_immersive_pos_factor_description, 0, 40, "X", 0, vrImmersivePositionalFactor)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_VR_IMMERSIVE_POSITIONAL_GAME_SCALER, Settings.SECTION_VR, R.string.vr_immersive_pos_game_scaler_title, R.string.vr_immersive_pos_game_scaler_description, R.array.vrPosFactorGameScalerNames, R.array.vrPosFactorGameScalerValues, 0, vrImmersivePositionalGameScaler)); } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java index 994ad37f4..3df7b1c54 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java @@ -139,6 +139,9 @@ public final class SettingsFile { public static final String KEY_VR_IMMERSIVE_POSITIONAL_FACTOR = "vr_immersive_positional_factor"; + public static final String KEY_VR_IMMERSIVE_POSITIONAL_GAME_SCALER = "vr_immersive_positional_game_scaler"; + public static final String KEY_VR_SI_MODE_REGISTER_OFFSET = "vr_si_mode_register_offset"; + public static final String KEY_LOG_FILTER = "log_filter"; private static BiMap sectionsMap = new BiMap<>(); diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 14cd0837b..3d211bd01 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -308,9 +308,15 @@ void Config::ReadValues() { VRSettings::values.vr_immersive_mode = sdl2_config->GetInteger( "VR", "vr_immersive_mode", 0); Settings::values.vr_immersive_mode = VRSettings::values.vr_immersive_mode; + VRSettings::values.vr_si_mode_register_offset = sdl2_config->GetInteger( + "VR", "vr_si_mode_register_offset", 0); + Settings::values.vr_si_mode_register_offset = VRSettings::values.vr_si_mode_register_offset; VRSettings::values.vr_immersive_positional_factor = sdl2_config->GetInteger( "VR", "vr_immersive_positional_factor", 0); Settings::values.vr_immersive_positional_factor = VRSettings::values.vr_immersive_positional_factor; + VRSettings::values.vr_immersive_positional_game_scaler = sdl2_config->GetInteger( + "VR", "vr_immersive_positional_game_scaler", 0); + Settings::values.vr_immersive_positional_game_scaler = VRSettings::values.vr_immersive_positional_game_scaler; if (Settings::values.vr_immersive_mode.GetValue() > 0) { LOG_INFO(Config, "VR immersive mode enabled"); diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 45413583c..3867bbeb2 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -31,8 +31,7 @@ License : Licensed under GPLv3 or any later version. namespace { constexpr float defaultLowerPanelScaleFactor = 0.75f; - -const std::vector immersiveLevelFactor = {1.0f, 5.0f, 3.0f}; +constexpr float superImmersiveRadius = 0.5f; /** Used to translate texture coordinates into the corresponding coordinates * on the Android Activity Window. @@ -254,20 +253,55 @@ void GameSurfaceLayer::SetSurface() const { } void GameSurfaceLayer::Frame(const XrSpace& space, std::vector& layers, - uint32_t& layerCount, const bool showLowerPanel) const + uint32_t& layerCount, const XrPosef& headPose, const float& immersiveModeFactor, const bool showLowerPanel) { const uint32_t panelWidth = swapchain_.Width / 2; const uint32_t panelHeight = swapchain_.Height / 2; const double aspectRatio = static_cast(2 * panelWidth) / static_cast(panelHeight); + + /* + * This bit is entirely optional, rather than having the panel appear/disappear it emerge in + * smoothly, however to achieve it I had to make the scale factor mutable, which I appreciate + * might not be following the intention of this class. + * If a mutable class member isn't desired, then just drop this bit and use the visibleLowerPanel + * variable directly. + */ + const auto panelZoomSpeed = 0.15f; + if (showLowerPanel && lowerPanelScaleFactor < defaultLowerPanelScaleFactor) + { + if (lowerPanelScaleFactor == 0.0f) + { + lowerPanelScaleFactor = panelZoomSpeed; + } + else + { + lowerPanelScaleFactor *= 1.0f + panelZoomSpeed; + lowerPanelScaleFactor = std::min(lowerPanelScaleFactor, defaultLowerPanelScaleFactor); + } + } + else if (!showLowerPanel && lowerPanelScaleFactor > 0.0f) + { + lowerPanelScaleFactor /= 1.0f + panelZoomSpeed; + if (lowerPanelScaleFactor < panelZoomSpeed) + { + lowerPanelScaleFactor = 0.0f; + } + } + // Prevent a seam between the top and bottom view constexpr uint32_t verticalBorderTex = 1; const bool useCylinder = (GetCylinderSysprop() != 0) || (immersiveMode_ > 0); if (useCylinder) { - // Create the Top Display Panel (Curved display) for (uint32_t eye = 0; eye < NUM_EYES; eye++) { + XrPosef topPanelFromWorld = topPanelFromWorld_; + if (immersiveMode_ >= 3 && !showLowerPanel) + { + topPanelFromWorld = GetTopPanelFromHeadPose(eye, headPose); + } + XrCompositionLayerCylinderKHR layer = {}; layer.type = XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR; @@ -278,6 +312,12 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector 0.0f) - { - lowerPanelScaleFactor /= 1.0f + panelZoomSpeed; - if (lowerPanelScaleFactor < panelZoomSpeed) - { - lowerPanelScaleFactor = 0.0f; - } - } - // Create the Lower Display Panel (flat touchscreen) // When citra is in stereo mode, this panel is also rendered in stereo (i.e. @@ -389,14 +394,14 @@ void GameSurfaceLayer::Frame(const XrSpace& space, std::vector& layers, - uint32_t& layerCount, const bool visibleLowerPanel) const; + uint32_t& layerCount, const XrPosef& headPose, + const float& immersiveModeFactor, const bool visibleLowerPanel); /** Given an origin, direction of a ray, * returns the coordinates of where the ray will intersects @@ -127,6 +128,7 @@ public: bool GetRayIntersectionWithPanelTopPanel(const XrVector3f& start, const XrVector3f& end, XrVector2f& result2d, XrPosef& result3d) const; void SetTopPanelFromController(const XrVector3f& controllerPosition); + XrPosef GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose); void SetTopPanelFromThumbstick(const float thumbstickY); diff --git a/src/android/app/src/main/jni/vr/utils/XrMath.h b/src/android/app/src/main/jni/vr/utils/XrMath.h index dbfd0fc85..a50c513f5 100644 --- a/src/android/app/src/main/jni/vr/utils/XrMath.h +++ b/src/android/app/src/main/jni/vr/utils/XrMath.h @@ -88,6 +88,112 @@ public: } }; +class Matrixf +{ +public: + static void Identity(XrVector4f mat[4]) { + mat[0] = {1.f, 0.f, 0.f, 0.f}; + mat[1] = {0.f, 1.f, 0.f, 0.f}; + mat[2] = {0.f, 0.f, 1.f, 0.f}; + mat[3] = {0.f, 0.f, 0.f, 1.f}; + } + + static XrVector4f XrVector4f_Multiply(const XrVector4f mat[4], const XrVector4f &v) + { + XrVector4f out; + out.x = mat[0].x * v.x + mat[0].y * v.y + mat[0].z * v.z + mat[0].w * v.w; + out.y = mat[1].x * v.x + mat[1].y * v.y + mat[1].z * v.z + mat[1].w * v.w; + out.z = mat[2].x * v.x + mat[2].y * v.y + mat[2].z * v.z + mat[2].w * v.w; + out.w = mat[3].x * v.x + mat[3].y * v.y + mat[3].z * v.z + mat[3].w * v.w; + return out; + } + + static XrVector3f XrVector3f_Multiply(const XrVector3f mat[3], const XrVector3f &v) + { + XrVector3f out; + out.x = mat[0].x * v.x + mat[0].y * v.y + mat[0].z * v.z; + out.y = mat[1].x * v.x + mat[1].y * v.y + mat[1].z * v.z; + out.z = mat[2].x * v.x + mat[2].y * v.y + mat[2].z * v.z; + return out; + } + + // Returns a 3x3 minor of a 4x4 matrix. + static float ToMinor(const float *matrix, int r0, int r1, int r2, int c0, int c1, int c2) { + return matrix[4 * r0 + c0] * + (matrix[4 * r1 + c1] * matrix[4 * r2 + c2] - matrix[4 * r2 + c1] * matrix[4 * r1 + c2]) - + matrix[4 * r0 + c1] * + (matrix[4 * r1 + c0] * matrix[4 * r2 + c2] - matrix[4 * r2 + c0] * matrix[4 * r1 + c2]) + + matrix[4 * r0 + c2] * + (matrix[4 * r1 + c0] * matrix[4 * r2 + c1] - matrix[4 * r2 + c0] * matrix[4 * r1 + c1]); + } + + static void ToInverse(const XrVector4f in[4], XrVector4f out[4]) { + float *matrix = (float*)in; + float *inv_mat = (float*)out; + const float rcpDet = + 1.0f / (matrix[0] * ToMinor(matrix, 1, 2, 3, 1, 2, 3) - + matrix[1] * ToMinor(matrix, 1, 2, 3, 0, 2, 3) + + matrix[2] * ToMinor(matrix, 1, 2, 3, 0, 1, 3) - + matrix[3] * ToMinor(matrix, 1, 2, 3, 0, 1, 2)); + + inv_mat[0] = ToMinor(matrix, 1, 2, 3, 1, 2, 3) * rcpDet; + inv_mat[1] = -ToMinor(matrix, 0, 2, 3, 1, 2, 3) * rcpDet; + inv_mat[2] = ToMinor(matrix, 0, 1, 3, 1, 2, 3) * rcpDet; + inv_mat[3] = -ToMinor(matrix, 0, 1, 2, 1, 2, 3) * rcpDet; + inv_mat[4] = -ToMinor(matrix, 1, 2, 3, 0, 2, 3) * rcpDet; + inv_mat[5] = ToMinor(matrix, 0, 2, 3, 0, 2, 3) * rcpDet; + inv_mat[6] = -ToMinor(matrix, 0, 1, 3, 0, 2, 3) * rcpDet; + inv_mat[7] = ToMinor(matrix, 0, 1, 2, 0, 2, 3) * rcpDet; + inv_mat[8] = ToMinor(matrix, 1, 2, 3, 0, 1, 3) * rcpDet; + inv_mat[9] = -ToMinor(matrix, 0, 2, 3, 0, 1, 3) * rcpDet; + inv_mat[10] = ToMinor(matrix, 0, 1, 3, 0, 1, 3) * rcpDet; + inv_mat[11] = -ToMinor(matrix, 0, 1, 2, 0, 1, 3) * rcpDet; + inv_mat[12] = -ToMinor(matrix, 1, 2, 3, 0, 1, 2) * rcpDet; + inv_mat[13] = ToMinor(matrix, 0, 2, 3, 0, 1, 2) * rcpDet; + inv_mat[14] = -ToMinor(matrix, 0, 1, 3, 0, 1, 2) * rcpDet; + inv_mat[15] = ToMinor(matrix, 0, 1, 2, 0, 1, 2) * rcpDet; + } + + static void Projection(XrVector4f result[4], const float fov_x, const float fov_y, + const float nearZ, const float farZ) { + float *projectionMatrix = (float*)result; + + float xmin, xmax, ymin, ymax; + float width, height, depth; + + + ymax = nearZ * tan( fov_y ); + ymin = -ymax; + + xmax = nearZ * tan( fov_x ); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + depth = farZ - nearZ; + + projectionMatrix[0] = 2 * nearZ / width; + projectionMatrix[4] = 0; + projectionMatrix[8] = ( xmax + xmin ) / width; + projectionMatrix[12] = 0; + + projectionMatrix[1] = 0; + projectionMatrix[5] = 2 * nearZ / height; + projectionMatrix[9] = ( ymax + ymin ) / height; + projectionMatrix[13] = 0; + + projectionMatrix[2] = 0; + projectionMatrix[6] = 0; + projectionMatrix[10] = -( farZ + nearZ ) / depth; + projectionMatrix[14] = -2 * farZ * nearZ / depth; + + projectionMatrix[3] = 0; + projectionMatrix[7] = 0; + projectionMatrix[11] = -1; + projectionMatrix[15] = 0; + } +}; + class Quatf { public: static XrQuaternionf Identity() { @@ -187,6 +293,65 @@ public: (up.z - forward.y) / s}; } } + + static void ToRotationMatrix(const XrQuaternionf& q, float rotation[16]) { + + float x2 = q.x + q.x; + float y2 = q.y + q.y; + float z2 = q.z + q.z; + float xx2 = q.x * x2; + float xy2 = q.x * y2; + float xz2 = q.x * z2; + float yy2 = q.y * y2; + float yz2 = q.y * z2; + float zz2 = q.z * z2; + float sx2 = q.w * x2; + float sy2 = q.w * y2; + float sz2 = q.w * z2; + + float r[16] = {1 - (yy2 + zz2), xy2 + sz2, xz2 - sy2, 0.f, // column 0 + xy2 - sz2, 1 - (xx2 + zz2), yz2 + sx2, 0.f, // column 1 + xz2 + sy2, yz2 - sx2, 1 - (xx2 + yy2), 0.f, // column 2 + 0.f, 0.f, 0.f, 1};// column 3 + + std::memcpy(rotation, r, sizeof(float ) * 16); + } + + static void ToVectors(const XrQuaternionf& q, XrVector3f& forward, + XrVector3f& right, XrVector3f& up) { + XrVector3f mat[3]; + const float ww = q.w * q.w; + const float xx = q.x * q.x; + const float yy = q.y * q.y; + const float zz = q.z * q.z; + + mat[0] = { + ww + xx - yy - zz, + 2 * (q.x * q.y - q.w * q.z), + 2 * (q.x * q.z + q.w * q.y)}; + + mat[1] = { + 2 * (q.x * q.y + q.w * q.z), + ww - xx + yy - zz, + 2 * (q.y * q.z - q.w * q.x)}; + + mat[2] = { + 2 * (q.x * q.z - q.w * q.y), + 2 * (q.y * q.z + q.w * q.x), + ww - xx - yy + zz}; + + XrVector3f glFlip[3] = {{0, 0, -1}, + {1, 0, 0}, + {0, 1, 0}}; + + XrVector3f f = Matrixf::XrVector3f_Multiply(mat, glFlip[0]); + XrVector3f r = Matrixf::XrVector3f_Multiply(mat, glFlip[1]); + XrVector3f u = Matrixf::XrVector3f_Multiply(mat, glFlip[2]); + + forward = {-f.z, -f.x, f.y}; + right = {-r.z, -r.x, r.y}; + up = {-u.z, -u.x, u.y}; + } }; class Posef { diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index e50d5e1bd..75b9f7c33 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -77,6 +77,8 @@ std::chrono::time_point gOnCreateStartTime; std::atomic gShouldShowErrorMessage = {false}; std::unique_ptr gOpenXr; +const std::vector immersiveScaleFactor = {1.0f, 5.0f, 3.0f, 1.8f}; + void ForwardButtonStateChangeToCitra(JNIEnv* jni, jobject activityObject, jmethodID forwardVRInputMethodID, const int androidButtonCode, const XrBool32 xrButtonState) { @@ -487,40 +489,90 @@ private: gOpenXr->headLocation = {XR_TYPE_SPACE_LOCATION}; OXR(xrLocateSpace(gOpenXr->viewSpace_, gOpenXr->headSpace_, frameState.predictedDisplayTime, &gOpenXr->headLocation)); - bool showLowerPanel = true; - // Push the HMD position through to the Rasterizer to pass on to the VS Uniform - if (VideoCore::g_renderer && VideoCore::g_renderer->Rasterizer()) + mInputStateFrame.SyncHandPoses(gOpenXr->session_, mInputStateStatic, gOpenXr->localSpace_, + frameState.predictedDisplayTime); + + //XrMath::Vector3f:: + XrVector3f leftVec = { + gOpenXr->headLocation.pose.position.x - mInputStateFrame.mHandPositions[InputStateFrame::LEFT_CONTROLLER].pose.position.x, + gOpenXr->headLocation.pose.position.y - mInputStateFrame.mHandPositions[InputStateFrame::LEFT_CONTROLLER].pose.position.y, + gOpenXr->headLocation.pose.position.z - mInputStateFrame.mHandPositions[InputStateFrame::LEFT_CONTROLLER].pose.position.z, + }; + const float lengthLeft = XrMath::Vector3f::Length(leftVec); + XrVector3f rightVec = { + gOpenXr->headLocation.pose.position.x - mInputStateFrame.mHandPositions[InputStateFrame::RIGHT_CONTROLLER].pose.position.x, + gOpenXr->headLocation.pose.position.y - mInputStateFrame.mHandPositions[InputStateFrame::RIGHT_CONTROLLER].pose.position.y, + gOpenXr->headLocation.pose.position.z - mInputStateFrame.mHandPositions[InputStateFrame::RIGHT_CONTROLLER].pose.position.z, + }; + const float lengthRight = XrMath::Vector3f::Length(rightVec); + const float length = std::min(lengthLeft, lengthRight); + + // This block is for testing which uinform offset is needed + // for a given game to implement new super-immersive profiles if needed + static bool increase = false; + static int uoffset = -1; { - //I am confused... but GetRollInRadians appears to return pitch - float pitch = XrMath::Quatf::GetRollInRadians(gOpenXr->headLocation.pose.orientation); - - if ((VRSettings::values.vr_immersive_positional_factor == 0) || - (pitch < -0.35f)) + if (VRSettings::values.vr_immersive_mode > 90) { - //Disable position when looking down at the lower panel - Common::Vec3f position = {}; - VideoCore::g_renderer->Rasterizer()->SetVRData(position); - } - else - { - //Only use position if it is enabled, and the user is not looking at the lower panel - Common::Vec3f position{-gOpenXr->headLocation.pose.position.y, - gOpenXr->headLocation.pose.position.x, - -gOpenXr->headLocation.pose.position.z};// - For some reason Z doesn't affect the actual Z position! + if (mInputStateFrame.mThumbrestTouchState[InputStateFrame::RIGHT_CONTROLLER].currentState) + { + if (increase) + { + ++uoffset; + increase = false; + } - ALOGI("gOpenXr->headLocation Position : {}.{}.{}", - position.x, - position.y, - position.z); - - position *= VRSettings::values.vr_immersive_positional_factor; - VideoCore::g_renderer->Rasterizer()->SetVRData(position); - showLowerPanel = false; + //There are 96 Vec4f; since we are applying 4 of them at a time we need to loop + // after 92 + if (uoffset > 92) + { + uoffset = 0; + } + } + else + { + increase = true; + } } } - mInputStateFrame.SyncHandPoses(gOpenXr->session_, mInputStateStatic, gOpenXr->localSpace_, - frameState.predictedDisplayTime); + bool showLowerPanel = true; + float immersiveModeFactor = (VRSettings::values.vr_immersive_mode <= 2) ? immersiveScaleFactor[VRSettings::values.vr_immersive_mode] : immersiveScaleFactor[3]; + // Push the HMD position through to the Rasterizer to pass on to the VS Uniform + if (VideoCore::g_renderer && VideoCore::g_renderer->Rasterizer()) + { + if ((VRSettings::values.vr_immersive_positional_factor == 0) || + //If in Normal immersive modes then look down for the lower panel to reveal itself (for some reason the Roll function returns pitch) + (VRSettings::values.vr_immersive_mode <= 2 && XrMath::Quatf::GetRollInRadians(gOpenXr->headLocation.pose.orientation) < -MATH_FLOAT_PI / 8.0f) || + //If in "super immersive" mode then put controller next to head in order to disable the mode temporarily + (VRSettings::values.vr_immersive_mode >= 3 && length < 0.2)) + { + XrVector4f identity[4] = {}; + XrMath::Matrixf::Identity(identity); + immersiveModeFactor = 1.0f; + VideoCore::g_renderer->Rasterizer()->SetVRData(1, immersiveModeFactor, -1, (float*)identity); + } + else + { + XrVector4f transform[4] = {}; + XrMath::Quatf::ToRotationMatrix(gOpenXr->headLocation.pose.orientation, (float*)transform); + + //Calculate the inverse + XrVector4f inv_transform[4]; + XrMath::Matrixf::ToInverse(transform, inv_transform); + + XrQuaternionf invertedOrientation = XrMath::Quatf::Inverted(gOpenXr->headLocation.pose.orientation); + XrVector3f position = XrMath::Quatf::Rotate(invertedOrientation, gOpenXr->headLocation.pose.position); + + float posScaler = powf(10.f, VRSettings::values.vr_immersive_positional_game_scaler); + inv_transform[3].x = -position.x * VRSettings::values.vr_immersive_positional_factor * posScaler; + inv_transform[3].y = -position.y * VRSettings::values.vr_immersive_positional_factor * posScaler; + inv_transform[3].z = -position.z * VRSettings::values.vr_immersive_positional_factor * posScaler; + + VideoCore::g_renderer->Rasterizer()->SetVRData(VRSettings::values.vr_immersive_mode, immersiveModeFactor, uoffset, (float*)inv_transform); + showLowerPanel = false; + } + } ////////////////////////////////////////////////// // Set the compositor layers for this frame. @@ -537,7 +589,8 @@ private: layers[layerCount++].Passthrough = passthroughLayer; } - mGameSurfaceLayer->Frame(gOpenXr->localSpace_, layers, layerCount, showLowerPanel); + mGameSurfaceLayer->Frame(gOpenXr->localSpace_, layers, layerCount, gOpenXr->headLocation.pose, + immersiveModeFactor, showLowerPanel); #if defined(UI_LAYER) if (gShouldShowErrorMessage) { XrCompositionLayerQuad quadLayer = {}; diff --git a/src/android/app/src/main/jni/vr/vr_settings.h b/src/android/app/src/main/jni/vr/vr_settings.h index a6773a5fe..839563637 100644 --- a/src/android/app/src/main/jni/vr/vr_settings.h +++ b/src/android/app/src/main/jni/vr/vr_settings.h @@ -42,7 +42,9 @@ struct Values { uint32_t resolution_factor = 0; int32_t vr_environment = 0; int32_t vr_immersive_mode = 0; + int32_t vr_si_mode_register_offset = -1; int32_t vr_immersive_positional_factor = 0; + int32_t vr_immersive_positional_game_scaler = 0; bool extra_performance_mode_enabled = false; } extern values; diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 88c998e0c..5ea2cf394 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -229,11 +229,27 @@ Off Large - Low Resolution Higher Resolution + Super Immersive - Profile 1 + Super Immersive - Profile 2 (Set Register Offset for game) 0 1 2 + 3 + 4 + + + + 1x + 10x + 100x + + + + 0 + 1 + 2 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index aaa581c51..ac295f372 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -303,8 +303,12 @@ Immersive Mode [WARNING: Graphics Artifacts] Immersive Mode (Experimental) Extends the top screen around a cylinder for immersive gameplay + Super Immersive Mode Profile 2 Register Offset + Sets the register offset used for the view when using Super Immersive Mode Profile 2 - varies by game Immersive Mode Positional Movement Factor Adjusts how much movement left/right/up/down affects the game camera (0 = disabled) + Adjusts Positional Movement Factor by a scaler + Adjusts how much movement left/right/up/down affects the game camera, some games required larger values, so this multiplies the positional factor DANGER: not all games/levels look good in immersive mode. Large visual artifacts are common when this option is selected WARNING: this WILL cause visual artifacting in most content diff --git a/src/common/settings.h b/src/common/settings.h index fa9bb0c20..188c143e8 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -528,7 +528,9 @@ struct Values { // VR Setting vr_immersive_mode{0, "vr_immersive_mode"}; + Setting vr_si_mode_register_offset{-1, "vr_si_mode_register_offset"}; Setting vr_immersive_positional_factor{0, "vr_immersive_positional_factor"}; + Setting vr_immersive_positional_game_scaler{0, "vr_immersive_positional_game_scaler"}; }; extern Values values; diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index 61cc60246..e59c06183 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -13,8 +13,6 @@ namespace VideoCore { using Pica::f24; -const std::vector immersiveLevelFactor = {1.0f, 5.0f, 3.0f}; - static Common::Vec4f ColorRGBA8(const u32 color) { const auto rgba = Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF}; @@ -139,7 +137,6 @@ void RasterizerAccelerated::SyncEntireState() { // Sync uniforms SyncClipPlane(); - SyncVRData(); SyncDepthScale(); SyncDepthOffset(); SyncAlphaTest(); @@ -864,19 +861,105 @@ void RasterizerAccelerated::SyncClipPlane() { } } -void RasterizerAccelerated::SyncVRData() { - if (vs_uniform_block_data.data.vr_immersive_mode_factor != immersiveLevelFactor[Settings::values.vr_immersive_mode.GetValue()]) +void RasterizerAccelerated::SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) +{ + if (vs_uniform_block_data.data.vr_immersive_mode_factor != immersiveModeFactor) { - vs_uniform_block_data.data.vr_immersive_mode_factor = immersiveLevelFactor[Settings::values.vr_immersive_mode.GetValue()]; + vs_uniform_block_data.data.vr_immersive_mode_factor = immersiveModeFactor; vs_uniform_block_data.dirty = true; } + + vr_uoffset = uoffset; + vr_immersive_mode = vrImmersiveMode; + std::memcpy(vr_view, view, sizeof(float) * 16); +} + +static void MatrixTranspose(float m[16], const float src[16]) { + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 4; ++j) + { + m[i * 4 + j] = src[j * 4 + i]; + } + } } -void RasterizerAccelerated::SetVRData(Common::Vec3f position) -{ - //Positional Tinkering - vs_uniform_block_data.data.vr_position = position; - vs_uniform_block_data.dirty = true; +static void MatrixMultiply(float m[16], const float a[16], const float b[16]) { + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 4; ++j) + { + m[i * 4 + j] = a[j] * b[i*4] + a[j+4] * b[i*4+1] + a[j+8] * b[i*4+2] + a[j+12] * b[i*4+3]; + } + } } +void RasterizerAccelerated::ApplyVRDataToPicaVSUniforms(Pica::Shader::Generator::VSPicaUniformData &vs_uniforms) +{ + if (vr_immersive_mode) + { + auto &f = vs_uniforms.uniforms.f; + + int viewMatrixIndex = -1; + int mode = vr_immersive_mode; + int matrixMode = vr_immersive_mode; + if (vr_immersive_mode > 9) + { + mode = vr_immersive_mode / 10; + matrixMode = vr_immersive_mode % 10; + } + switch (mode) + { + //OOT / MM specific + case 3: + if (vs_uniforms.uniforms.bools[1].b != 0 // this is essential + && f[0][3] != 1.0) // This fixes the HUD + { + viewMatrixIndex = 4; + } + break; + case 4: + viewMatrixIndex = Settings::values.vr_si_mode_register_offset.GetValue(); + break; + case 9: + //TEST MODE + viewMatrixIndex = vr_uoffset; + break; + default: + viewMatrixIndex = -1; + break; + } + + if (viewMatrixIndex != -1 && vs_uniforms.uniforms.f.size() > viewMatrixIndex) + { + if (matrixMode == 3) + { + f[viewMatrixIndex][0] = vr_view[0]; + f[viewMatrixIndex][1] = vr_view[4]; + f[viewMatrixIndex][2] = vr_view[8]; + f[viewMatrixIndex + 1][0] = vr_view[1]; + f[viewMatrixIndex + 1][1] = vr_view[5]; + f[viewMatrixIndex + 1][2] = vr_view[9]; + f[viewMatrixIndex + 2][0] = vr_view[2]; + f[viewMatrixIndex + 2][1] = vr_view[6]; + f[viewMatrixIndex + 2][2] = vr_view[10]; + } + else if (matrixMode >= 4) + { + float v[16], v2[16], v3[16]; + MatrixTranspose(v, &f[viewMatrixIndex].x); + std::memcpy(v2, vr_view, sizeof(float) * 16); + v2[12] = v2[13] = v2[14] = 0.f; + MatrixMultiply(v3, v2, v); + MatrixTranspose(&f[viewMatrixIndex].x, v3); + } + + f[viewMatrixIndex][3] += vr_view[12]; + f[viewMatrixIndex + 1][3] += vr_view[13]; + f[viewMatrixIndex + 2][3] += vr_view[14]; + } + } +} + + } // namespace VideoCore diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h index eede3828d..52082a1cc 100644 --- a/src/video_core/rasterizer_accelerated.h +++ b/src/video_core/rasterizer_accelerated.h @@ -29,7 +29,7 @@ public: void NotifyPicaRegisterChanged(u32 id) override; void SyncEntireState() override; - void SetVRData(Common::Vec3f position) override; + void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) override; protected: /// Sync fixed-function pipeline state @@ -105,9 +105,6 @@ protected: /// Syncs the clip plane state to match the PICA register void SyncClipPlane(); - /// Syncs the VR data - void SyncVRData(); - protected: /// Structure that keeps tracks of the vertex shader uniform state struct VSUniformBlockData { @@ -170,5 +167,14 @@ protected: std::array proctex_alpha_map_data{}; std::array proctex_lut_data{}; std::array proctex_diff_lut_data{}; + + //VR Stuff + u32 vr_uoffset = -1; + u32 vr_immersive_mode; + float vr_view[16] = {}; + +public: + + void ApplyVRDataToPicaVSUniforms(Pica::Shader::Generator::VSPicaUniformData &vs_uniforms); }; } // namespace VideoCore diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index ba5c66e2f..44edfaae9 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -84,6 +84,6 @@ public: virtual void SyncEntireState() {} /// Set VR position data on the rasterizer - virtual void SetVRData(Common::Vec3f position) {} + virtual void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) {} }; } // namespace VideoCore diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8818820a9..5694c24f6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -434,9 +434,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { SyncAndUploadLUTs(); SyncAndUploadLUTsLF(); - //Sync VR data if needed - SyncVRData(); - // Sync the uniform data UploadUniforms(accelerate); @@ -1133,6 +1130,9 @@ void RasterizerOpenGL::UploadUniforms(bool accelerate_draw) { if (sync_vs_pica) { VSPicaUniformData vs_uniforms; vs_uniforms.uniforms.SetFromRegs(regs.vs, Pica::g_state.vs); + + ApplyVRDataToPicaVSUniforms(vs_uniforms); + std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms)); glBindBufferRange(GL_UNIFORM_BUFFER, UniformBindings::VSPicaData, uniform_buffer.GetHandle(), offset + used_bytes, sizeof(vs_uniforms)); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 68091a77f..afa0f4a0d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1133,6 +1133,9 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) { if (sync_vs_pica) { VSPicaUniformData vs_uniforms; vs_uniforms.uniforms.SetFromRegs(regs.vs, Pica::g_state.vs); + + ApplyVRDataToPicaVSUniforms(vs_uniforms); + std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms)); pipeline_cache.SetBufferOffset(0, offset + used_bytes); diff --git a/src/video_core/shader/generator/glsl_shader_gen.cpp b/src/video_core/shader/generator/glsl_shader_gen.cpp index c42666d1a..a121d42f6 100644 --- a/src/video_core/shader/generator/glsl_shader_gen.cpp +++ b/src/video_core/shader/generator/glsl_shader_gen.cpp @@ -47,7 +47,6 @@ layout (binding = 1, std140) uniform vs_data { vec4 clip_coef; float vr_immersive_mode_factor; - vec3 vr_position; }; )"; @@ -1642,12 +1641,13 @@ do { return out; } - static std::string GenerateGLPositionAndGLClipDistanceBlock(bool use_clip_planes) { std::string out; - - out+= "\n gl_Position = vec4(vtx_pos.x / vr_immersive_mode_factor + vr_position.x, vtx_pos.y / vr_immersive_mode_factor + vr_position.y, -vtx_pos.z - vr_position.z, vtx_pos.w - vr_position.z);\n"; + out += R"( + gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w); + gl_Position.xy /= vr_immersive_mode_factor; + )"; if (use_clip_planes) { @@ -1706,7 +1706,22 @@ void main() { } )"; - out += GenerateGLPositionAndGLClipDistanceBlock(use_clip_planes); + out += R"( + gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w); + gl_Position.xy /= vr_immersive_mode_factor; + )"; + + if (use_clip_planes) + { + out += R"( + gl_ClipDistance[0] = -vtx_pos.z; // fixed PICA clipping plane z <= 0 + if (enable_clip1) { + gl_ClipDistance[1] = dot(clip_coef, vtx_pos); + } else { + gl_ClipDistance[1] = 0.0; + } + )"; + } out += "}\n"; @@ -1814,7 +1829,22 @@ std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const P out += " vtx_pos.z = 0.f;\n"; out += " }\n"; - out += GenerateGLPositionAndGLClipDistanceBlock(config.state.use_clip_planes); + out += R"( + gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w); + gl_Position.xy /= vr_immersive_mode_factor; + )"; + + if (config.state.use_clip_planes) + { + out += R"( + gl_ClipDistance[0] = -vtx_pos.z; // fixed PICA clipping plane z <= 0 + if (enable_clip1) { + gl_ClipDistance[1] = dot(clip_coef, vtx_pos); + } else { + gl_ClipDistance[1] = 0.0; + } + )"; + } out += " normquat = GetVertexQuaternion();\n"; out += " vec4 vtx_color = vec4(" + semantic(VSOutputAttributes::COLOR_R) + ", " + diff --git a/src/video_core/shader/generator/shader_uniforms.h b/src/video_core/shader/generator/shader_uniforms.h index 64cd8386d..bf53c50df 100644 --- a/src/video_core/shader/generator/shader_uniforms.h +++ b/src/video_core/shader/generator/shader_uniforms.h @@ -94,9 +94,8 @@ struct VSUniformData { //VR data at the end to ensure it doesn't interfere with existing uniforms' alignment alignas(4) float vr_immersive_mode_factor; - alignas(16) Common::Vec3f vr_position; }; -static_assert(sizeof(VSUniformData) == 64, +static_assert(sizeof(VSUniformData) == 48, "The size of the VSUniformData does not match the structure in the shader"); static_assert(sizeof(VSUniformData) < 16384, "VSUniformData structure must be less than 16kb as per the OpenGL spec");