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 c15234c78..4ba242d16 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 @@ -63,7 +63,8 @@ enum class IntSetting( USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0), - ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2); + ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2), + DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 1); override var int: Int = defaultValue override val valueAsString: String @@ -91,7 +92,8 @@ enum class IntSetting( CPU_JIT, ASYNC_CUSTOM_LOADING, AUDIO_INPUT_TYPE, - USE_ARTIC_BASE_CONTROLLER + USE_ARTIC_BASE_CONTROLLER, + SHADERS_ACCURATE_MUL, ) fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } 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 fade306eb..c124c91aa 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 @@ -847,6 +847,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.STEREOSCOPIC_3D_DEPTH.defaultValue.toFloat() ) ) + add( + SwitchSetting( + IntSetting.DISABLE_RIGHT_EYE_RENDER, + R.string.disable_right_eye_render, + R.string.use_disk_shader_cache_description, + IntSetting.DISABLE_RIGHT_EYE_RENDER.key, + IntSetting.DISABLE_RIGHT_EYE_RENDER.defaultValue + ) + ) add(HeaderSetting(R.string.cardboard_vr)) add( diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 866940e66..e91876584 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -1138,9 +1138,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram val perfStats = NativeLibrary.getPerfStats() if (perfStats[FPS] > 0) { binding.showFpsText.text = String.format( - "FPS: %d Speed: %d%%", + "FPS: %d Speed: %d%% FT: %.2fms", (perfStats[FPS] + 0.5).toInt(), - (perfStats[SPEED] * 100.0 + 0.5).toInt() + (perfStats[SPEED] * 100.0 + 0.5).toInt(), + (perfStats[FRAMETIME] * 1000.0f).toFloat() ) } perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 3000) diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 7f26f8fb9..f2cad225a 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -20,8 +20,6 @@ add_library(citra-android SHARED emu_window/emu_window.cpp emu_window/emu_window.h game_info.cpp - game_settings.cpp - game_settings.h id_cache.cpp id_cache.h native.cpp diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 5462c1cb5..19e422324 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -172,6 +172,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); + ReadSetting("Renderer", Settings::values.disable_right_eye_render); // Layout // Somewhat inelegant solution to ensure layout value is between 0 and 5 on read diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 21578b04b..9ec472b12 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -182,6 +182,11 @@ filter_mode = # Set to 0 for no delay, only useful in dynamic-fps games to simulate GPU delay. delay_game_render_thread_us = +# Disables rendering the right eye image. +# Greatly improves performance in some games, but can cause flickering in others. +# 0: Enable right eye rendering, 1 (default): Disable right eye rendering +disable_right_eye_render = + [Layout] # Layout for the screen inside the render window, landscape mode # 0: Original (screens vertically aligned) diff --git a/src/android/app/src/main/jni/game_settings.cpp b/src/android/app/src/main/jni/game_settings.cpp deleted file mode 100644 index d8382b0e1..000000000 --- a/src/android/app/src/main/jni/game_settings.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/settings.h" - -namespace GameSettings { - -void LoadOverrides(u64 program_id) { - switch (program_id) { - // JAP / The Legend of Zelda: Ocarina of Time 3D - case 0x0004000000033400: - // USA / The Legend of Zelda: Ocarina of Time 3D - case 0x0004000000033500: - // EUR / The Legend of Zelda: Ocarina of Time 3D - case 0x0004000000033600: - // KOR / The Legend of Zelda: Ocarina of Time 3D - case 0x000400000008F800: - // CHI / The Legend of Zelda: Ocarina of Time 3D - case 0x000400000008F900: - // This game requires accurate multiplication to render properly - Settings::values.shaders_accurate_mul = true; - break; - - // USA / Mario & Luigi: Superstar Saga + Bowsers Minions - case 0x00040000001B8F00: - // EUR / Mario & Luigi: Superstar Saga + Bowsers Minions - case 0x00040000001B9000: - // JAP / Mario & Luigi: Superstar Saga + Bowsers Minions - case 0x0004000000194B00: - // This game requires accurate multiplication to render properly - Settings::values.shaders_accurate_mul = true; - break; - - // USA / Mario & Luigi: Bowsers Inside Story + Bowser Jrs Journey - case 0x00040000001D1400: - // EUR / Mario & Luigi: Bowsers Inside Story + Bowser Jrs Journey - case 0x00040000001D1500: - // This game requires accurate multiplication to render properly - Settings::values.shaders_accurate_mul = true; - break; - } -} - -} // namespace GameSettings diff --git a/src/android/app/src/main/jni/game_settings.h b/src/android/app/src/main/jni/game_settings.h deleted file mode 100644 index b034e1865..000000000 --- a/src/android/app/src/main/jni/game_settings.h +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/common_types.h" - -namespace GameSettings { - -void LoadOverrides(u64 program_id); - -} // namespace GameSettings diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c61676103..4682df04d 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -50,7 +50,6 @@ #ifdef ENABLE_VULKAN #include "jni/emu_window/emu_window_vk.h" #endif -#include "jni/game_settings.h" #include "jni/id_cache.h" #include "jni/input_manager.h" #include "jni/ndk_motion.h" @@ -181,7 +180,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { if (app_loader) { app_loader->ReadProgramId(program_id); system.RegisterAppLoaderEarly(app_loader); - GameSettings::LoadOverrides(program_id); } system.ApplySettings(); Settings::LogSettings(); @@ -624,7 +622,6 @@ void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNI if (system.IsPoweredOn()) { u64 program_id{}; system.GetAppLoader().ReadProgramId(program_id); - GameSettings::LoadOverrides(program_id); } system.ApplySettings(); 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 73ba58d9d..a3a5c8f1f 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -671,5 +671,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Misceláneo Usar Artic Controller cuando se está conectado a Artic Base Server Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado. + Desactivar dibujado de ojo derecho + Mejora considerablemente el rendimiento en algunos juegos, pero puede producir parpadeos en otros. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index f90c06e13..3c6c0a5e9 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -782,5 +782,7 @@ Use the controls provided by Artic Base Server when connected to it instead of the configured input device. Flush log output on every message Immediately commits the debug log to file. Use this if citra crashes and the log output is being cut. + Disable Right Eye Render + Greatly improves performance in some games, but can cause flickering in others. diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index f3af3d0a5..1c88bf45c 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -689,6 +689,7 @@ void QtConfig::ReadRendererValues() { ReadGlobalSetting(Settings::values.texture_sampling); ReadGlobalSetting(Settings::values.delay_game_render_thread_us); + ReadGlobalSetting(Settings::values.disable_right_eye_render); if (global) { ReadBasicSetting(Settings::values.use_shader_jit); @@ -1220,6 +1221,7 @@ void QtConfig::SaveRendererValues() { WriteGlobalSetting(Settings::values.texture_sampling); WriteGlobalSetting(Settings::values.delay_game_render_thread_us); + WriteGlobalSetting(Settings::values.disable_right_eye_render); if (global) { WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(), diff --git a/src/citra_qt/configuration/configure_enhancements.cpp b/src/citra_qt/configuration/configure_enhancements.cpp index 91953991c..7d5c6fb2f 100644 --- a/src/citra_qt/configuration/configure_enhancements.cpp +++ b/src/citra_qt/configuration/configure_enhancements.cpp @@ -66,6 +66,7 @@ void ConfigureEnhancements::SetConfiguration() { ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue()); ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue()); ui->toggle_async_custom_loading->setChecked(Settings::values.async_custom_loading.GetValue()); + ui->disable_right_eye_render->setChecked(Settings::values.disable_right_eye_render.GetValue()); } void ConfigureEnhancements::updateShaders(Settings::StereoRenderOption stereo_option) { @@ -120,6 +121,7 @@ void ConfigureEnhancements::ApplyConfiguration() { Settings::values.pp_shader_name = ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString(); } + Settings::values.disable_right_eye_render = ui->disable_right_eye_render->isChecked(); ConfigurationShared::ApplyPerGameSetting(&Settings::values.filter_mode, ui->toggle_linear_filter, linear_filter); @@ -133,6 +135,9 @@ void ConfigureEnhancements::ApplyConfiguration() { ui->toggle_preload_textures, preload_textures); ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_custom_loading, ui->toggle_async_custom_loading, async_custom_loading); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.disable_right_eye_render, + ui->disable_right_eye_render, + disable_right_eye_render); } void ConfigureEnhancements::SetupPerGameUI() { @@ -146,10 +151,15 @@ void ConfigureEnhancements::SetupPerGameUI() { ui->toggle_preload_textures->setEnabled(Settings::values.preload_textures.UsingGlobal()); ui->toggle_async_custom_loading->setEnabled( Settings::values.async_custom_loading.UsingGlobal()); + ui->disable_right_eye_render->setEnabled( + Settings::values.disable_right_eye_render.UsingGlobal()); return; } - ui->stereo_group->setVisible(false); + ui->render_3d_combobox->setEnabled(false); + ui->factor_3d->setEnabled(false); + ui->mono_rendering_eye->setEnabled(false); + ui->widget_shader->setVisible(false); ConfigurationShared::SetColoredTristate(ui->toggle_linear_filter, Settings::values.filter_mode, @@ -163,6 +173,9 @@ void ConfigureEnhancements::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->toggle_async_custom_loading, Settings::values.async_custom_loading, async_custom_loading); + ConfigurationShared::SetColoredTristate(ui->disable_right_eye_render, + Settings::values.disable_right_eye_render, + disable_right_eye_render); ConfigurationShared::SetColoredComboBox( ui->resolution_factor_combobox, ui->widget_resolution, diff --git a/src/citra_qt/configuration/configure_enhancements.h b/src/citra_qt/configuration/configure_enhancements.h index d366f854a..c9f3449b1 100644 --- a/src/citra_qt/configuration/configure_enhancements.h +++ b/src/citra_qt/configuration/configure_enhancements.h @@ -43,5 +43,6 @@ private: ConfigurationShared::CheckState custom_textures; ConfigurationShared::CheckState preload_textures; ConfigurationShared::CheckState async_custom_loading; + ConfigurationShared::CheckState disable_right_eye_render; QColor bg_color; }; diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui index 0a738c227..f87d30167 100644 --- a/src/citra_qt/configuration/configure_enhancements.ui +++ b/src/citra_qt/configuration/configure_enhancements.ui @@ -310,6 +310,16 @@ + + + + Disable Right Eye Rendering + + + <html><head/><body><p>Disable Right Eye Rendering</p><p>Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some games, but can cause flickering in others.</p></body></html> + + + diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 2e244b33a..d06f5a97d 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -23,6 +23,7 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::spanphysical_device_combo->setEnabled(!is_powered_on); ui->toggle_async_shaders->setEnabled(!is_powered_on); ui->toggle_async_present->setEnabled(!is_powered_on); + ui->toggle_accurate_mul->setEnabled(!is_powered_on); // Set the index to -1 to ensure the below lambda is called with setCurrentIndex ui->graphics_api_combo->setCurrentIndex(-1); diff --git a/src/citra_sdl/config.cpp b/src/citra_sdl/config.cpp index 0312c4a24..adb55b4c6 100644 --- a/src/citra_sdl/config.cpp +++ b/src/citra_sdl/config.cpp @@ -160,6 +160,7 @@ void SdlConfig::ReadValues() { ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); + ReadSetting("Renderer", Settings::values.disable_right_eye_render); // Layout ReadSetting("Layout", Settings::values.layout_option); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 567831249..85acf1c7a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -88,6 +88,10 @@ add_library(citra_common STATIC file_util.cpp file_util.h hash.h + hacks/hack_list.h + hacks/hack_list.cpp + hacks/hack_manager.h + hacks/hack_manager.cpp literals.h logging/backend.cpp logging/backend.h diff --git a/src/common/hacks/hack_list.cpp b/src/common/hacks/hack_list.cpp new file mode 100644 index 000000000..bf1ce6c52 --- /dev/null +++ b/src/common/hacks/hack_list.cpp @@ -0,0 +1,62 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/hacks/hack_manager.h" + +namespace Common::Hacks { + +HackManager hack_manager = { + .entries = { + + // The following games cannot use the right eye disable hack due to the way they + // handle rendering. + {HackType::RIGHT_EYE_DISABLE, + HackEntry{ + .mode = HackAllowMode::DISALLOW, + .affected_title_ids = + { + // Luigi's Mansion + 0x00040000001D1900, + 0x00040000001D1A00, + + // Luigi's Mansion 2 + 0x0004000000055F00, + 0x0004000000076500, + 0x0004000000076400, + 0x00040000000D0000, + + // Rayman Origins + 0x000400000005A500, + 0x0004000000084400, + 0x0004000000057600, + }, + }}, + + // The following games require accurate multiplication to render properly. + {HackType::ACCURATE_MULTIPLICATION, + HackEntry{ + .mode = HackAllowMode::FORCE, + .affected_title_ids = + { + // The Legend of Zelda: Ocarina of Time 3D + 0x0004000000033400, // JAP + 0x0004000000033500, // USA + 0x0004000000033600, // EUR + 0x000400000008F800, // KOR + 0x000400000008F900, // CHI + + // Mario & Luigi: Superstar Saga + Bowsers Minions + 0x00040000001B8F00, // USA + 0x00040000001B9000, // EUR + 0x0004000000194B00, // JAP + + // Mario & Luigi: Bowsers Inside Story + Bowser Jrs Journey + 0x00040000001D1400, // USA + 0x00040000001D1500, // EUR + 0x00040000001CA900, // JAP + }, + }}, + + }}; +} \ No newline at end of file diff --git a/src/common/hacks/hack_list.h b/src/common/hacks/hack_list.h new file mode 100644 index 000000000..d8ea037fd --- /dev/null +++ b/src/common/hacks/hack_list.h @@ -0,0 +1,18 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Common::Hacks { + +enum class HackType : int { + RIGHT_EYE_DISABLE, + ACCURATE_MULTIPLICATION, +}; + +class UserHackData {}; + +} // namespace Common::Hacks \ No newline at end of file diff --git a/src/common/hacks/hack_manager.cpp b/src/common/hacks/hack_manager.cpp new file mode 100644 index 000000000..ae61324ce --- /dev/null +++ b/src/common/hacks/hack_manager.cpp @@ -0,0 +1,21 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "hack_manager.h" + +namespace Common::Hacks { +const HackEntry* HackManager::GetHack(const HackType& type, u64 title_id) { + auto range = entries.equal_range(type); + + for (auto it = range.first; it != range.second; it++) { + auto tid_found = std::find(it->second.affected_title_ids.begin(), + it->second.affected_title_ids.end(), title_id); + if (tid_found != it->second.affected_title_ids.end()) { + return &it->second; + } + } + + return nullptr; +} +} // namespace Common::Hacks diff --git a/src/common/hacks/hack_manager.h b/src/common/hacks/hack_manager.h new file mode 100644 index 000000000..fba859247 --- /dev/null +++ b/src/common/hacks/hack_manager.h @@ -0,0 +1,40 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "common/hacks/hack_list.h" + +namespace Common::Hacks { + +enum class HackAllowMode { + ALLOW, + DISALLOW, + FORCE, +}; + +struct HackEntry { + HackAllowMode mode{}; + std::vector affected_title_ids{}; + UserHackData* hack_data{nullptr}; +}; + +struct HackManager { + const HackEntry* GetHack(const HackType& type, u64 title_id); + + HackAllowMode GetHackAllowMode(const HackType& type, u64 title_id, + HackAllowMode default_mode = HackAllowMode::ALLOW) { + const HackEntry* hack = GetHack(type, title_id); + return (hack != nullptr) ? hack->mode : default_mode; + } + + std::multimap entries; +}; + +extern HackManager hack_manager; + +} // namespace Common::Hacks \ No newline at end of file diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 908447940..e99ec7787 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -102,6 +102,7 @@ void LogSettings() { log_setting("Renderer_TextureSampling", GetTextureSamplingName(values.texture_sampling.GetValue())); log_setting("Renderer_DelayGameRenderThreasUs", values.delay_game_render_thread_us.GetValue()); + log_setting("Renderer_DisableRightEyeRender", values.disable_right_eye_render.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()); @@ -217,6 +218,7 @@ void RestoreGlobalState(bool is_powered_on) { values.dump_textures.SetGlobal(true); values.custom_textures.SetGlobal(true); values.preload_textures.SetGlobal(true); + values.disable_right_eye_render.SetGlobal(true); } void LoadProfile(int index) { diff --git a/src/common/settings.h b/src/common/settings.h index 49decaa83..f8e9e3e2e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -556,6 +556,7 @@ struct Values { SwitchableSetting custom_textures{false, "custom_textures"}; SwitchableSetting preload_textures{false, "preload_textures"}; SwitchableSetting async_custom_loading{true, "async_custom_loading"}; + SwitchableSetting disable_right_eye_render{false, "disable_right_eye_render"}; // Audio bool audio_muted; diff --git a/src/core/core.cpp b/src/core/core.cpp index a725bcf47..4db13d8a6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -266,10 +266,10 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st return ResultStatus::ErrorGetLoader; } + u64_le program_id = 0; + app_loader->ReadProgramId(program_id); if (restore_plugin_context.has_value() && restore_plugin_context->is_enabled && restore_plugin_context->use_user_load_parameters) { - u64_le program_id = 0; - app_loader->ReadProgramId(program_id); if (restore_plugin_context->user_load_parameters.low_title_Id == static_cast(program_id) && restore_plugin_context->user_load_parameters.plugin_memory_strategy == @@ -312,6 +312,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st System::Shutdown(); return init_result; } + gpu->ReportLoadingProgramID(program_id); // Restore any parameters that should be carried through a reset. if (restore_deliver_arg.has_value()) { @@ -714,7 +715,7 @@ void System::serialize(Archive& ar, const unsigned int file_version) { if (Archive::is_saving::value) { num_cores = this->GetNumCores(); } - ar & num_cores; + ar& num_cores; if (Archive::is_loading::value) { // When loading, we want to make sure any lingering state gets cleared out before we begin. @@ -750,7 +751,7 @@ void System::serialize(Archive& ar, const unsigned int file_version) { ar&* memory.get(); ar&* kernel.get(); ar&* gpu.get(); - ar & movie; + ar& movie; // This needs to be set from somewhere - might as well be here! if (Archive::is_loading::value) { diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index b363f59ab..4a7d310f6 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/hacks/hack_manager.h" #include "common/settings.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" @@ -20,6 +21,7 @@ #include "video_core/gpu.h" #include "video_core/gpu_debugger.h" #include "video_core/pica/regs_lcd.h" +#include "video_core/right_eye_disabler.h" SERIALIZE_EXPORT_IMPL(Service::GSP::SessionData) SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU) @@ -599,6 +601,13 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx, LOG_DEBUG(Service_GSP, "called flag={:08X} process={} thread_id={}", flag, process->process_id, session_data->thread_id); + bool right_eye_disable_allow = + Common::Hacks::hack_manager.GetHackAllowMode(Common::Hacks::HackType::RIGHT_EYE_DISABLE, + process->codeset->program_id) != + Common::Hacks::HackAllowMode::DISALLOW; + auto& gpu = system.GPU(); + gpu.GetRightEyeDisabler().SetEnabled(right_eye_disable_allow); + if (active_thread_id == session_data->thread_id) { return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success, ErrorLevel::Success}; @@ -708,11 +717,11 @@ SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { template void GSP_GPU::serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); - ar & shared_memory; - ar & active_thread_id; - ar & first_initialization; - ar & used_thread_ids; - ar & saved_vram; + ar& shared_memory; + ar& active_thread_id; + ar& first_initialization; + ar& used_thread_ids; + ar& saved_vram; } SERIALIZE_IMPL(GSP_GPU) @@ -771,10 +780,10 @@ std::unique_ptr GSP_GPU::MakeSes template void SessionData::serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); - ar & gsp; - ar & interrupt_event; - ar & thread_id; - ar & registered; + ar& gsp; + ar& interrupt_event; + ar& thread_id; + ar& registered; } SERIALIZE_IMPL(SessionData) diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 3593a271a..e59b6ad7e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(video_core STATIC gpu.cpp gpu.h gpu_debugger.h + gpu_impl.h pica_types.h precompiled_headers.h rasterizer_accelerated.cpp @@ -63,6 +64,8 @@ add_library(video_core STATIC # Needed as a fallback regardless of enabled renderers. renderer_software/sw_blitter.cpp renderer_software/sw_blitter.h + right_eye_disabler.cpp + right_eye_disabler.h shader/debug_data.h shader/generator/glsl_fs_shader_gen.cpp shader/generator/glsl_fs_shader_gen.h diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index a8193a600..ecfae2932 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "common/archives.h" +#include "common/hacks/hack_manager.h" #include "common/microprofile.h" #include "core/core.h" #include "core/core_timing.h" @@ -11,10 +12,12 @@ #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/gpu_debugger.h" +#include "video_core/gpu_impl.h" #include "video_core/pica/pica_core.h" #include "video_core/pica/regs_lcd.h" #include "video_core/renderer_base.h" #include "video_core/renderer_software/sw_blitter.h" +#include "video_core/right_eye_disabler.h" #include "video_core/video_core.h" namespace VideoCore { @@ -25,32 +28,10 @@ constexpr VAddr VADDR_GPU = 0x1EF00000; MICROPROFILE_DEFINE(GPU_DisplayTransfer, "GPU", "DisplayTransfer", MP_RGB(100, 100, 255)); MICROPROFILE_DEFINE(GPU_CmdlistProcessing, "GPU", "Cmdlist Processing", MP_RGB(100, 255, 100)); -struct GPU::Impl { - Core::Timing& timing; - Core::System& system; - Memory::MemorySystem& memory; - std::shared_ptr debug_context; - Pica::PicaCore pica; - GraphicsDebugger gpu_debugger; - std::unique_ptr renderer; - RasterizerInterface* rasterizer; - std::unique_ptr sw_blitter; - Core::TimingEventType* vblank_event; - Service::GSP::InterruptHandler signal_interrupt; - - explicit Impl(Core::System& system, Frontend::EmuWindow& emu_window, - Frontend::EmuWindow* secondary_window) - : timing{system.CoreTiming()}, system{system}, memory{system.Memory()}, - debug_context{Pica::g_debug_context}, pica{memory, debug_context}, - renderer{VideoCore::CreateRenderer(emu_window, secondary_window, pica, system)}, - rasterizer{renderer->Rasterizer()}, - sw_blitter{std::make_unique(memory, rasterizer)} {} - ~Impl() = default; -}; - GPU::GPU(Core::System& system, Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window) - : impl{std::make_unique(system, emu_window, secondary_window)} { + : right_eye_disabler{std::make_unique(*this)}, + impl{std::make_unique(system, emu_window, secondary_window)} { impl->vblank_event = impl->timing.RegisterEvent( "GPU::VBlankCallback", [this](uintptr_t user_data, s64 cycles_late) { VBlankCallback(user_data, cycles_late); }); @@ -232,6 +213,7 @@ void GPU::SetBufferSwap(u32 screen_id, const Service::GSP::FrameBufferInfo& info if (screen_id == 0) { MicroProfileFlip(); impl->system.perf_stats->EndGameFrame(); + right_eye_disabler->ReportEndFrame(); } } @@ -332,6 +314,26 @@ GraphicsDebugger& GPU::Debugger() { return impl->gpu_debugger; } +void GPU::ReportLoadingProgramID(u64 program_ID) { + auto hack = Common::Hacks::hack_manager.GetHack( + Common::Hacks::HackType::ACCURATE_MULTIPLICATION, program_ID); + bool use_accurate_mul = Settings::values.shaders_accurate_mul.GetValue(); + if (hack) { + switch (hack->mode) { + case Common::Hacks::HackAllowMode::DISALLOW: + use_accurate_mul = false; + break; + case Common::Hacks::HackAllowMode::FORCE: + use_accurate_mul = true; + break; + case Common::Hacks::HackAllowMode::ALLOW: + default: + break; + } + } + impl->rasterizer->SetAccurateMul(use_accurate_mul); +} + void GPU::SubmitCmdList(u32 index) { // Check if a command list was triggered. auto& config = impl->pica.regs.internal.pipeline.command_buffer; @@ -344,7 +346,8 @@ void GPU::SubmitCmdList(u32 index) { // Forward command list processing to the PICA core. const PAddr addr = config.GetPhysicalAddress(index); const u32 size = config.GetSize(index); - impl->pica.ProcessCmdList(addr, size); + impl->pica.ProcessCmdList(addr, size, + !right_eye_disabler->ShouldAllowCmdQueueTrigger(addr, size)); config.trigger[index] = 0; } @@ -396,8 +399,11 @@ void GPU::MemoryTransfer() { impl->sw_blitter->TextureCopy(config); } } else { - if (!impl->rasterizer->AccelerateDisplayTransfer(config)) { - impl->sw_blitter->DisplayTransfer(config); + if (right_eye_disabler->ShouldAllowDisplayTransfer(config.GetPhysicalInputAddress(), + config.input_height)) { + if (!impl->rasterizer->AccelerateDisplayTransfer(config)) { + impl->sw_blitter->DisplayTransfer(config); + } } } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index c5dd2c868..2ca608b2b 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -37,6 +37,7 @@ constexpr u64 FRAME_TICKS = 4481136ull; class GraphicsDebugger; class RendererBase; +class RightEyeDisabler; /** * The GPU class is the high level interface to the video_core for core services. @@ -92,6 +93,12 @@ public: /// Returns a mutable reference to the GSP command debugger. [[nodiscard]] GraphicsDebugger& Debugger(); + RightEyeDisabler& GetRightEyeDisabler() { + return *right_eye_disabler; + } + + void ReportLoadingProgramID(u64 program_ID); + private: void SubmitCmdList(u32 index); @@ -105,7 +112,10 @@ private: template void serialize(Archive& ar, const u32 file_version); + std::unique_ptr right_eye_disabler; + private: + friend class RightEyeDisabler; struct Impl; std::unique_ptr impl; diff --git a/src/video_core/gpu_impl.h b/src/video_core/gpu_impl.h new file mode 100644 index 000000000..015918fe1 --- /dev/null +++ b/src/video_core/gpu_impl.h @@ -0,0 +1,48 @@ +// Copyright 2023 Citra Emulator Project +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/archives.h" +#include "common/microprofile.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/service/gsp/gsp_gpu.h" +#include "core/hle/service/plgldr/plgldr.h" +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/gpu.h" +#include "video_core/gpu_debugger.h" +#include "video_core/gpu_impl.h" +#include "video_core/pica/pica_core.h" +#include "video_core/pica/regs_lcd.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_software/sw_blitter.h" +#include "video_core/right_eye_disabler.h" +#include "video_core/video_core.h" + +namespace VideoCore { +struct GPU::Impl { + Core::Timing& timing; + Core::System& system; + Memory::MemorySystem& memory; + std::shared_ptr debug_context; + Pica::PicaCore pica; + GraphicsDebugger gpu_debugger; + std::unique_ptr renderer; + RasterizerInterface* rasterizer; + std::unique_ptr sw_blitter; + Core::TimingEventType* vblank_event; + Service::GSP::InterruptHandler signal_interrupt; + + explicit Impl(Core::System& system, Frontend::EmuWindow& emu_window, + Frontend::EmuWindow* secondary_window) + : timing{system.CoreTiming()}, system{system}, memory{system.Memory()}, + debug_context{Pica::g_debug_context}, pica{memory, debug_context}, + renderer{VideoCore::CreateRenderer(emu_window, secondary_window, pica, system)}, + rasterizer{renderer->Rasterizer()}, + sw_blitter{std::make_unique(memory, rasterizer)} {} + ~Impl() = default; +}; +} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/pica/pica_core.cpp b/src/video_core/pica/pica_core.cpp index 243626a87..d49db6268 100644 --- a/src/video_core/pica/pica_core.cpp +++ b/src/video_core/pica/pica_core.cpp @@ -91,7 +91,11 @@ void PicaCore::SetInterruptHandler(Service::GSP::InterruptHandler& signal_interr this->signal_interrupt = signal_interrupt; } -void PicaCore::ProcessCmdList(PAddr list, u32 size) { +void PicaCore::ProcessCmdList(PAddr list, u32 size, bool ignore_list) { + if (ignore_list) { + signal_interrupt(Service::GSP::InterruptId::P3D); + return; + } // Initialize command list tracking. const u8* head = memory.GetPhysicalPointer(list); cmd_list.Reset(list, head, size); @@ -610,11 +614,78 @@ void PicaCore::LoadVertices(bool is_indexed) { } } +PicaCore::RenderPropertiesGuess PicaCore::GuessCmdRenderProperties(PAddr list, u32 size) { + // Initialize command list tracking. + const u8* head = memory.GetPhysicalPointer(list); + cmd_list.Reset(list, head, size); + + constexpr size_t max_iterations = 0x100; + + RenderPropertiesGuess find_info{}; + + find_info.vp_height = regs.internal.rasterizer.viewport_size_y.Value(); + find_info.paddr = regs.internal.framebuffer.framebuffer.color_buffer_address.Value() * 8; + + auto process_write = [this, &find_info](u32 cmd_id, u32 value) { + switch (cmd_id) { + case PICA_REG_INDEX(rasterizer.viewport_size_y): + find_info.vp_height = value; + find_info.vp_heigh_found = true; + break; + case PICA_REG_INDEX(framebuffer.framebuffer.color_buffer_address): + find_info.paddr = value * 8; + find_info.paddr_found = true; + break; + [[unlikely]] case PICA_REG_INDEX(pipeline.command_buffer.trigger[0]) : + [[unlikely]] case PICA_REG_INDEX(pipeline.command_buffer.trigger[1]) : { + const u32 index = + static_cast(cmd_id - PICA_REG_INDEX(pipeline.command_buffer.trigger[0])); + const PAddr addr = regs.internal.pipeline.command_buffer.GetPhysicalAddress(index); + const u32 size = regs.internal.pipeline.command_buffer.GetSize(index); + const u8* head = memory.GetPhysicalPointer(addr); + cmd_list.Reset(addr, head, size); + break; + } + default: + break; + } + return find_info.vp_heigh_found && find_info.paddr_found; + }; + + size_t iterations = 0; + while (cmd_list.current_index < cmd_list.length && iterations < max_iterations) { + // Align read pointer to 8 bytes + if (cmd_list.current_index % 2 != 0) { + cmd_list.current_index++; + } + + // Read the header and the value to write. + const u32 value = cmd_list.head[cmd_list.current_index++]; + const CommandHeader header{cmd_list.head[cmd_list.current_index++]}; + + // Write to the requested PICA register. + if (process_write(header.cmd_id, value)) + break; + + // Write any extra paramters as well. + for (u32 i = 0; i < header.extra_data_length; ++i) { + const u32 cmd = header.cmd_id + (header.group_commands ? i + 1 : 0); + const u32 extra_value = cmd_list.head[cmd_list.current_index++]; + if (process_write(cmd, extra_value)) + break; + } + + iterations++; + } + + return find_info; +} + template void PicaCore::CommandList::serialize(Archive& ar, const u32 file_version) { - ar & addr; - ar & length; - ar & current_index; + ar& addr; + ar& length; + ar& current_index; if (Archive::is_loading::value) { const u8* ptr = Core::System::GetInstance().Memory().GetPhysicalPointer(addr); head = reinterpret_cast(ptr); diff --git a/src/video_core/pica/pica_core.h b/src/video_core/pica/pica_core.h index f2af318c7..5eb4e9239 100644 --- a/src/video_core/pica/pica_core.h +++ b/src/video_core/pica/pica_core.h @@ -4,6 +4,7 @@ #pragma once +#include "common/common_types.h" #include "core/hle/service/gsp/gsp_interrupt.h" #include "video_core/pica/geometry_pipeline.h" #include "video_core/pica/packed_attribute.h" @@ -36,7 +37,7 @@ public: void SetInterruptHandler(Service::GSP::InterruptHandler& signal_interrupt); - void ProcessCmdList(PAddr list, u32 size); + void ProcessCmdList(PAddr list, u32 size, bool ignore_list); private: void InitializeRegs(); @@ -109,10 +110,10 @@ public: friend class boost::serialization::access; template void serialize(Archive& ar, const u32 file_version) { - ar & input_vertex; - ar & current_attribute; - ar & reset_geometry_pipeline; - ar & queue; + ar& input_vertex; + ar& current_attribute; + ar& reset_geometry_pipeline; + ar& queue; } }; @@ -199,7 +200,7 @@ public: template void serialize(Archive& ar, const u32 file_version) { - ar & raw; + ar& raw; } }; @@ -256,21 +257,31 @@ private: friend class boost::serialization::access; template void serialize(Archive& ar, const u32 file_version) { - ar & regs_lcd; - ar & regs.reg_array; - ar & gs_unit; - ar & vs_setup; - ar & gs_setup; - ar & proctex; - ar & lighting; - ar & fog; - ar & input_default_attributes; - ar & immediate; - ar & geometry_pipeline; - ar & primitive_assembler; - ar & cmd_list; + ar& regs_lcd; + ar& regs.reg_array; + ar& gs_unit; + ar& vs_setup; + ar& gs_setup; + ar& proctex; + ar& lighting; + ar& fog; + ar& input_default_attributes; + ar& immediate; + ar& geometry_pipeline; + ar& primitive_assembler; + ar& cmd_list; } +public: + struct RenderPropertiesGuess { + u32 vp_height; + PAddr paddr; + bool vp_heigh_found = false; + bool paddr_found = false; + }; + + RenderPropertiesGuess GuessCmdRenderProperties(PAddr list, u32 size); + private: Memory::MemorySystem& memory; VideoCore::RasterizerInterface* rasterizer; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index a75f10e6f..cd74bae5e 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -82,5 +82,12 @@ public: [[maybe_unused]] const DiskResourceLoadCallback& callback) {} virtual void SyncEntireState() {} + + void SetAccurateMul(bool accurate_mul_) { + accurate_mul = accurate_mul_; + } + +protected: + bool accurate_mul = false; }; } // namespace VideoCore diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index e78411b40..b16d239e2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -175,7 +175,7 @@ void RasterizerOpenGL::TickFrame() { void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { - shader_manager.LoadDiskCache(stop_loading, callback); + shader_manager.LoadDiskCache(stop_loading, callback, accurate_mul); } void RasterizerOpenGL::SyncFixedState() { @@ -271,7 +271,7 @@ void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, bool RasterizerOpenGL::SetupVertexShader() { MICROPROFILE_SCOPE(OpenGL_VS); - return shader_manager.UseProgrammableVertexShader(regs, pica.vs_setup); + return shader_manager.UseProgrammableVertexShader(regs, pica.vs_setup, accurate_mul); } bool RasterizerOpenGL::SetupGeometryShader() { @@ -333,7 +333,7 @@ bool RasterizerOpenGL::AccelerateDrawBatchInternal(bool is_indexed) { SetupVertexArray(buffer_ptr, buffer_offset, vs_input_index_min, vs_input_index_max); vertex_buffer.Unmap(vs_input_size); - shader_manager.ApplyTo(state); + shader_manager.ApplyTo(state, accurate_mul); state.Apply(); if (is_indexed) { @@ -458,7 +458,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { state.draw.vertex_buffer = vertex_buffer.GetHandle(); shader_manager.UseTrivialVertexShader(); shader_manager.UseTrivialGeometryShader(); - shader_manager.ApplyTo(state); + shader_manager.ApplyTo(state, accurate_mul); state.Apply(); std::size_t max_vertices = 3 * (VERTEX_BUFFER_SIZE / (3 * sizeof(HardwareVertex))); @@ -970,9 +970,10 @@ void RasterizerOpenGL::SyncAndUploadLUTsLF() { if (fs_uniform_block_data.fog_lut_dirty || invalidate) { std::array new_data; - std::transform( - pica.fog.lut.begin(), pica.fog.lut.end(), new_data.begin(), - [](const auto& entry) { return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()}; }); + std::transform(pica.fog.lut.begin(), pica.fog.lut.end(), new_data.begin(), + [](const auto& entry) { + return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()}; + }); if (new_data != fog_lut_data || invalidate) { fog_lut_data = new_data; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index b446d42b5..6681980c2 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -82,7 +82,7 @@ static std::set GetSupportedFormats() { } static std::tuple BuildVSConfigFromRaw( - const ShaderDiskCacheRaw& raw, const Driver& driver) { + const ShaderDiskCacheRaw& raw, const Driver& driver, bool accurate_mul) { Pica::ProgramCode program_code{}; Pica::SwizzleData swizzle_data{}; std::copy_n(raw.GetProgramCode().begin(), Pica::MAX_PROGRAM_CODE_LENGTH, program_code.begin()); @@ -96,7 +96,7 @@ static std::tuple BuildVSConfigFromRaw( // and care about proper quaternions. Otherwise just use standard vertex+fragment shaders const bool use_geometry_shader = !raw.GetRawShaderConfig().lighting.disable; return {PicaVSConfig{raw.GetRawShaderConfig(), setup, driver.HasClipCullDistance(), - use_geometry_shader}, + use_geometry_shader, accurate_mul}, setup}; } @@ -337,12 +337,14 @@ ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, con ShaderProgramManager::~ShaderProgramManager() = default; bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::RegsInternal& regs, - Pica::ShaderSetup& setup) { + Pica::ShaderSetup& setup, + bool accurate_mul) { // Enable the geometry-shader only if we are actually doing per-fragment lighting // and care about proper quaternions. Otherwise just use standard vertex+fragment shaders const bool use_geometry_shader = !regs.lighting.disable; - PicaVSConfig config{regs, setup, driver.HasClipCullDistance(), use_geometry_shader}; + PicaVSConfig config{regs, setup, driver.HasClipCullDistance(), use_geometry_shader, + accurate_mul}; auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup); if (handle == 0) return false; @@ -358,9 +360,8 @@ bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::RegsInternal& const u64 unique_identifier = GetUniqueIdentifier(regs, program_code); const ShaderDiskCacheRaw raw{unique_identifier, ProgramType::VS, regs, std::move(program_code)}; - const bool sanitize_mul = Settings::values.shaders_accurate_mul.GetValue(); disk_cache.SaveRaw(raw); - disk_cache.SaveDecompiled(unique_identifier, *result, sanitize_mul); + disk_cache.SaveDecompiled(unique_identifier, *result, accurate_mul); } return true; } @@ -398,7 +399,7 @@ void ShaderProgramManager::UseFragmentShader(const Pica::RegsInternal& regs, } } -void ShaderProgramManager::ApplyTo(OpenGLState& state) { +void ShaderProgramManager::ApplyTo(OpenGLState& state, bool accurate_mul) { if (impl->separable) { if (driver.HasBug(DriverBug::ShaderStageChangeFreeze)) { glUseProgramStages( @@ -418,15 +419,15 @@ void ShaderProgramManager::ApplyTo(OpenGLState& state) { cached_program.Create(false, std::array{impl->current.vs, impl->current.gs, impl->current.fs}); auto& disk_cache = impl->disk_cache; - const bool sanitize_mul = Settings::values.shaders_accurate_mul.GetValue(); - disk_cache.SaveDumpToFile(unique_identifier, cached_program.handle, sanitize_mul); + disk_cache.SaveDumpToFile(unique_identifier, cached_program.handle, accurate_mul); } state.draw.shader_program = cached_program.handle; } } void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, - const VideoCore::DiskResourceLoadCallback& callback) { + const VideoCore::DiskResourceLoadCallback& callback, + bool accurate_mul) { auto& disk_cache = impl->disk_cache; const auto transferable = disk_cache.LoadTransferable(); if (!transferable) { @@ -484,9 +485,8 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, if (dump != dump_map.end() && decomp != decompiled_map.end()) { // Only load the vertex shader if its sanitize_mul setting matches - const bool sanitize_mul = Settings::values.shaders_accurate_mul.GetValue(); if (raw.GetProgramType() == ProgramType::VS && - decomp->second.sanitize_mul != sanitize_mul) { + decomp->second.sanitize_mul != accurate_mul) { continue; } @@ -502,7 +502,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, // we have both the binary shader and the decompiled, so inject it into the // cache if (raw.GetProgramType() == ProgramType::VS) { - auto [conf, setup] = BuildVSConfigFromRaw(raw, driver); + auto [conf, setup] = BuildVSConfigFromRaw(raw, driver, accurate_mul); std::scoped_lock lock(mutex); impl->programmable_vertex_shaders.Inject(conf, decomp->second.code, std::move(shader)); @@ -541,8 +541,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, const auto decomp{decompiled_map.find(unique_identifier)}; // Only load the program if its sanitize_mul setting matches - const bool sanitize_mul = Settings::values.shaders_accurate_mul.GetValue(); - if (decomp->second.sanitize_mul != sanitize_mul) { + if (decomp->second.sanitize_mul != accurate_mul) { continue; } @@ -610,7 +609,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, // Otherwise decompile and build the shader at boot and save the result to the // precompiled file if (raw.GetProgramType() == ProgramType::VS) { - auto [conf, setup] = BuildVSConfigFromRaw(raw, driver); + auto [conf, setup] = BuildVSConfigFromRaw(raw, driver, accurate_mul); code = GLSL::GenerateVertexShader(setup, conf, impl->separable); OGLShaderStage stage{impl->separable}; stage.Create(code.c_str(), GL_VERTEX_SHADER); diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 705772b17..a42aedd5f 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -38,9 +38,10 @@ public: ~ShaderProgramManager(); void LoadDiskCache(const std::atomic_bool& stop_loading, - const VideoCore::DiskResourceLoadCallback& callback); + const VideoCore::DiskResourceLoadCallback& callback, bool accurate_mul); - bool UseProgrammableVertexShader(const Pica::RegsInternal& config, Pica::ShaderSetup& setup); + bool UseProgrammableVertexShader(const Pica::RegsInternal& config, Pica::ShaderSetup& setup, + bool accurate_mul); void UseTrivialVertexShader(); @@ -50,7 +51,7 @@ public: void UseFragmentShader(const Pica::RegsInternal& config, const Pica::Shader::UserConfig& user); - void ApplyTo(OpenGLState& state); + void ApplyTo(OpenGLState& state, bool accurate_mul); private: Frontend::EmuWindow& emu_window; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index df63e1697..98f68abfb 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -334,13 +334,14 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) { bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, - const VertexLayout& layout) { + const VertexLayout& layout, bool accurate_mul) { // Enable the geometry-shader only if we are actually doing per-fragment lighting // and care about proper quaternions. Otherwise just use standard vertex+fragment shaders. // We also don't need the geometry shader if we have the barycentric extension. const bool use_geometry_shader = instance.UseGeometryShaders() && !regs.lighting.disable && !instance.IsFragmentShaderBarycentricSupported(); - PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), use_geometry_shader}; + PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), use_geometry_shader, + accurate_mul}; for (u32 i = 0; i < layout.attribute_count; i++) { const VertexAttribute& attr = layout.attributes[i]; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 5abb040d6..6bb85d5e4 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -68,7 +68,7 @@ public: /// Binds a PICA decompiled vertex shader bool UseProgrammableVertexShader(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, - const VertexLayout& layout); + const VertexLayout& layout, bool accurate_mul); /// Binds a passthrough vertex shader void UseTrivialVertexShader(); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9c41f4883..1c481c98d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -330,7 +330,7 @@ void RasterizerVulkan::SetupFixedAttribs() { bool RasterizerVulkan::SetupVertexShader() { MICROPROFILE_SCOPE(Vulkan_VS); return pipeline_cache.UseProgrammableVertexShader(regs, pica.vs_setup, - pipeline_info.vertex_layout); + pipeline_info.vertex_layout, accurate_mul); } bool RasterizerVulkan::SetupGeometryShader() { @@ -963,9 +963,10 @@ void RasterizerVulkan::SyncAndUploadLUTsLF() { if (fs_uniform_block_data.fog_lut_dirty || invalidate) { std::array new_data; - std::transform( - pica.fog.lut.begin(), pica.fog.lut.end(), new_data.begin(), - [](const auto& entry) { return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()}; }); + std::transform(pica.fog.lut.begin(), pica.fog.lut.end(), new_data.begin(), + [](const auto& entry) { + return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()}; + }); if (new_data != fog_lut_data || invalidate) { fog_lut_data = new_data; diff --git a/src/video_core/right_eye_disabler.cpp b/src/video_core/right_eye_disabler.cpp new file mode 100644 index 000000000..2cecfc232 --- /dev/null +++ b/src/video_core/right_eye_disabler.cpp @@ -0,0 +1,75 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/settings.h" +#include "right_eye_disabler.h" +#include "video_core/gpu.h" +#include "video_core/gpu_impl.h" + +namespace VideoCore { +bool RightEyeDisabler::ShouldAllowCmdQueueTrigger(PAddr addr, u32 size) { + if (!enabled || !enable_for_frame) + return true; + + constexpr u32 top_screen_size = 0x00469000; + + if (report_end_frame_pending) { + ReportEndFrame(); + report_end_frame_pending = false; + } + cmd_queue_trigger_happened = true; + + auto guess = gpu.impl->pica.GuessCmdRenderProperties(addr, size); + if (guess.vp_height == top_screen_size && !top_screen_blocked) { + if (top_screen_buf == 0) { + top_screen_buf = guess.paddr; + } + top_screen_drawn = true; + if (top_screen_transfered) { + cmd_trigger_blocked = true; + return false; + } + } + + cmd_trigger_blocked = false; + return true; +} +bool RightEyeDisabler::ShouldAllowDisplayTransfer(PAddr src_address, size_t size) { + if (!enabled || !enable_for_frame) + return true; + + if (size >= 400 && !top_screen_blocked) { + if (top_screen_drawn && src_address == top_screen_buf) { + top_screen_transfered = true; + } + + if (src_address == top_screen_buf && cmd_trigger_blocked) { + top_screen_blocked = true; + return false; + } + } + + if (cmd_queue_trigger_happened) + display_tranfer_happened = true; + return true; +} +void RightEyeDisabler::ReportEndFrame() { + if (!enabled) + return; + + enable_for_frame = Settings::values.disable_right_eye_render.GetValue(); + + if (display_tranfer_happened) { + top_screen_drawn = false; + top_screen_transfered = false; + top_screen_blocked = false; + cmd_queue_trigger_happened = false; + cmd_trigger_blocked = false; + display_tranfer_happened = false; + top_screen_buf = 0; + } else { + report_end_frame_pending = true; + } +} +} // namespace VideoCore diff --git a/src/video_core/right_eye_disabler.h b/src/video_core/right_eye_disabler.h new file mode 100644 index 000000000..8d75d2479 --- /dev/null +++ b/src/video_core/right_eye_disabler.h @@ -0,0 +1,41 @@ +// Copyright 2024 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace VideoCore { +class GPU; + +class RightEyeDisabler { +public: + RightEyeDisabler(GPU& gpu) : gpu{gpu} {} + + bool ShouldAllowCmdQueueTrigger(PAddr addr, u32 size); + bool ShouldAllowDisplayTransfer(PAddr src_address, size_t size); + + void ReportEndFrame(); + + void SetEnabled(bool enable) { + enabled = enable; + } + +private: + bool enabled = true; + bool enable_for_frame = true; + + bool top_screen_drawn = false; + bool top_screen_transfered = false; + bool top_screen_blocked = false; + bool cmd_trigger_blocked = false; + PAddr top_screen_buf = 0; + + bool cmd_queue_trigger_happened = false; + bool display_tranfer_happened = false; + bool report_end_frame_pending = false; + + GPU& gpu; +}; +} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/shader/generator/shader_gen.cpp b/src/video_core/shader/generator/shader_gen.cpp index 70be1bd40..6d7e61190 100644 --- a/src/video_core/shader/generator/shader_gen.cpp +++ b/src/video_core/shader/generator/shader_gen.cpp @@ -37,14 +37,14 @@ void PicaGSConfigState::Init(const Pica::RegsInternal& regs, bool use_clip_plane } void PicaVSConfigState::Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, - bool use_clip_planes_, bool use_geometry_shader_) { + bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_) { use_clip_planes = use_clip_planes_; use_geometry_shader = use_geometry_shader_; + sanitize_mul = accurate_mul_; program_hash = setup.GetProgramCodeHash(); swizzle_hash = setup.GetSwizzleDataHash(); main_offset = regs.vs.main_offset; - sanitize_mul = Settings::values.shaders_accurate_mul.GetValue(); num_outputs = 0; load_flags.fill(AttribLoadFlags::Float); @@ -60,8 +60,8 @@ void PicaVSConfigState::Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& } PicaVSConfig::PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, - bool use_clip_planes_, bool use_geometry_shader_) { - state.Init(regs, setup, use_clip_planes_, use_geometry_shader_); + bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_) { + state.Init(regs, setup, use_clip_planes_, use_geometry_shader_, accurate_mul_); } PicaFixedGSConfig::PicaFixedGSConfig(const Pica::RegsInternal& regs, bool use_clip_planes_) { diff --git a/src/video_core/shader/generator/shader_gen.h b/src/video_core/shader/generator/shader_gen.h index 02a9ca33c..560dfc6a1 100644 --- a/src/video_core/shader/generator/shader_gen.h +++ b/src/video_core/shader/generator/shader_gen.h @@ -66,7 +66,7 @@ struct PicaGSConfigState { */ struct PicaVSConfigState { void Init(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, bool use_clip_planes_, - bool use_geometry_shader_); + bool use_geometry_shader_, bool accurate_mul_); bool use_clip_planes; bool use_geometry_shader; @@ -92,7 +92,7 @@ struct PicaVSConfigState { */ struct PicaVSConfig : Common::HashableStruct { explicit PicaVSConfig(const Pica::RegsInternal& regs, Pica::ShaderSetup& setup, - bool use_clip_planes_, bool use_geometry_shader_); + bool use_clip_planes_, bool use_geometry_shader_, bool accurate_mul_); }; /**