From e90795b6169ee18c271fb83ee7307347ca920614 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 13 Jul 2024 00:57:03 +0200 Subject: [PATCH] Implement game render thread delay (#180) More details: https://www.reddit.com/r/Citra/comments/1e1v4e1/fixing_luigis_mansion_2_performance_issues_once/ --- .../features/settings/model/IntSetting.kt | 3 +- .../settings/ui/SettingsFragmentPresenter.kt | 12 +++ src/android/app/src/main/jni/config.cpp | 1 + src/android/app/src/main/jni/default_ini.h | 4 + .../app/src/main/res/values-es/strings.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + src/citra/config.cpp | 1 + src/citra_qt/configuration/config.cpp | 4 + .../configuration/configure_graphics.cpp | 37 +++++++++ .../configuration/configure_graphics.ui | 77 +++++++++++++++++++ src/common/settings.cpp | 2 + src/common/settings.h | 2 + src/core/hle/service/gsp/gsp_gpu.cpp | 24 +++++- 13 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index b2c378397..378c0eb05 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -40,7 +40,8 @@ enum class IntSetting( VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1), DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), - USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1); + USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 9f504e603..f949950bc 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -729,6 +729,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.TEXTURE_FILTER.defaultValue ) ) + add( + SliderSetting( + IntSetting.DELAY_RENDER_THREAD_US, + R.string.delay_render_thread, + R.string.delay_render_thread_description, + 0, + 16000, + " μs", + IntSetting.DELAY_RENDER_THREAD_US.key, + IntSetting.DELAY_RENDER_THREAD_US.defaultValue.toFloat() + ) + ) add(HeaderSetting(R.string.stereoscopy)) add( diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 4e862f3a0..bafac3129 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -169,6 +169,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); + ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); // Layout Settings::values.layout_option = static_cast(sdl2_config->GetInteger( diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index c46395fea..7bf546e03 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -175,6 +175,10 @@ anaglyph_shader_name = # 0: Nearest, 1 (default): Linear filter_mode = +# Delays the game render thread by the specified amount of microseconds +# Set to 0 for no delay, only useful in dynamic-fps games to simulate GPU delay. +delay_game_render_thread_us = + [Layout] # Layout for the screen inside the render window. # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 7c7ba9e53..c4c9db23a 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -663,5 +663,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Conectar con Artic Base Conectar con una consola real que esté ejecutando un servidor Artic Base Introduce la dirección del servidor Artic Base + Retrasa el hilo de dibujado del juego + Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 6614e97f1..4de0ba43b 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -689,5 +689,7 @@ Connect to a real console that is running an Artic Base server Connect to Artic Base Enter Artic Base server address + Delay game render thread + Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) dynamic-fps games. diff --git a/src/citra/config.cpp b/src/citra/config.cpp index e47421102..bd60d4764 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -147,6 +147,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.use_vsync_new); ReadSetting("Renderer", Settings::values.texture_filter); ReadSetting("Renderer", Settings::values.texture_sampling); + ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); ReadSetting("Renderer", Settings::values.mono_render_option); ReadSetting("Renderer", Settings::values.render_3d); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index e54fcee87..76ae4934d 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -667,6 +667,8 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.texture_filter); ReadGlobalSetting(Settings::values.texture_sampling); + ReadGlobalSetting(Settings::values.delay_game_render_thread_us); + if (global) { ReadBasicSetting(Settings::values.use_shader_jit); } @@ -1168,6 +1170,8 @@ void Config::SaveRendererValues() { WriteGlobalSetting(Settings::values.texture_filter); WriteGlobalSetting(Settings::values.texture_sampling); + WriteGlobalSetting(Settings::values.delay_game_render_thread_us); + if (global) { WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(), true); diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 80c2d138d..2e244b33a 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -26,6 +26,10 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::spangraphics_api_combo->setCurrentIndex(-1); + const auto width = static_cast(QString::fromStdString("000000000").size() * 6); + ui->delay_render_display_label->setMinimumWidth(width); + ui->delay_render_combo->setVisible(!Settings::IsConfiguringGlobal()); + auto graphics_api_combo_model = qobject_cast(ui->graphics_api_combo->model()); #ifndef ENABLE_SOFTWARE_RENDERER @@ -82,12 +86,25 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::spangraphics_api_combo, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureGraphics::SetPhysicalDeviceComboVisibility); + connect(ui->delay_render_slider, &QSlider::valueChanged, this, [&](int value) { + ui->delay_render_display_label->setText( + QStringLiteral("%1 ms") + .arg(((double)value) / 1000.f, 0, 'f', 3) + .rightJustified(QString::fromStdString("000000000").size())); + }); + SetConfiguration(); } ConfigureGraphics::~ConfigureGraphics() = default; void ConfigureGraphics::SetConfiguration() { + ui->delay_render_slider->setValue(Settings::values.delay_game_render_thread_us.GetValue()); + ui->delay_render_display_label->setText( + QStringLiteral("%1 ms") + .arg(((double)ui->delay_render_slider->value()) / 1000, 0, 'f', 3) + .rightJustified(QString::fromStdString("000000000").size())); + if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight(ui->graphics_api_group, !Settings::values.graphics_api.UsingGlobal()); @@ -101,6 +118,16 @@ void ConfigureGraphics::SetConfiguration() { &Settings::values.texture_sampling); ConfigurationShared::SetHighlight(ui->widget_texture_sampling, !Settings::values.texture_sampling.UsingGlobal()); + ConfigurationShared::SetHighlight( + ui->delay_render_layout, !Settings::values.delay_game_render_thread_us.UsingGlobal()); + + if (Settings::values.delay_game_render_thread_us.UsingGlobal()) { + ui->delay_render_combo->setCurrentIndex(0); + ui->delay_render_slider->setEnabled(false); + } else { + ui->delay_render_combo->setCurrentIndex(1); + ui->delay_render_slider->setEnabled(true); + } } else { ui->graphics_api_combo->setCurrentIndex( static_cast(Settings::values.graphics_api.GetValue())); @@ -144,6 +171,9 @@ void ConfigureGraphics::ApplyConfiguration() { ui->toggle_disk_shader_cache, use_disk_shader_cache); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new, use_vsync_new); + ConfigurationShared::ApplyPerGameSetting( + &Settings::values.delay_game_render_thread_us, ui->delay_render_combo, + [this](s32) { return ui->delay_render_slider->value(); }); if (Settings::IsConfiguringGlobal()) { Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); @@ -170,9 +200,16 @@ void ConfigureGraphics::SetupPerGameUI() { ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal()); ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal()); + ui->delay_render_combo->setEnabled( + Settings::values.delay_game_render_thread_us.UsingGlobal()); return; } + connect(ui->delay_render_combo, qOverload(&QComboBox::activated), this, [this](int index) { + ui->delay_render_slider->setEnabled(index == 1); + ConfigurationShared::SetHighlight(ui->delay_render_layout, index == 1); + }); + ui->toggle_shader_jit->setVisible(false); ConfigurationShared::SetColoredComboBox( diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index a052186cd..122fdddcd 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -307,6 +307,83 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Use global + + + + + Use per-game + + + + + + + + Delay game render thread: + + + <html><head/><body><p>Delays the emulated game render thread the specified amount of milliseconds every time it submits render commands to the GPU.</p><p>Adjust this feature in the (very few) dynamic-fps games to fix performance issues.</p></body></html> + + + + + + + 0 + + + 16000 + + + 100 + + + 250 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 657747b61..e2f432a71 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -100,6 +100,7 @@ void LogSettings() { log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue())); log_setting("Renderer_TextureSampling", GetTextureSamplingName(values.texture_sampling.GetValue())); + log_setting("Renderer_DelayGameRenderThreasUs", values.delay_game_render_thread_us.GetValue()); log_setting("Stereoscopy_Render3d", values.render_3d.GetValue()); log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue()); log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue()); @@ -192,6 +193,7 @@ void RestoreGlobalState(bool is_powered_on) { values.frame_limit.SetGlobal(true); values.texture_filter.SetGlobal(true); values.texture_sampling.SetGlobal(true); + values.delay_game_render_thread_us.SetGlobal(true); values.layout_option.SetGlobal(true); values.swap_screen.SetGlobal(true); values.upright_screen.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 64bba90ee..b7ac91a0b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -479,6 +479,8 @@ struct Values { SwitchableSetting texture_filter{TextureFilter::None, "texture_filter"}; SwitchableSetting texture_sampling{TextureSampling::GameControlled, "texture_sampling"}; + SwitchableSetting delay_game_render_thread_us{0, 0, 16000, + "delay_game_render_thread_us"}; SwitchableSetting layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting swap_screen{false, "swap_screen"}; diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 329c653c8..1b2dd29f1 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -9,6 +9,7 @@ #include #include "common/archives.h" #include "common/bit_field.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/shared_memory.h" @@ -410,6 +411,9 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { auto* command_buffer = GetCommandBuffer(active_thread_id); auto& gpu = system.GPU(); + + bool requires_delay = false; + while (command_buffer->number_commands) { if (command_buffer->should_stop) { command_buffer->status.Assign(CommandBuffer::STATUS_STOPPED); @@ -420,6 +424,10 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { } Command command = command_buffer->commands[command_buffer->index]; + if (command.id == CommandId::SubmitCmdList && !requires_delay && + Settings::values.delay_game_render_thread_us.GetValue() != 0) { + requires_delay = true; + } // Decrease the number of commands remaining and increase the current index command_buffer->number_commands.Assign(command_buffer->number_commands - 1); @@ -435,8 +443,20 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { } } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); + if (requires_delay) { + ctx.RunAsync( + [](Kernel::HLERequestContext& ctx) { + return Settings::values.delay_game_render_thread_us.GetValue() * 1000; + }, + [](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(ResultSuccess); + }, + false); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); + } } void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {