From 89921e5ec22740485da9af87beaa27b3c37b300d Mon Sep 17 00:00:00 2001 From: bicarus-dev <202771338+bicarus-dev@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:07:04 -0700 Subject: [PATCH] graphics: custom full screen resolution (#274) 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 Adds a new option to forcibly set the full screen resolution. Same idea as -windowscale. ## Compiling 🔺 ## Testing Seems to be showing similar results as -windowscale: * Works GREAT for IIDX/SDVX * popn launches at correct resolution but only draws a small image (at native res) - can be scaled with F11 menu * nostalgia is broken in weird ways, image is drawn correctly but bottom `credits` text draws at wrong offset, game's wintouch logic broken (perhaps expectedly) ## Other notes In theory this could enable "play sdvx in landscape mode" option but the image scaling doesn't quite work with extreme values. --- .../graphics/backends/d3d9/d3d9_backend.cpp | 21 ++++++++-- src/spice2x/hooks/graphics/graphics.cpp | 1 + src/spice2x/hooks/graphics/graphics.h | 1 + .../hooks/graphics/graphics_windowed.cpp | 41 +++++++------------ src/spice2x/launcher/launcher.cpp | 19 ++++++--- src/spice2x/launcher/options.cpp | 26 ++++++++---- src/spice2x/launcher/options.h | 3 +- src/spice2x/util/utils.h | 13 ++++++ 8 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index ee9fddc..493b2db 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -680,7 +680,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice( // dump presentation parameters for (size_t i = 0; i < num_adapters; i++) { - const auto *params = &pPresentationParameters[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; + } log_info("graphics::d3d9", "D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, " @@ -846,7 +851,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( } for (size_t i = 0; i < num_adapters; i++) { - const auto *params = &pPresentationParameters[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; + } log_info("graphics::d3d9", "D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, " @@ -870,7 +880,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( } if (pFullscreenDisplayMode) { for (size_t i = 0; i < num_adapters; i++) { - const auto *fullscreen_display_mode = &pFullscreenDisplayMode[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; + } log_info("graphics::d3d9", "D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, " diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index e219a9a..f8e4567 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -67,6 +67,7 @@ graphics_dx9on12_state GRAPHICS_9_ON_12_STATE = DX9ON12_AUTO; 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; // 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 365d8f6..6b334d7 100644 --- a/src/spice2x/hooks/graphics/graphics.h +++ b/src/spice2x/hooks/graphics/graphics.h @@ -35,6 +35,7 @@ extern UINT GRAPHICS_FORCE_REFRESH; 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 graphics_dx9on12_state GRAPHICS_9_ON_12_STATE; extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME; diff --git a/src/spice2x/hooks/graphics/graphics_windowed.cpp b/src/spice2x/hooks/graphics/graphics_windowed.cpp index bde9316..185f843 100644 --- a/src/spice2x/hooks/graphics/graphics_windowed.cpp +++ b/src/spice2x/hooks/graphics/graphics_windowed.cpp @@ -123,10 +123,6 @@ void graphics_load_windowed_parameters() { } log_debug("graphics-windowed", "graphics_load_windowed_parameters called"); - const auto remove_spaces = [](const char& c) { - return c == ' '; - }; - if (GRAPHICS_WINDOW_STYLE.has_value()) { log_debug( "graphics-windowed", @@ -148,13 +144,11 @@ void graphics_load_windowed_parameters() { log_debug( "graphics-windowed", "graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS"); - int32_t x, y; - auto s = GRAPHICS_WINDOW_POS.value(); - s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end()); - if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) { + std::pair result; + if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) { cfg::SCREENRESIZE->enable_window_resize = true; - cfg::SCREENRESIZE->window_offset_x = x; - cfg::SCREENRESIZE->window_offset_y = y; + cfg::SCREENRESIZE->window_offset_x = result.first; + cfg::SCREENRESIZE->window_offset_y = result.second; } else { log_warning("graphics-windowed", "failed to parse -windowpos"); } @@ -174,20 +168,16 @@ void graphics_load_windowed_subscreen_parameters() { } log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called"); - const auto remove_spaces = [](const char& c) { - return c == ' '; - }; - + if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) { log_debug( "graphics-windowed", "graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE"); - uint32_t w, h; - auto s = GRAPHICS_IIDX_WSUB_SIZE.value(); - s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end()); - if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) { - GRAPHICS_IIDX_WSUB_WIDTH = w; - GRAPHICS_IIDX_WSUB_HEIGHT = h; + + std::pair result; + if (parse_width_height(GRAPHICS_IIDX_WSUB_SIZE.value(), result)) { + GRAPHICS_IIDX_WSUB_WIDTH = result.first; + GRAPHICS_IIDX_WSUB_HEIGHT = result.second; } else { log_warning("graphics-windowed", "failed to parse -wsubsize"); } @@ -197,12 +187,11 @@ void graphics_load_windowed_subscreen_parameters() { log_debug( "graphics-windowed", "graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS"); - int32_t x, y; - auto s = GRAPHICS_IIDX_WSUB_POS.value(); - s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end()); - if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) { - GRAPHICS_IIDX_WSUB_X = x; - GRAPHICS_IIDX_WSUB_Y = y; + + std::pair result; + if (parse_width_height(GRAPHICS_IIDX_WSUB_POS.value(), result)) { + GRAPHICS_IIDX_WSUB_X = result.first; + GRAPHICS_IIDX_WSUB_Y = result.second; } else { log_warning("graphics-windowed", "failed to parse -wsubpos"); } diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 3d37a63..887c106 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -102,6 +102,7 @@ #include "util/sysutils.h" #include "util/tapeled.h" #include "util/time.h" +#include "util/utils.h" #include "avs/ssl.h" #include "nvapi/nvapi.h" #include "hooks/graphics/nvapi_hook.h" @@ -366,6 +367,15 @@ 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; } @@ -952,12 +962,9 @@ int main_implementation(int argc, char *argv[]) { GRAPHICS_WINDOW_STYLE = options[launcher::Options::spice2x_WindowBorder].value_uint32(); } if (options[launcher::Options::spice2x_WindowSize].is_active()) { - auto s = options[launcher::Options::spice2x_WindowSize].value_text(); - uint32_t w, h; - const auto remove_spaces = [](const char& c) { return c == ' '; }; - s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end()); - if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) { - GRAPHICS_WINDOW_SIZE = std::pair(w, h); + std::pair result; + if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) { + GRAPHICS_WINDOW_SIZE = result; } else { log_warning("launcher", "failed to parse -windowsize"); } diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 0167ef4..e2ab97d 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -189,6 +189,14 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Integer, .category = "Graphics (Common)", }, + { + .title = "Only Use One Monitor", + .name = "graphics-force-single-adapter", + .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)", + }, { .title = "Force Refresh Rate", .name = "graphics-force-refresh", @@ -197,12 +205,16 @@ static const std::vector OPTION_DEFINITIONS = { .category = "Graphics (Common)", }, { - .title = "Only Use One Monitor", - .name = "graphics-force-single-adapter", - .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)", + // FullscreenResolution + .title = "Force Full Screen Resolution", + .name = "forceres", + .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" + "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)" }, { // Graphics9On12 @@ -1783,7 +1795,7 @@ static const std::vector OPTION_DEFINITIONS = { .title = "Window Forced Render Scaling", .name = "windowscale", .desc = "For windowed mode: forcibly set DX9 back buffer dimensions to match window size. " - "Reduces pixelated scaling artifacts. Works great on some games, but completely broken on others", + "Reduces pixelated scaling artifacts. Works great for some games, but can COMPLETELY BREAK other games - YMMV!", .type = OptionType::Bool, .category = "Graphics (Windowed)", }, diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index c8d9163..d49d3d8 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -27,8 +27,9 @@ namespace launcher { CaptureCursor, ShowCursor, DisplayAdapter, - GraphicsForceRefresh, GraphicsForceSingleAdapter, + GraphicsForceRefresh, + FullscreenResolution, Graphics9On12, spice2x_Dx9On12, NoLegacy, diff --git a/src/spice2x/util/utils.h b/src/spice2x/util/utils.h index af0b057..188fd10 100644 --- a/src/spice2x/util/utils.h +++ b/src/spice2x/util/utils.h @@ -298,3 +298,16 @@ static inline int get_async_secondary_mouse() { int vk = GetSystemMetrics(SM_SWAPBUTTON) ? VK_LBUTTON : VK_RBUTTON; return GetAsyncKeyState(vk); } + +static inline bool parse_width_height(const std::string wh, std::pair &result) { + std::string s = wh; + uint32_t w, h; + const auto remove_spaces = [](const char& c) { return c == ' '; }; + s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end()); + if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) { + result = std::pair(w, h); + return true; + } else { + return false; + } +} \ No newline at end of file