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.
This commit is contained in:
bicarus
2026-01-07 16:35:08 -08:00
committed by GitHub
parent b1ee37a066
commit 0078b72b03
4 changed files with 33 additions and 28 deletions
-11
View File
@@ -81,19 +81,8 @@ 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");
}
}
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
+4
View File
@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <optional>
@@ -51,6 +52,9 @@ namespace cfg {
bool enable_linear_filter = true;
fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing
// Windows terminology:
// window = rectangle including the frame
@@ -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;
+22 -8
View File
@@ -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<int *>(&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() {