From 0078b72b03f1a959306db7208728163e526594bf Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:35:08 -0800 Subject: [PATCH] graphics: address performance regression with Image Resize (#504) ## Link to GitHub Issue, if one exists Regressed by #401 ## Description of change Two things from testing SDVX Live2D - 1. ColorFill is almost always too expensive and will lead to drops 1. Scale factor of 0.5 causes GPU to sync and causes drops (ARGB/XRGB doesn't matter, linear filtering doesn't matter) 1 is fixed by removing ColorFill calls on every frame - instead only do this once when the user changes the duplicate option (on the next frame) 2 is fixed by updating the UI (limit the slider) though people can ctrl+click to override these slider limits. The warning label will be kept with slightly reworded text. ## Testing tested in sdvx eg final (sudden death) - on my RTX 4070, my FPS is rock solid 120 fps even with image resize on now. --- src/spice2x/cfg/screen_resize.cpp | 13 +------- src/spice2x/cfg/screen_resize.h | 4 +++ .../graphics/backends/d3d9/d3d9_device.cpp | 14 ++++----- src/spice2x/overlay/windows/screen_resize.cpp | 30 ++++++++++++++----- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/spice2x/cfg/screen_resize.cpp b/src/spice2x/cfg/screen_resize.cpp index 2654a20..1f4a511 100644 --- a/src/spice2x/cfg/screen_resize.cpp +++ b/src/spice2x/cfg/screen_resize.cpp @@ -81,18 +81,7 @@ namespace cfg { load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize); if (this->enable_screen_resize) { - if (avs::game::is_model("KFC")) { - log_warning( - "ScreenResize", - "Image Resize feature enabled for SDVX; enabling this is known to significantly " - "lower framerate for songs with Live2D!"); - deferredlogs::defer_error_messages({ - "Image Resize + SDVX Live2D performance warning", - " Enabling Image Resize feature is known to have significant impact", - " on Live2D performance; you should disable it"}); - } else { - log_misc("ScreenResize", "enabled by config file"); - } + log_misc("ScreenResize", "enabled by config file"); } load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter); diff --git a/src/spice2x/cfg/screen_resize.h b/src/spice2x/cfg/screen_resize.h index 9204542..f986028 100644 --- a/src/spice2x/cfg/screen_resize.h +++ b/src/spice2x/cfg/screen_resize.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -51,6 +52,9 @@ namespace cfg { bool enable_linear_filter = true; fullscreen_setting scene_settings[4]; + // state + std::atomic need_surface_clean = false; + // windowed mode sizing // Windows terminology: // window = rectangle including the frame diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp index 27a2ebf..e6db0d2 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp @@ -742,14 +742,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) { const int w = param.BackBufferWidth; const int h = param.BackBufferHeight; - if (cfg::SCREENRESIZE->enable_screen_resize) { - RECT rect { - 0, - 0, - (LONG)topSurface_width, - (LONG)topSurface_height, - }; - pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0)); + // this code used to clear the surface using ColorFill on every call, but + // this turned out to be very expensive, leading to major frame drops in + // SDVX Live2D scenario + if (cfg::SCREENRESIZE->need_surface_clean) { + pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0)); + cfg::SCREENRESIZE->need_surface_clean = false; } D3DLOCKED_RECT rect; diff --git a/src/spice2x/overlay/windows/screen_resize.cpp b/src/spice2x/overlay/windows/screen_resize.cpp index 8b03994..ed5e52f 100644 --- a/src/spice2x/overlay/windows/screen_resize.cpp +++ b/src/spice2x/overlay/windows/screen_resize.cpp @@ -103,12 +103,16 @@ namespace overlay::windows { if (avs::game::is_model("KFC")) { ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f), - "Warning: Enabling Image Resize will significantly\n" - "lower framerate for songs with Live2D!"); + "Warning: Enabling Image Resize uses more GPU\n" + "resources and may significantly lower framerate\n" + "for songs with Live2D! Results may vary, use at\n" + "your own risk."); } else { ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f), - "Warning: Some games are known to have significant\n" - "performance issues when Image Resize is enabled."); + "Warning: Enabling Image Resize uses more GPU\n" + "resources and may significantly lower framerate\n" + "in some situations! Results may vary, use at\n" + "your own risk."); } // enable checkbox @@ -152,22 +156,27 @@ namespace overlay::windows { // aspect ratio ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio); if (scene.keep_aspect_ratio) { - if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.5f, 2.5f)) { + + // we want to avoid zooming out below 0.6 as it will cause StretchRect calls to + // go on a slow sync path with the GPU, leading to frame drops in some situations + // (e.g., SDVX Live2D) + + if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.6f, 2.5f)) { scene.scale_y = scene.scale_x; } ImGui::SameLine(); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); } else { - ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.5f, 2.5f); + ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.6f, 2.5f); ImGui::SameLine(); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); - ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.5f, 2.5f); + ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.6f, 2.5f); ImGui::SameLine(); ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value."); } static const char* dupe_items[] = { "None", "Copy Left", "Copy Right" }; - ImGui::Combo( + const bool duplicate_changed = ImGui::Combo( "Duplicate", reinterpret_cast(&scene.duplicate), dupe_items, @@ -178,6 +187,11 @@ namespace overlay::windows { "wrap-around effect when an offset is set."); ImGui::EndDisabled(); + + // tell d3d9 device to clear surface to get rid of ghost image + if (duplicate_changed) { + cfg::SCREENRESIZE->need_surface_clean = true; + } } void ScreenResize::build_windowed_config() {