diff --git a/CMake/lus-cvars.cmake b/CMake/lus-cvars.cmake index c64782e75ea..6b9f4e4d457 100644 --- a/CMake/lus-cvars.cmake +++ b/CMake/lus-cvars.cmake @@ -2,6 +2,10 @@ set(CVAR_VSYNC_ENABLED "${CVAR_PREFIX_SETTING}.VsyncEnabled" CACHE STRING "") set(CVAR_Z_FIGHTING_MODE "${CVAR_PREFIX_SETTING}.ZFightingMode" CACHE STRING "") set(CVAR_INTERNAL_RESOLUTION "${CVAR_PREFIX_SETTING}.InternalResolution" CACHE STRING "") set(CVAR_MSAA_VALUE "${CVAR_PREFIX_SETTING}.MSAAValue" CACHE STRING "") +set(CVAR_STEREO_MODE "${CVAR_PREFIX_SETTING}.Stereo.Mode" CACHE STRING "") +set(CVAR_STEREO_SEPARATION "${CVAR_PREFIX_SETTING}.Stereo.Separation" CACHE STRING "") +set(CVAR_STEREO_CONVERGENCE "${CVAR_PREFIX_SETTING}.Stereo.Convergence" CACHE STRING "") +set(CVAR_STEREO_UI_DEPTH "${CVAR_PREFIX_SETTING}.Stereo.UiDepth" CACHE STRING "") set(CVAR_SDL_WINDOWED_FULLSCREEN "${CVAR_PREFIX_SETTING}.SdlWindowedFullscreen" CACHE STRING "") set(CVAR_TEXTURE_FILTER "${CVAR_PREFIX_SETTING}.TextureFilter" CACHE STRING "") set(CVAR_IMGUI_CONTROLLER_NAV "${CVAR_PREFIX_SETTING}.ControlNav" CACHE STRING "") diff --git a/libultraship b/libultraship index bbb565bd937..f3168fb0f5c 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit bbb565bd9376751889bda36856a05ebb0ab9dc4b +Subproject commit f3168fb0f5ceb0b6f591e09f3840629a58fa824f diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp index c494f3bdcfc..5b3038bfd37 100644 --- a/soh/soh/SohGui/ResolutionEditor.cpp +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -37,6 +37,13 @@ std::map aspectRatioPresetLabels = { { 0, "Off" }, { 4, "Nintendo 3DS (5:3)" }, { 5, "16:10 (8:5)" }, { 6, "Ultrawide (21:9)" } }; +static const std::map stereoRenderingMap = { + { static_cast(Fast::StereoMode::Off), "Off" }, + { static_cast(Fast::StereoMode::HalfSBS), "Half SBS (Side-by-Side)" }, + { static_cast(Fast::StereoMode::FullSBS), "Full SBS (Side-by-Side)" }, + { static_cast(Fast::StereoMode::HalfTAB), "Half TAB (Top-and-Bottom)" }, + { static_cast(Fast::StereoMode::FullTAB), "Full TAB (Top-and-Bottom)" }, +}; const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f }; const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f }; const int default_aspectRatio = 1; // Default combo list option @@ -346,6 +353,67 @@ void ResolutionCustomWidget(WidgetInfo& info) { } // End of additional settings UIWidgets::PopStyleHeader(); + UIWidgets::PushStyleHeader(THEME_COLOR); + if (ImGui::CollapsingHeader("Stereoscopic 3D Settings")) { + if (UIWidgets::CVarCombobox( + "Stereo Output", CVAR_STEREO_MODE, stereoRenderingMap, + UIWidgets::ComboboxOptions() + .Tooltip("The output format of stereoscopic rendering.") + .DefaultIndex(static_cast(Fast::StereoMode::Off)) + .Color(THEME_COLOR))) { + Ship::Context::GetRawInstance()->GetWindow()->SetStereoMode( + CVarGetInteger(CVAR_STEREO_MODE, static_cast(Fast::StereoMode::Off))); + } + + const bool stereoDisabled = + CVarGetInteger(CVAR_STEREO_MODE, static_cast(Fast::StereoMode::Off)) == + static_cast(Fast::StereoMode::Off); + ImGui::BeginDisabled(stereoDisabled); + + if (UIWidgets::CVarSliderFloat( + "Separation: %.0f", CVAR_STEREO_SEPARATION, + UIWidgets::FloatSliderOptions() + .Tooltip("Controls maximum depth of the image, default is 20. \n" + "Higher values increase stereo depth but will add eyes strain.") + .Min(0.0f) + .Max(100.0f) + .Step(1.0f) + .DefaultValue(20.0f) + .Format("%.0f") + .Color(THEME_COLOR))) { + Ship::Context::GetRawInstance()->GetWindow()->SetStereoSeparation(CVarGetFloat(CVAR_STEREO_SEPARATION, 20.0f)); + } + if (UIWidgets::CVarSliderFloat( + "Convergence: %.0f", CVAR_STEREO_CONVERGENCE, + UIWidgets::FloatSliderOptions() + .Tooltip("Controls where that depth sits relative to the screen, default is 20. \n" + "Higher values shifts the whole 3D scene closer to the viewer.") + .Min(0.0f) + .Max(100.0f) + .Step(1.0f) + .DefaultValue(20.0f) + .Format("%.0f") + .Color(THEME_COLOR))) { + Ship::Context::GetRawInstance()->GetWindow()->SetStereoConvergence( + CVarGetFloat(CVAR_STEREO_CONVERGENCE, 20.0f)); + } + if (UIWidgets::CVarSliderFloat( + "UI Depth: %.0f", CVAR_STEREO_UI_DEPTH, + UIWidgets::FloatSliderOptions() + .Tooltip("Moves UI in stereo depth, default is 0.") + .Min(-50.0f) + .Max(50.0f) + .Step(1.0f) + .DefaultValue(0.0f) + .Format("%.0f") + .Color(THEME_COLOR))) { + Ship::Context::GetRawInstance()->GetWindow()->SetStereoUiDepth( + CVarGetFloat(CVAR_STEREO_UI_DEPTH, 0.0f)); + } + ImGui::EndDisabled(); + } + UIWidgets::PopStyleHeader(); + // Clamp and update the cvars that don't use UIWidgets if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { if (update[UPDATE_aspectRatioX]) { diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 843fa301ee9..d6d92b90f3c 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -1531,8 +1531,12 @@ void Play_Draw(PlayState* play) { Vec3f quakeOffset; Camera_GetSkyboxOffset(&quakeOffset, GET_ACTIVE_CAM(play)); + // Stereo: apply the shared prerendered-background depth to this late indoor panorama. + gSPSetExtraGeometryMode(POLY_OPA_DISP++, G_EX_STEREO_BACKGROUND); SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, 0, play->view.eye.x + quakeOffset.x, play->view.eye.y + quakeOffset.y, play->view.eye.z + quakeOffset.z); + // Stereo: restore normal sky/world depth after the indoor panorama. + gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_STEREO_BACKGROUND); } } diff --git a/soh/src/code/z_room.c b/soh/src/code/z_room.c index 68bb0e5b14f..9e5525c8fe2 100644 --- a/soh/src/code/z_room.c +++ b/soh/src/code/z_room.c @@ -263,6 +263,9 @@ void Room_DrawBackground2D(Gfx** gfxP, void* tex, void* tlut, u16 width, u16 hei Gfx* gfx = *gfxP; uObjBg* bg; + // Stereo: apply the shared screen-space depth used by all prerendered 2D backgrounds. + gSPSetExtraGeometryMode(gfx++, G_EX_STEREO_BACKGROUND); + bg = (uObjBg*)(gfx + 1); gSPBranchList(gfx, (Gfx*)(bg + 1)); @@ -341,6 +344,8 @@ void Room_DrawBackground2D(Gfx** gfxP, void* tex, void* tlut, u16 width, u16 hei } gDPPipeSync(gfx++); + // Stereo: do not leak the prerendered-background depth override into later S2DEX or UI draws. + gSPClearExtraGeometryMode(gfx++, G_EX_STEREO_BACKGROUND); *gfxP = gfx; } diff --git a/soh/src/code/z_vr_box_draw.c b/soh/src/code/z_vr_box_draw.c index 9eb539f8592..1c04abe7225 100644 --- a/soh/src/code/z_vr_box_draw.c +++ b/soh/src/code/z_vr_box_draw.c @@ -27,6 +27,8 @@ void SkyboxDraw_Draw(SkyboxContext* skyboxCtx, GraphicsContext* gfxCtx, s16 skyb gDPSetPrimColor(POLY_OPA_DISP++, 0x00, 0x00, 0, 0, 0, blend); gSPTexture(POLY_OPA_DISP++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON); + // Stereo: keep the camera-centered sky at maximum (horizon) depth. + gSPSetExtraGeometryMode(POLY_OPA_DISP++, G_EX_STEREO_HORIZON); sSkyboxDrawMatrix = Graph_Alloc(gfxCtx, sizeof(Mtx)); @@ -91,6 +93,8 @@ void SkyboxDraw_Draw(SkyboxContext* skyboxCtx, GraphicsContext* gfxCtx, s16 skyb } } + // Stereo: restore normal finite-depth projection for subsequent world geometry. + gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_STEREO_HORIZON); gDPPipeSync(POLY_OPA_DISP++); // gsSPShaderTest2(POLY_OPA_DISP++);