From 45a52cff90a4d615f7f2a3aaeca8694e12000562 Mon Sep 17 00:00:00 2001 From: bicarus-dev <202771338+bicarus-dev@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:21:05 -0700 Subject: [PATCH] sdvx: landscape mode (#277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Link to GitHub Issue, if one exists n/a ## Description of change Add `SDVX Landscape Mode` option, allowing you to play SDVX in landscape monitor orientation, with black borders on left and right - similar to how EAC does it. This is meant to be an alternative to what people do today, which is launching in windowed mode. By default, with just `-sdvxlandscape` enabled, EG will launch at 1920x1080 (transposed from the original 1080x1920 resolution). It is possible to combine with `-forceres` to launch at higher resolutions. At some point though, when rendering at large resolutions past 1440p, at some point scaling may break due to #276. Disabled on 32-bit SDVX since dx9 `CreateDeviceEx` call is required; `CreateDevice` won't work. For the same reason this isn't being made a generic option for museca/rb/etc. In theory you can supply `-sdvxlandscape` to games like IIDX and play landscape games in portrait mode, although again, scaling my break due to #276. Also, add new options (bottomleft/right) for subscreen overlay position so that they don't overlap with the main screen in landscape mode. ## Compiling 🙂 ## Testing Tested a couple versions of VW and EG. --- src/spice2x/games/sdvx/sdvx.h | 4 +- .../graphics/backends/d3d9/d3d9_backend.cpp | 21 +++-- .../graphics/backends/d3d9/d3d9_device.cpp | 86 ++++++++++++------- src/spice2x/hooks/graphics/graphics.cpp | 1 + src/spice2x/hooks/graphics/graphics.h | 1 + src/spice2x/launcher/launcher.cpp | 31 +++++-- src/spice2x/launcher/options.cpp | 27 +++++- src/spice2x/launcher/options.h | 1 + src/spice2x/overlay/windows/sdvx_sub.cpp | 28 +++++- 9 files changed, 148 insertions(+), 52 deletions(-) diff --git a/src/spice2x/games/sdvx/sdvx.h b/src/spice2x/games/sdvx/sdvx.h index 0ce9f5e..86faed4 100644 --- a/src/spice2x/games/sdvx/sdvx.h +++ b/src/spice2x/games/sdvx/sdvx.h @@ -11,7 +11,9 @@ namespace games::sdvx { enum SdvxOverlayPosition { SDVX_OVERLAY_TOP, SDVX_OVERLAY_MIDDLE, - SDVX_OVERLAY_BOTTOM + SDVX_OVERLAY_BOTTOM, + SDVX_OVERLAY_BOTTOM_LEFT, + SDVX_OVERLAY_BOTTOM_RIGHT }; // settings diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index 493b2db..9ce8916 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -853,9 +853,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( for (size_t i = 0; i < num_adapters; i++) { auto *params = &pPresentationParameters[i]; - if (!GRAPHICS_WINDOWED && i == 0 && GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { - params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; - params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; + if (!GRAPHICS_WINDOWED && i == 0) { + if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { + params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; + params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; + } else if (GRAPHICS_FS_FORCE_LANDSCAPE) { + std::swap(params->BackBufferWidth, params->BackBufferHeight); + } } log_info("graphics::d3d9", @@ -882,10 +886,15 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( for (size_t i = 0; i < num_adapters; i++) { auto *fullscreen_display_mode = &pFullscreenDisplayMode[i]; - if (!GRAPHICS_WINDOWED && i == 0 && GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { - fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; - fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; + if (!GRAPHICS_WINDOWED && i == 0) { + if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) { + fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first; + fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second; + } else if (GRAPHICS_FS_FORCE_LANDSCAPE) { + std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height); + } } + log_info("graphics::d3d9", "D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, " diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp index 25b7945..7dbdf0d 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp @@ -718,7 +718,37 @@ void SurfaceHook(IDirect3DDevice9 *pReal) { const int rectTop = 576; const int w = param.BackBufferWidth; const int h = param.BackBufferHeight; + D3DLOCKED_RECT rect; + HRESULT hr; + topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT); + if (GRAPHICS_FS_FORCE_LANDSCAPE) { + const int w_new = h * 9 / 16; + const int x_new = rectLeft + ((w - w_new) / 2); + RECT originRect { + x_new, + rectTop, + (LONG)(x_new + w_new), + (LONG)(rectTop + h), + }; + hr = pReal->StretchRect( + backbuffer, nullptr, + topSurface, &originRect, + D3DTEXF_LINEAR); + if (hr != D3D_OK) { + log_misc("graphics::d3d9", "StretchRect backbuffer failed (forced landscape)"); + } + } else { + // stretch to add top/left offset to avoid going negative + hr = pReal->StretchRect( + backbuffer, nullptr, + topSurface, nullptr, + D3DTEXF_LINEAR); + if (hr != D3D_OK) { + log_misc("graphics::d3d9", "StretchRect backbuffer failed"); + } + } + topSurface->UnlockRect(); RECT targetRect { rectLeft, @@ -727,44 +757,38 @@ void SurfaceHook(IDirect3DDevice9 *pReal) { (LONG)(rectTop + h), }; - // stretch to add top/left offset to avoid going negative - topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT); - auto hr = pReal->StretchRect( - backbuffer, nullptr, - topSurface, &targetRect, - D3DTEXF_LINEAR); - - if (hr != D3D_OK) { - log_misc("graphics::d3d9", "StretchRect backbuffer failed"); - } - topSurface->UnlockRect(); - // do the actual zoom / offset math - auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene]; - if (scene.centered) { - targetRect.right = (w + rectLeft) / scene.scale_x; - targetRect.bottom = (h + rectTop) / scene.scale_y; - const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2; - const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2; - targetRect.top -= deltaH; - targetRect.bottom -= deltaH; - targetRect.left -= deltaW; - targetRect.right -= deltaW; - } else { - targetRect.left -= scene.offset_x; - targetRect.top += scene.offset_y; - targetRect.right = -scene.offset_x; - targetRect.right += (w + rectLeft) / scene.scale_x; - targetRect.bottom = scene.offset_y; - targetRect.bottom += (h + rectTop) / scene.scale_y; + if (cfg::SCREENRESIZE->enable_screen_resize) { + auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene]; + if (scene.centered) { + targetRect.right = (w + rectLeft) / scene.scale_x; + targetRect.bottom = (h + rectTop) / scene.scale_y; + const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2; + const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2; + targetRect.top -= deltaH; + targetRect.bottom -= deltaH; + targetRect.left -= deltaW; + targetRect.right -= deltaW; + } else { + targetRect.left -= scene.offset_x; + targetRect.top += scene.offset_y; + targetRect.right = -scene.offset_x; + targetRect.right += (w + rectLeft) / scene.scale_x; + targetRect.bottom = scene.offset_y; + targetRect.bottom += (h + rectTop) / scene.scale_y; + } } // draw to back buffer backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT); + bool use_linear_filter = true; + if (cfg::SCREENRESIZE->enable_screen_resize) { + use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter; + } hr = pReal->StretchRect( topSurface, &targetRect, backbuffer, nullptr, - cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE); + use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE); backbuffer->UnlockRect(); if (hr != D3D_OK) { log_misc("graphics::d3d9", "StretchRect targetRect failed"); @@ -774,7 +798,7 @@ void SurfaceHook(IDirect3DDevice9 *pReal) { HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() { WRAP_DEBUG; - if (cfg::SCREENRESIZE->enable_screen_resize) { + if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_FORCE_LANDSCAPE) { SurfaceHook(pReal); } diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index f8e4567..128855d 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -68,6 +68,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false; bool SUBSCREEN_FORCE_REDRAW = false; bool D3D9_DEVICE_HOOK_DISABLE = false; std::optional> GRAPHICS_FS_CUSTOM_RESOLUTION; +bool GRAPHICS_FS_FORCE_LANDSCAPE = false; // settings std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146"; diff --git a/src/spice2x/hooks/graphics/graphics.h b/src/spice2x/hooks/graphics/graphics.h index 6b334d7..907c9e0 100644 --- a/src/spice2x/hooks/graphics/graphics.h +++ b/src/spice2x/hooks/graphics/graphics.h @@ -36,6 +36,7 @@ extern std::optional GRAPHICS_FORCE_VSYNC_BUFFER; extern bool GRAPHICS_FORCE_SINGLE_ADAPTER; extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW; extern std::optional> GRAPHICS_FS_CUSTOM_RESOLUTION; +extern bool GRAPHICS_FS_FORCE_LANDSCAPE; extern graphics_dx9on12_state GRAPHICS_9_ON_12_STATE; extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME; diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 887c106..5d4c21b 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -367,15 +367,6 @@ int main_implementation(int argc, char *argv[]) { GRAPHICS_FORCE_SINGLE_ADAPTER = false; } - if (options[launcher::Options::FullscreenResolution].is_active()) { - std::pair result; - if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) { - GRAPHICS_FS_CUSTOM_RESOLUTION = result; - } else { - log_warning("launcher", "failed to parse -forceres"); - } - } - if (options[launcher::Options::spice2x_NvapiProfile].value_bool() && !cfg::CONFIGURATOR_STANDALONE) { nvapi::ADD_PROFILE = true; } @@ -448,7 +439,12 @@ int main_implementation(int argc, char *argv[]) { games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_TOP; } else if (txt == "center") { games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_MIDDLE; + } else if (txt == "bottomleft") { + games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT; + } else if (txt == "bottomright") { + games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT; } + // else - bottom } if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) { SUBSCREEN_FORCE_REDRAW = true; @@ -1160,6 +1156,23 @@ int main_implementation(int argc, char *argv[]) { } log_info("launcher", "arguments:\n{}", arguments.str()); + if (options[launcher::Options::FullscreenResolution].is_active()) { + std::pair result; + if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) { + GRAPHICS_FS_CUSTOM_RESOLUTION = result; + } else { + log_warning("launcher", "failed to parse -forceres"); + } + } + + if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) { +#if SPICE64 + GRAPHICS_FS_FORCE_LANDSCAPE = true; +#else + log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring..."); +#endif + } + // deleted options if (options[launcher::Options::OpenKFControl].value_bool()) { log_fatal("launcher", "KFControl has been removed from spice2x; please use an older version."); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 9031482..edc8421 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -19,6 +19,7 @@ static const std::vector CATEGORY_ORDER_BASIC = { "Network", "Overlay", "Graphics (Common)", + "Graphics (Full Screen)", "Graphics (Windowed)", "Audio", }; @@ -195,7 +196,7 @@ static const std::vector OPTION_DEFINITIONS = { .desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n" "May cause unstable framerate and desyncs, especially if monitors have different refresh rates!", .type = OptionType::Bool, - .category = "Graphics (Common)", + .category = "Graphics (Full Screen)", }, { .title = "Force Refresh Rate", @@ -211,10 +212,11 @@ static const std::vector OPTION_DEFINITIONS = { .desc = "For full screen mode, forcibly set a custom resolution.\n\n" "Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n" + "If you are using -sdvxlandscape, put the TARGET monitor resolution in this field.\n\n" "This should only be used as last resort if your GPU/monitor can't display the resolution required by the game", .type = OptionType::Text, .setting_name = "1280,720", - .category = "Graphics (Common)" + .category = "Graphics (Full Screen)" }, { // Graphics9On12 @@ -745,7 +747,13 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Enum, .game_name = "Sound Voltex", .category = "Overlay", - .elements = {{"top", ""}, {"center", ""}, {"bottom", ""}}, + .elements = { + {"top", ""}, + {"center", ""}, + {"bottom", ""}, + {"bottomleft", "for landscape"}, + {"bottomright", "for landscape"}, + }, }, { // spice2x_SDVXSubRedraw @@ -2025,6 +2033,19 @@ static const std::vector OPTION_DEFINITIONS = { .game_name = "Sound Voltex", .category = "Game Options", }, + { + // SDVXFullscreenLandscape + .title = "SDVX Landscape Mode (SDVX5+)", + .name = "sdvxlandscape", + .desc = + "Allows you to play in landscape by transposing resolution and applying image scaling.\n\n" + "Only for SDVX5 and above!\n\n" + "Can also be combined with -forceres option to render at larger or smaller resolution, " + "and with image resize option to zoom into certain areas of the screen", + .type = OptionType::Bool, + .game_name = "Sound Voltex", + .category = "Game Options" + }, { // spice2x_EnableSMXStage .title = "StepManiaX Stage Lighting Support", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index d49d3d8..e148d79 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -222,6 +222,7 @@ namespace launcher { spice2x_NoNVAPI, spice2x_NoD3D9DeviceHook, spice2x_SDVXNoSub, + SDVXFullscreenLandscape, spice2x_EnableSMXStage, spice2x_EnableSMXDedicab, IIDXRecQuality, diff --git a/src/spice2x/overlay/windows/sdvx_sub.cpp b/src/spice2x/overlay/windows/sdvx_sub.cpp index 26ed44a..3d2d6a1 100644 --- a/src/spice2x/overlay/windows/sdvx_sub.cpp +++ b/src/spice2x/overlay/windows/sdvx_sub.cpp @@ -23,11 +23,35 @@ namespace overlay::windows { const auto padding = ImGui::GetFrameHeight() / 2; - this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f); - this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight(); + switch (games::sdvx::OVERLAY_POS) { + case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT: + case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT: + this->init_size.x = (ImGui::GetIO().DisplaySize.x - (ImGui::GetIO().DisplaySize.y * 9 / 16)) / 2 - padding; + this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight(); + break; + case games::sdvx::SDVX_OVERLAY_TOP: + case games::sdvx::SDVX_OVERLAY_BOTTOM: + case games::sdvx::SDVX_OVERLAY_MIDDLE: + default: + this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f); + this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight(); + if (GRAPHICS_FS_FORCE_LANDSCAPE) { + this->init_size.x /= 2; + this->init_size.y /= 2; + } + break; + } this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, 0); switch (games::sdvx::OVERLAY_POS) { + case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT: + this->init_pos.x = 0; + this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y; + break; + case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT: + this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x; + this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y; + break; case games::sdvx::SDVX_OVERLAY_TOP: this->init_pos.y = padding; break;