From c4b7d06619d096058f583c62d2bee31b8f0f7fdb Mon Sep 17 00:00:00 2001 From: MaranBr Date: Fri, 10 Jul 2026 05:25:12 +0200 Subject: [PATCH] [video_core] Improve synchronization and refactor buffer cache timing logic (#4182) This simplifies the GPU accuracy setting by removing the intermediate Balanced mode and setting High as the default on desktop platforms. It introduces a dedicated setting for GPU fence behavior, allowing the synchronization policy to be configured independently of GPU accuracy. The Vulkan buffer cache now tracks GPU recording timeline ticks and waits only when necessary, reducing unnecessary synchronization while maintaining correctness for hard-to-trace graphical bugs. GPU buffer readback has also been refined to synchronize only the affected upload regions when needed, and default DMA behavior has been updated to align with the new GPU accuracy model. ### TL;DR The fix for particles freezing and unfreezing in mid-air in `Super Mario Odyssey` has been improved, resulting in less of a performance hit. This game requires the new `Enable GPU Buffer Readback` option to be enabled to fix this issue. The vertex explosions that occurred in `Super Mario Bros. Wonder`, especially in World 4, have been completely eliminated. You can now enjoy a smooth experience without graphical glitches exploding across the screen. This game requires the new `GPU Fence Behavior` option to be set to `Strict` to fully fix this issue. The flickering issue inside certain Shrines in `The Legend of Zelda: Tears of the Kingdom` has also been fixed. For now, this game requires the new `GPU Fence Behavior` option to be set to `Accurate` to fully fix this issue. These options are intended to fix graphical bugs in games that require better synchronization behavior between CPU and GPU, so other games may be affected as well. Co-authored-by: xbzk Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4182 Reviewed-by: Lizzie --- .../features/settings/model/BooleanSetting.kt | 1 - .../features/settings/model/IntSetting.kt | 1 + .../settings/model/view/SettingsItem.kt | 16 +++--- .../settings/ui/SettingsFragmentPresenter.kt | 2 +- .../app/src/main/res/values-ar/strings.xml | 2 - .../app/src/main/res/values-es/strings.xml | 2 - .../app/src/main/res/values-ru/strings.xml | 2 - .../app/src/main/res/values-uk/strings.xml | 2 - .../src/main/res/values-zh-rCN/strings.xml | 2 - .../app/src/main/res/values/arrays.xml | 15 ++++++ .../app/src/main/res/values/strings.xml | 11 ++++- src/common/settings.cpp | 24 ++++++--- src/common/settings.h | 28 ++++++----- src/common/settings_enums.h | 3 +- src/qt_common/config/shared_translation.cpp | 22 +++++---- src/qt_common/config/shared_translation.h | 1 - src/video_core/buffer_cache/buffer_base.h | 9 ++++ src/video_core/buffer_cache/buffer_cache.h | 49 ++++++++++++------- .../buffer_cache/buffer_cache_base.h | 2 + src/video_core/dma_pusher.cpp | 3 +- src/video_core/fence_manager.h | 9 ++-- src/video_core/query_cache/query_cache.h | 2 +- .../renderer_vulkan/vk_buffer_cache.cpp | 12 +++++ .../renderer_vulkan/vk_buffer_cache.h | 6 +++ .../renderer_vulkan/vk_scheduler.cpp | 24 +-------- src/video_core/renderer_vulkan/vk_scheduler.h | 2 - src/yuzu/main_window.cpp | 3 -- 27 files changed, 151 insertions(+), 104 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index ad983bd749..7f6e793199 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -16,7 +16,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_USE_SPEED_LIMIT("use_speed_limit"), USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"), SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), - ANTIFLICKER("antiflicker"), FIX_BLOOM_EFFECTS("fix_bloom_effects"), EMULATE_BGR565("emulate_bgr565"), RESCALE_HACK("rescale_hack"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 60e2a89564..117336ab64 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -27,6 +27,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { RENDERER_DYNA_STATE("dyna_state"), DMA_ACCURACY("dma_accuracy"), + GPU_FENCE_BEHAVIOR("gpu_fence_behavior"), FRAME_PACING_MODE("frame_pacing_mode"), AUDIO_OUTPUT_ENGINE("output_engine"), MAX_ANISOTROPY("max_anisotropy"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 67cf392fef..7c6063988f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -669,6 +669,15 @@ abstract class SettingsItem( valuesId = R.array.dmaAccuracyValues ) ) + put( + SingleChoiceSetting( + IntSetting.GPU_FENCE_BEHAVIOR, + titleId = R.string.gpu_fence_behavior, + descriptionId = R.string.gpu_fence_behavior_description, + choicesId = R.array.gpuFenceBehaviorNames, + valuesId = R.array.gpuFenceBehaviorValues + ) + ) put( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, @@ -757,13 +766,6 @@ abstract class SettingsItem( descriptionId = R.string.skip_cpu_inner_invalidation_description ) ) - put( - SwitchSetting( - BooleanSetting.ANTIFLICKER, - titleId = R.string.antiflicker, - descriptionId = R.string.antiflicker_description - ) - ) put( SwitchSetting( BooleanSetting.FIX_BLOOM_EFFECTS, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 7b7762520d..81702d7110 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -283,6 +283,7 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key) + add(IntSetting.GPU_FENCE_BEHAVIOR.key) add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) @@ -299,7 +300,6 @@ class SettingsFragmentPresenter( add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) - add(BooleanSetting.ANTIFLICKER.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.EMULATE_BGR565.key) add(BooleanSetting.RESCALE_HACK.key) diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index 16a34f2f4e..57c1cdde44 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -506,8 +506,6 @@ يُجبر هذا الخيار معظم الألعاب على العمل بأعلى دقة عرض أصلية. استخدم 256 للحصول على أقصى أداء و512 للحصول على أعلى جودة رسومات. تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب. - مضاد الوميض - يُجبر هذا الوضع وظائف وحدة معالجة الرسومات على الانتظار حتى يتم إرسال العمل إليها. استخدمه مع وضع وحدة معالجة الرسومات السريع لتجنب الوميض مع تأثير أقل على الأداء. إصلاح تأثيرات التوهج يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى. محاكاة BGR565 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 7e477ddba9..2143c9b456 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -502,8 +502,6 @@ Fuerza a la mayoría de los juegos a ejecutarse a su resolución nativa más alta. Usa 256 para un máximo rendimiento y 512 para una fidelidad gráfica óptima. Omitir invalidación interna de la CPU Omite ciertas invalidaciones de caché de la CPU durante las actualizaciones de memoria, lo que reduce el uso de la CPU y mejora su rendimiento. Esto puede causar fallos o bloqueos en algunos juegos. - Antiparpadeo - Fuerza a las funciones de devolución de llamada de la GPU a esperar a que se envíen las tareas a la GPU.\nÚsalo con el modo de GPU rápida para evitar el parpadeo con un menor impacto en el rendimiento. Arreglar los efectos de resplandor Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos. Emular BGR565 diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index c6fd58f04b..7fbd94923a 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -499,8 +499,6 @@ Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики. Пропустить внутреннюю инвалидацию ЦП Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх. - Анти-мерцание - Принудительно заставляет обратные вызовы ГПУ-фильтра ожидать выполнения отправленных задач на ГПУ. Используйте с Быстрым режимом ГПУ, что бы избежать мерцаний с меньшим влиянием на производительность. Исправить эффекты размытия Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх. Эмулировать BGR565 diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 6e21a5cafa..f87d7154e8 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -502,8 +502,6 @@ Примушує більшість ігор працювати на їхній максимальній нативній роздільності. Використовуйте 256 для максимальної продуктивності та 512 для найкращої якості. Пропустити внутрішнє інвалідування CPU Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх. - Антимерехтіння - Змушує механізм синхронізації чекати, доки ГП завершить подані завдання. Використовуйте з режимом ГП «Швидко», щоб уникнути мерехтіння з меншими втратами продуктивності. Виправити ефекти світіння Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх. Емулювати BGR565 diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index aa1876e43f..d3710268f3 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -498,8 +498,6 @@ 强制大多数游戏以其最高原生分辨率运行。设置为 256 可获得最佳性能,设置为 512 可获得最佳画面保真度。 跳过CPU内部无效化 在更新内存时跳过某些 CPU 端的缓存失效操作,从而降低 CPU 占用率并提升性能。可能会在某些游戏中引发故障点或崩溃。 - 防闪烁 - 强制 GPU 围栏回调等待已提交的 GPU 任务。配合“快速 GPU 模式”一起使用,以牺牲少量性能为代价来避免画面闪烁现象。 修复 Bloom 效果 减少《智慧的再现》和《众神的三角力量2》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。 模拟 BGR565 diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index f3a2a069e7..03390f9645 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -522,6 +522,21 @@ 2 + + @string/gpu_fence_behavior_default + @string/gpu_fence_behavior_immediate + @string/gpu_fence_behavior_balanced + @string/gpu_fence_behavior_accurate + @string/gpu_fence_behavior_strict + + + 0 + 1 + 2 + 3 + 4 + + @string/applet_hle diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c67c2f279f..cd5d0c41e7 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -482,6 +482,8 @@ Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. DMA Accuracy Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default. + GPU Fence Behavior + Controls the GPU fence synchronization behavior. Immediate is the fastest option, but can introduce some issues. Balanced offers better compatibility and may fix issues in some games. Accurate further improves compatibility at the cost of some performance. Strict is the slowest option, but can fix issues that require stricter synchronization. Default follows the GPU Accuracy setting. Anisotropic filtering Improves the quality of textures when viewed at oblique angles VRAM Usage Mode @@ -514,8 +516,6 @@ Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity. Skip CPU Inner Invalidation Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games. - Anti-Flicker - Forces GPU fence callbacks to wait for submitted GPU work. Use with Fast GPU mode, to avoid flicker with lower performance impact. Fix Bloom Effects Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games. Emulate BGR565 @@ -1047,6 +1047,13 @@ Unsafe Safe + + Default + Immediate + Balanced + Accurate + Strict + CPU GPU diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 9b582f1c2a..db318613dd 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -156,14 +156,6 @@ void UpdateGPUAccuracy() { values.current_gpu_accuracy = values.gpu_accuracy.GetValue(); } -bool IsGPULevelLow() { - return values.current_gpu_accuracy == GpuAccuracy::Low; -} - -bool IsGPULevelMedium() { - return values.current_gpu_accuracy == GpuAccuracy::Medium; -} - bool IsGPULevelHigh() { return values.current_gpu_accuracy == GpuAccuracy::High; } @@ -176,6 +168,22 @@ bool IsDMALevelSafe() { return values.dma_accuracy.GetValue() == DmaAccuracy::Safe; } +bool IsGPUFenceBehaviorDefault() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Default; +} + +bool IsGPUFenceBehaviorBalanced() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Balanced; +} + +bool IsGPUFenceBehaviorAccurate() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate; +} + +bool IsGPUFenceBehaviorStrict() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Strict; +} + bool IsFastmemEnabled() { if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) return bool(values.cpuopt_fastmem); diff --git a/src/common/settings.h b/src/common/settings.h index 8202bc6e77..00ba7dddc9 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -420,7 +420,7 @@ struct Values { #ifdef __ANDROID__ GpuAccuracy::Low, #else - GpuAccuracy::Medium, + GpuAccuracy::High, #endif "gpu_accuracy", Category::RendererAdvanced, @@ -428,7 +428,7 @@ struct Values { true, true}; - GpuAccuracy current_gpu_accuracy{GpuAccuracy::Medium}; + GpuAccuracy current_gpu_accuracy{GpuAccuracy::High}; SwitchableSetting dma_accuracy{linkage, DmaAccuracy::Default, @@ -438,6 +438,16 @@ struct Values { true, true}; + SwitchableSetting gpu_fence_behavior{linkage, + GpuFenceBehavior::Default, + GpuFenceBehavior::Default, + GpuFenceBehavior::Strict, + "gpu_fence_behavior", + Category::RendererAdvanced, + Specialization::Default, + true, + true}; + SwitchableSetting vram_usage_mode{linkage, VramUsageMode::Conservative, "vram_usage_mode", @@ -545,13 +555,6 @@ struct Values { Specialization::Default, true, true}; - SwitchableSetting antiflicker{linkage, - false, - "antiflicker", - Category::RendererHacks, - Specialization::Default, - true, - true}; SwitchableSetting async_presentation{linkage, #ifdef __ANDROID__ false, @@ -871,13 +874,16 @@ extern Values values; bool getDebugKnobAt(u8 i); void UpdateGPUAccuracy(); -bool IsGPULevelLow(); -bool IsGPULevelMedium(); bool IsGPULevelHigh(); bool IsDMALevelDefault(); bool IsDMALevelSafe(); +bool IsGPUFenceBehaviorDefault(); +bool IsGPUFenceBehaviorBalanced(); +bool IsGPUFenceBehaviorAccurate(); +bool IsGPUFenceBehaviorStrict(); + bool IsFastmemEnabled(); void SetNceEnabled(bool is_64bit); bool IsNceEnabled(); diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index b14ed9bc67..2db0e02a5f 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -135,8 +135,9 @@ ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); -ENUM(GpuAccuracy, Low, Medium, High); +ENUM(GpuAccuracy, Low, High); ENUM(DmaAccuracy, Default, Unsafe, Safe); +ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict); ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging); ENUM(CpuClock, Off, Boost, Fast) diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 72241bab51..893e4a0854 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -193,9 +193,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) { INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"), tr("Skips certain cache invalidations during memory updates, reducing CPU usage and " "improving latency. This may cause soft-crashes.")); - INSERT(Settings, antiflicker, tr("Anti-Flicker"), - tr("Forces GPU fence callbacks to wait for submitted GPU work.\n" - "Use with Fast GPU mode, to avoid flicker with lower performance impact.")); INSERT(Settings, vsync_mode, tr("VSync Mode:"), tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n" @@ -223,14 +220,14 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on " "most GPUs.")); INSERT(Settings, gpu_accuracy, tr("GPU Mode:"), - tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced " - "modes, but Accurate is still " + tr("Controls the GPU emulation mode.\nMost games render fine with Fast, but Accurate is still " "required for some.\nParticles tend to only render correctly with Accurate mode.")); INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), - tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but " - "may degrade performance.")); + tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting.")); + INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"), + tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nStrict is the slowest option, but can fix issues that require stricter synchronization.\nDefault follows the GPU Accuracy setting.")); INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"), - tr("Preserves GPU-modified buffer data by reading it back before uploads.\nSome games require this to render certain effects properly.\nMay cause issues if the hardware cannot handle the additional workload.")); + tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly.")); INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), tr("May reduce shader stutter.")); INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), @@ -430,7 +427,6 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { translations->insert({Settings::EnumMetadata::Index(), { PAIR(GpuAccuracy, Low, tr("Fast")), - PAIR(GpuAccuracy, Medium, tr("Balanced")), PAIR(GpuAccuracy, High, tr("Accurate")), }}); translations->insert({Settings::EnumMetadata::Index(), @@ -439,6 +435,14 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(DmaAccuracy, Unsafe, tr("Unsafe (fast)")), PAIR(DmaAccuracy, Safe, tr("Safe (stable)")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuFenceBehavior, Default, tr("Default")), + PAIR(GpuFenceBehavior, Immediate, tr("Immediate")), + PAIR(GpuFenceBehavior, Balanced, tr("Balanced")), + PAIR(GpuFenceBehavior, Accurate, tr("Accurate")), + PAIR(GpuFenceBehavior, Strict, tr("Strict")), + }}); translations->insert( {Settings::EnumMetadata::Index(), { diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h index c34b5162c4..dbe472caa1 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -64,7 +64,6 @@ static const std::map use_docked_mode_texts_map static const std::map gpu_accuracy_texts_map = { {Settings::GpuAccuracy::Low, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Fast"))}, - {Settings::GpuAccuracy::Medium, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Balanced"))}, {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Accurate"))}, }; diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index bec2dac246..d7eb21612f 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -121,12 +121,21 @@ public: return size_bytes; } + u64 getWriteTick() const noexcept { + return write_tick; + } + + void setWriteTick(u64 write_tick_) { + write_tick = write_tick_; + } + private: VAddr cpu_addr = 0; BufferFlagBits flags{}; int stream_score = 0; size_t lru_id = SIZE_MAX; size_t size_bytes = 0; + u64 write_tick = 0; }; } // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 740d99558e..e40aad1fb5 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1430,6 +1430,10 @@ void BufferCache

::UpdateComputeTextureBuffers() { template void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size) { + if constexpr (!IS_OPENGL) { + Buffer& buffer = slot_buffers[buffer_id]; + buffer.setWriteTick(runtime.CurrentTick()); + } memory_tracker.MarkRegionAsGpuModified(device_addr, size); gpu_modified_ranges.Add(device_addr, size); uncommitted_gpu_modified_ranges.Add(device_addr, size); @@ -1442,16 +1446,32 @@ BufferId BufferCache

::FindBuffer(DAddr device_addr, u32 size) { } const u64 page = device_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - return CreateBuffer(device_addr, size); - } - const Buffer& buffer = slot_buffers[buffer_id]; - if (buffer.IsInBounds(device_addr, size)) { - return buffer_id; + if (buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + WaitForGpuFenceIfNeeded(buffer); + if (buffer.IsInBounds(device_addr, size)) { + return buffer_id; + } } return CreateBuffer(device_addr, size); } +template +void BufferCache

::WaitForGpuFenceIfNeeded(Buffer& buffer) { + if constexpr (!IS_OPENGL) { + const bool gpu_fence_accurate = Settings::IsGPUFenceBehaviorAccurate(); + const bool gpu_fence_strict = Settings::IsGPUFenceBehaviorStrict(); + if (gpu_fence_accurate || gpu_fence_strict) { + const u64 gpu_tick_delay = gpu_fence_strict ? 0 : 3; + const u64 buffer_tick = buffer.getWriteTick(); + const u64 gpu_tick = runtime.KnownGpuTick(); + if (buffer_tick > gpu_tick + gpu_tick_delay) { + runtime.Wait(buffer_tick); + } + } + } +} + template typename BufferCache

::OverlapResult BufferCache

::ResolveOverlaps(DAddr device_addr, u32 wanted_size) { @@ -1634,17 +1654,6 @@ bool BufferCache

::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si if (total_size_bytes == 0) { return true; } - if (Settings::values.enable_gpu_buffer_readback.GetValue()) { - u64 min_offset = (std::numeric_limits::max)(); - u64 max_offset = 0; - for (const auto& copy : upload_copies) { - min_offset = (std::min)(min_offset, copy.dst_offset); - max_offset = (std::max)(max_offset, copy.dst_offset + copy.size); - } - const DAddr sync_addr = buffer.CpuAddr() + min_offset; - const u64 sync_size = max_offset - min_offset; - DownloadBufferMemory(buffer, sync_addr, sync_size); - } const std::span copies_span(upload_copies.data(), upload_copies.size()); UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); any_buffer_uploaded = true; @@ -1679,6 +1688,9 @@ void BufferCache

::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer, if (immediate_buffer.empty()) { immediate_buffer = ImmediateBuffer(largest_copy); } + if (Settings::values.enable_gpu_buffer_readback.GetValue()) { + DownloadBufferMemory(buffer, device_addr, copy.size); + } device_memory.ReadBlockUnsafe(device_addr, immediate_buffer.data(), copy.size); upload_span = immediate_buffer.subspan(0, copy.size); } @@ -1697,6 +1709,9 @@ void BufferCache

::MappedUploadMemory([[maybe_unused]] Buffer& buffer, for (BufferCopy& copy : copies) { u8* const src_pointer = staging_pointer.data() + copy.src_offset; const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; + if (Settings::values.enable_gpu_buffer_readback.GetValue()) { + DownloadBufferMemory(buffer, device_addr, copy.size); + } device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size); // Apply the staging offset copy.src_offset += upload_staging.offset; diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 473cc6842e..14ab3e6ebc 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -416,6 +416,8 @@ private: [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size); + void WaitForGpuFenceIfNeeded(Buffer& buffer); + [[nodiscard]] OverlapResult ResolveOverlaps(DAddr device_addr, u32 wanted_size); void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 41bb43f0c1..2417659f09 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -78,7 +78,8 @@ bool DmaPusher::Step() { } if (header.size > 0) { - if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) { + const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe(); + if (use_safe) { Tegra::Memory::GpuGuestMemoryheaders(memory_manager, dma_state.dma_get, header.size, &command_headers); ProcessCommands(headers); } else { diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index 1b11ff52d2..16f19bf009 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -72,16 +72,13 @@ public: } void SignalFence(std::function&& func) { + const bool delay_fence = Settings::IsGPUFenceBehaviorDefault() ? Settings::IsGPULevelHigh() : Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate() || Settings::IsGPUFenceBehaviorStrict(); + const bool should_flush = ShouldFlush(); if constexpr (!can_async_check) { TryReleasePendingFences(); } - const bool should_flush = ShouldFlush(); - const bool antiflicker_toggled = Settings::values.antiflicker.GetValue(); - const bool delay_fence = Settings::IsGPULevelHigh() || - (Settings::IsGPULevelMedium() && should_flush) || - antiflicker_toggled; CommitAsyncFlushes(); - TFence new_fence = CreateFence(!should_flush && !antiflicker_toggled); + TFence new_fence = CreateFence(!should_flush); if constexpr (can_async_check) { guard.lock(); } diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h index 6bed91a53e..50b662e0ad 100644 --- a/src/video_core/query_cache/query_cache.h +++ b/src/video_core/query_cache/query_cache.h @@ -260,7 +260,7 @@ void QueryCacheBase::CounterReport(GPUVAddr addr, QueryType counter_type }; u8* pointer = impl->device_memory.template GetPointer(cpu_addr); u8* pointer_timestamp = impl->device_memory.template GetPointer(cpu_addr + 8); - bool is_synced = !Settings::IsGPULevelHigh() && is_fence; + bool is_synced = (Settings::IsGPUFenceBehaviorDefault() ? !Settings::IsGPULevelHigh() : !Settings::IsGPUFenceBehaviorBalanced() && !Settings::IsGPUFenceBehaviorAccurate() && !Settings::IsGPUFenceBehaviorStrict()) && is_fence; std::function operation([this, is_synced, streamer, query_base = query, query_location, pointer, pointer_timestamp] { if (True(query_base->flags & QueryFlagBits::IsInvalidated)) { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 3d8307d4dd..8884535f1c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -396,6 +396,18 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noe } } +u64 BufferCacheRuntime::CurrentTick() { + return scheduler.GetMasterSemaphore().CurrentTick(); +} + +u64 BufferCacheRuntime::KnownGpuTick() { + return scheduler.GetMasterSemaphore().KnownGpuTick(); +} + +void BufferCacheRuntime::Wait(u64 buffer_tick) { + scheduler.Wait(buffer_tick); +} + void BufferCacheRuntime::Finish() { scheduler.Finish(); } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 764263717d..d4ad156073 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -92,6 +92,12 @@ public: void TickFrame(Common::SlotVector& slot_buffers) noexcept; + u64 CurrentTick(); + + u64 KnownGpuTick(); + + void Wait(u64 buffer_tick); + void Finish(); u64 GetDeviceLocalMemory() const; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index b2b2dc51b2..c527254d27 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -27,8 +27,6 @@ namespace Vulkan { -constexpr u64 MAX_PENDING_FLUSHES = 5; - void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) { auto command = first; @@ -49,15 +47,6 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) master_semaphore{std::make_unique(device)}, command_pool{std::make_unique(*master_semaphore, device)} { - /*// PRE-OPTIMIZATION: Warm up the pool to prevent mid-frame spikes - { - std::scoped_lock rl{reserve_mutex}; - chunk_reserve.reserve(2048); // Prevent vector resizing - for (int i = 0; i < 1024; ++i) { - chunk_reserve.push_back(std::make_unique()); - } - }*/ - AcquireNewChunk(); AllocateWorkerCommandBuffer(); worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); @@ -66,18 +55,7 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) Scheduler::~Scheduler() = default; u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { - // Prevent the CPU from getting too far ahead of the GPU by limiting pending flushes. - const bool should_throttle = Settings::IsGPULevelHigh(); - if (should_throttle) { - const u64 current_tick = master_semaphore->CurrentTick(); - const u64 gap = current_tick > last_submitted_tick ? current_tick - last_submitted_tick : 0; - const u64 step = (std::min)(MAX_PENDING_FLUSHES, gap); - const u64 new_tick = last_submitted_tick + step; - if (new_tick < current_tick) { - last_submitted_tick = new_tick; - master_semaphore->Wait(last_submitted_tick); - } - } + // When flushing, we only send data to the worker thread; no waiting is necessary. const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore); AllocateNewContext(); return signal_value; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index e848dd5f79..10bfc5570f 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -307,8 +307,6 @@ private: double last_target_fps{}; u64 max_frame_count{}; u64 frame_counter{}; - - u64 last_submitted_tick = 0; }; } // namespace Vulkan diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 2801fbd321..88de185c6a 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -3585,9 +3585,6 @@ void MainWindow::OnToggleDockedMode() { void MainWindow::OnToggleGpuAccuracy() { switch (Settings::values.gpu_accuracy.GetValue()) { case Settings::GpuAccuracy::Low: - Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::Medium); - break; - case Settings::GpuAccuracy::Medium: Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::High); break; case Settings::GpuAccuracy::High: