From 0dc0704f48ff54431f478cf1180a104a1d67f25b Mon Sep 17 00:00:00 2001 From: Bruno <56844494+fab918@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:44:59 +0200 Subject: [PATCH] Port Eden SMO/DMA synchronization improvements (#284) --- lang/english/css/main_window.css | 2 + lang/english/html/main_window.html | 1 + lang/english/html/system_config_graphics.html | 8 + .../hle/service/nvdrv/devices/nvhost_gpu.h | 9 +- src/nxemu-video/video_settings.cpp | 43 +++++ src/nxemu-video/video_settings.h | 6 + src/nxemu-video/video_settings_identifiers.h | 2 + .../user_interface/sciter_main_window.cpp | 22 +++ .../user_interface/settings/system_config.cpp | 5 + .../user_interface/settings/system_config.h | 1 + .../settings/system_config_graphics.cpp | 2 + src/yuzu_video_core/dma_pusher.cpp | 127 +++++++------ src/yuzu_video_core/dma_pusher.h | 12 +- src/yuzu_video_core/fence_manager.h | 170 +++++++++++++----- src/yuzu_video_core/rasterizer_interface.h | 3 + .../renderer_null/null_rasterizer.cpp | 1 + .../renderer_null/null_rasterizer.h | 1 + .../renderer_opengl/gl_rasterizer.cpp | 4 + .../renderer_opengl/gl_rasterizer.h | 1 + .../renderer_vulkan/vk_rasterizer.cpp | 4 + .../renderer_vulkan/vk_rasterizer.h | 1 + 21 files changed, 314 insertions(+), 111 deletions(-) diff --git a/lang/english/css/main_window.css b/lang/english/css/main_window.css index 62f56d95..3c2f60be 100644 --- a/lang/english/css/main_window.css +++ b/lang/english/css/main_window.css @@ -36,6 +36,7 @@ #StatusBar #volume, #StatusBar #renderer, #StatusBar #dockedMode, +#StatusBar #syncMemoryOperations, #StatusBar #volumePopupBtn { border: 1px solid transparent; @@ -47,6 +48,7 @@ #StatusBar #volume:hover, #StatusBar #renderer:hover, #StatusBar #dockedMode:hover, +#StatusBar #syncMemoryOperations:hover, #StatusBar #volumePopupBtn:hover { border: 1px solid color(color-statusbar-hover-border); diff --git a/lang/english/html/main_window.html b/lang/english/html/main_window.html index d33f23c5..1df278c0 100644 --- a/lang/english/html/main_window.html +++ b/lang/english/html/main_window.html @@ -43,6 +43,7 @@
+ diff --git a/lang/english/html/system_config_graphics.html b/lang/english/html/system_config_graphics.html index 9aeb3748..ba8cd815 100644 --- a/lang/english/html/system_config_graphics.html +++ b/lang/english/html/system_config_graphics.html @@ -66,6 +66,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/nxemu-os/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/nxemu-os/core/hle/service/nvdrv/devices/nvhost_gpu.h index 119fdee1..6d09fa17 100644 --- a/src/nxemu-os/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/nxemu-os/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -46,10 +46,17 @@ static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect struct CommandListHeader { union { u64 raw; - BitField<0, 40, GPUVAddr> addr; + BitField<0, 1, u64> conditional_fetch; + BitField<40, 1, u64> is_privileged; BitField<41, 1, u64> is_non_main; BitField<42, 21, u64> size; + BitField<63, 1, u64> sync; }; + + [[nodiscard]] GPUVAddr Address() const { + // GP_ENTRY0 bits 0-1 are not part of the byte address. + return static_cast(raw & 0xFFFFFFFFFCULL); + } }; static_assert(sizeof(CommandListHeader) == sizeof(u64), "CommandListHeader is incorrect size"); diff --git a/src/nxemu-video/video_settings.cpp b/src/nxemu-video/video_settings.cpp index d72c8a69..7427017e 100644 --- a/src/nxemu-video/video_settings.cpp +++ b/src/nxemu-video/video_settings.cpp @@ -2,6 +2,7 @@ #include "video_enum_strings.h" #include "video_settings_identifiers.h" #include +#include #include #include #include @@ -11,6 +12,13 @@ extern IModuleSettings * g_settings; VideoSettings videoSettings{}; +namespace +{ +// Configuration storage stays on the settings thread; the GPU reads these snapshots. +std::atomic runtime_dma_accuracy{0}; +std::atomic runtime_sync_memory_operations{true}; +} + namespace Settings { @@ -30,6 +38,27 @@ bool IsGPULevelHigh() videoSettings.current_gpu_accuracy == GpuAccuracy::High; } +bool IsDMALevelDefault() +{ + return runtime_dma_accuracy.load(std::memory_order_relaxed) == 0; +} + +bool IsDMALevelSafe() +{ + return runtime_dma_accuracy.load(std::memory_order_relaxed) == 2; +} + +bool UseSafeDMAReads() +{ + const int32_t accuracy = runtime_dma_accuracy.load(std::memory_order_relaxed); + return accuracy == 0 ? IsGPULevelHigh() : accuracy == 2; +} + +bool IsSyncMemoryOperationsEnabled() +{ + return runtime_sync_memory_operations.load(std::memory_order_relaxed); +} + void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo & info) { info.downscale = false; @@ -213,6 +242,8 @@ static VideoSetting settings[] = { {NXVideoSetting::AccuracyLevel, "video", "accuracy_level", &videoSettings.gpu_accuracy, GpuAccuracy::High}, {NXVideoSetting::AnisotropicFiltering, "video", "anisotropic_filtering", &videoSettings.max_anisotropy, AnisotropyMode::Automatic}, #endif + {NXVideoSetting::DMAAccuracy, "video", "dma_accuracy", &videoSettings.dma_accuracy, 0, 0, 2}, + {NXVideoSetting::SyncMemoryOperations, "video", "sync_memory_operations", &videoSettings.sync_memory_operations, true}, {NXVideoSetting::VSyncMode, "video", "vsync_mode", &videoSettings.vsync_mode, VSyncMode::Fifo}, {NXVideoSetting::NvdecEmulation, "video", "nvdec_emulation", &videoSettings.nvdec_emulation, NvdecEmulation::Gpu}, #ifdef _WIN32 @@ -309,6 +340,16 @@ void VideoSettingChanged(const char * setting, void * /*userData*/) default: UNIMPLEMENTED(); } + if (strcmp(setting, NXVideoSetting::DMAAccuracy) == 0) + { + runtime_dma_accuracy.store(videoSettings.dma_accuracy, std::memory_order_relaxed); + return; + } + if (strcmp(setting, NXVideoSetting::SyncMemoryOperations) == 0) + { + runtime_sync_memory_operations.store(videoSettings.sync_memory_operations, std::memory_order_relaxed); + return; + } } Settings::UpdateRescalingInfo(); Settings::UpdateGPUAccuracy(); @@ -576,6 +617,8 @@ void SetupVideoSetting(void) } g_settings->RegisterCallback(videoSetting.identifier, VideoSettingChanged, nullptr); } + runtime_dma_accuracy.store(videoSettings.dma_accuracy, std::memory_order_relaxed); + runtime_sync_memory_operations.store(videoSettings.sync_memory_operations, std::memory_order_relaxed); Settings::UpdateRescalingInfo(); Settings::UpdateGPUAccuracy(); } diff --git a/src/nxemu-video/video_settings.h b/src/nxemu-video/video_settings.h index 8d4dcabb..7d5ccc81 100644 --- a/src/nxemu-video/video_settings.h +++ b/src/nxemu-video/video_settings.h @@ -38,6 +38,10 @@ struct ResolutionScalingInfo void UpdateGPUAccuracy(); bool IsGPULevelExtreme(); bool IsGPULevelHigh(); +bool IsDMALevelDefault(); +bool IsDMALevelSafe(); +bool UseSafeDMAReads(); +bool IsSyncMemoryOperationsEnabled(); void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo & info); void UpdateRescalingInfo(); @@ -72,9 +76,11 @@ struct VideoSettings bool renderer_force_max_clock; AstcDecodeMode accelerate_astc; GpuAccuracy gpu_accuracy; + int32_t dma_accuracy; AnisotropyMode max_anisotropy; bool async_presentation; bool use_reactive_flushing; + bool sync_memory_operations; bool use_asynchronous_shaders; bool use_fast_gpu_time; diff --git a/src/nxemu-video/video_settings_identifiers.h b/src/nxemu-video/video_settings_identifiers.h index 6a11e621..32668e27 100644 --- a/src/nxemu-video/video_settings_identifiers.h +++ b/src/nxemu-video/video_settings_identifiers.h @@ -19,12 +19,14 @@ namespace NXVideoSetting constexpr const char * EnableAsynchronousPresentation = "nxvideo:EnableAsynchronousPresentation"; constexpr const char * ForceMaximumClocks = "nxvideo:ForceMaximumClocks"; constexpr const char * EnableReactiveFlushing = "nxvideo:EnableReactiveFlushing"; + constexpr const char * SyncMemoryOperations = "nxvideo:SyncMemoryOperations"; constexpr const char * UseAsynchronousShaderBuilding = "nxvideo:UseAsynchronousShaderBuilding"; constexpr const char * FastGPUTime = "nxvideo:FastGPUTime"; constexpr const char * UseVulkanPipelineCache = "nxvideo:UseVulkanPipelineCache"; constexpr const char * SyncToFramerateOfVideoPlayback = "nxvideo:SyncToFramerateOfVideoPlayback"; constexpr const char * BarrierFeedbackLoops = "nxvideo:BarrierFeedbackLoops"; constexpr const char * AccuracyLevel = "nxvideo:AccuracyLevel"; + constexpr const char * DMAAccuracy = "nxvideo:DMAAccuracy"; constexpr const char * AnisotropicFiltering = "nxvideo:AnisotropicFiltering"; constexpr const char * ASTCRecompressionMethod = "nxvideo:ASTCRecompressionMethod"; constexpr const char * VRAMUsageMode = "nxvideo:VRAMUsageMode"; diff --git a/src/nxemu/user_interface/sciter_main_window.cpp b/src/nxemu/user_interface/sciter_main_window.cpp index ee0f2d4b..1e809fc4 100644 --- a/src/nxemu/user_interface/sciter_main_window.cpp +++ b/src/nxemu/user_interface/sciter_main_window.cpp @@ -287,6 +287,7 @@ SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitl settings.RegisterCallback(NXOsSetting::DockedMode, SciterMainWindow::SettingChanged, this); settings.RegisterCallback(NXUISetting::Hotkeys, SciterMainWindow::HotKeysChanged, this); settings.RegisterCallback(NXVideoSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this); + settings.RegisterCallback(NXVideoSetting::SyncMemoryOperations, SciterMainWindow::SettingChanged, this); settings.RegisterCallback(NXUISetting::HideMouseOnInactivity, SciterMainWindow::SettingChanged, this); settings.RegisterCallback(NXUISetting::EnableDiscordPresence, SciterMainWindow::SettingChanged, this); @@ -383,6 +384,7 @@ SciterMainWindow::~SciterMainWindow() settings.UnregisterCallback(NXOsSetting::DockedMode, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXUISetting::Hotkeys, SciterMainWindow::HotKeysChanged, this); settings.UnregisterCallback(NXVideoSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this); + settings.UnregisterCallback(NXVideoSetting::SyncMemoryOperations, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXUISetting::HideMouseOnInactivity, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXUISetting::EnableDiscordPresence, SciterMainWindow::SettingChanged, this); @@ -654,6 +656,7 @@ bool SciterMainWindow::Show() m_sciterUI.AttachHandler(m_rootElement.GetElementByID("dockedMode"), IID_ICLICKSINK, (IClickSink *)this); m_sciterUI.AttachHandler(m_rootElement.GetElementByID("renderer"), IID_ICLICKSINK, (IClickSink *)this); + m_sciterUI.AttachHandler(m_rootElement.GetElementByID("syncMemoryOperations"), IID_ICLICKSINK, (IClickSink *)this); m_sciterUI.AttachHandler(m_rootElement.GetElementByID("volume"), IID_ICLICKSINK, (IClickSink *)this); m_sciterUI.AttachHandler(m_rootElement.GetElementByID("volumePopupBtn"), IID_ICLICKSINK, (IClickSink *)this); m_sciterUI.AttachHandler(m_rootElement.GetElementByID("audioVolume"), IID_ISTATECHANGESINK, (IStateChangeSink *)this); @@ -844,6 +847,14 @@ void SciterMainWindow::UpdateStatusWidgets() renderer.SetHTML((const uint8_t *)text.c_str(), text.size()); } + SciterElement syncMemoryOperations(m_rootElement.GetElementByID("syncMemoryOperations")); + if (syncMemoryOperations.IsValid()) + { + const bool enabled = settings.GetBool(NXVideoSetting::SyncMemoryOperations); + stdstr_f text("SMO: %s", enabled ? "ON" : "OFF"); + syncMemoryOperations.SetHTML((const uint8_t *)text.c_str(), text.size()); + } + SciterElement volume(m_rootElement.GetElementByID("volume")); if (volume.IsValid()) { @@ -2389,6 +2400,13 @@ bool SciterMainWindow::OnClick(SCITER_ELEMENT element, SCITER_ELEMENT source, ui m_modules.FlushSettings(); } } + else if (source == rootElement.GetElementByID("syncMemoryOperations")) + { + SettingsStore & settings = SettingsStore::GetInstance(); + const bool enabled = settings.GetBool(NXVideoSetting::SyncMemoryOperations); + settings.SetBool(NXVideoSetting::SyncMemoryOperations, !enabled); + UpdateStatusWidgets(); + } else if (source == rootElement.GetElementByID("volume")) { SettingsStore & settings = SettingsStore::GetInstance(); @@ -2446,6 +2464,10 @@ void SciterMainWindow::SettingChanged(const char * setting, void * userData) { impl->m_resolutionUpFactor = SettingsStore::GetInstance().GetFloat(NXVideoSetting::ResolutionUpFactor); } + else if (strcmp(setting, NXVideoSetting::SyncMemoryOperations) == 0) + { + impl->UpdateStatusWidgets(); + } else if (strcmp(setting, NXOsSetting::UseMultiCore) == 0) { impl->m_useMultiCore = SettingsStore::GetInstance().GetBool(NXOsSetting::UseMultiCore); diff --git a/src/nxemu/user_interface/settings/system_config.cpp b/src/nxemu/user_interface/settings/system_config.cpp index a035f6cc..98df6006 100644 --- a/src/nxemu/user_interface/settings/system_config.cpp +++ b/src/nxemu/user_interface/settings/system_config.cpp @@ -474,6 +474,11 @@ void SystemConfig::InitializeTranslations() {(uint32_t)GpuAccuracy::High, "High"}, {(uint32_t)GpuAccuracy::Extreme, "Extreme"}, }}); + m_settingTranslations.insert({ TranslationType::DmaAccuracy, { + {0, "Default"}, + {1, "Unsafe (fast)"}, + {2, "Safe (stable)"}, + }}); m_settingTranslations.insert({ TranslationType::AnisotropyMode, { {(uint32_t)AnisotropyMode::Automatic, "Automatic"}, {(uint32_t)AnisotropyMode::Default, "Default"}, diff --git a/src/nxemu/user_interface/settings/system_config.h b/src/nxemu/user_interface/settings/system_config.h index feccba43..fa403247 100644 --- a/src/nxemu/user_interface/settings/system_config.h +++ b/src/nxemu/user_interface/settings/system_config.h @@ -38,6 +38,7 @@ public: ScalingFilter, AntiAliasing, GpuAccuracy, + DmaAccuracy, AnisotropyMode, AstcRecompression, VramUsageMode, diff --git a/src/nxemu/user_interface/settings/system_config_graphics.cpp b/src/nxemu/user_interface/settings/system_config_graphics.cpp index 5497022a..96f91536 100644 --- a/src/nxemu/user_interface/settings/system_config_graphics.cpp +++ b/src/nxemu/user_interface/settings/system_config_graphics.cpp @@ -27,6 +27,8 @@ namespace static ConfigSetting advancedSettings[] = { ConfigSetting(ConfigSetting::ComboBox, "AccuracyLevel", true, SystemConfig::TranslationType::GpuAccuracy, NXVideoSetting::AccuracyLevel), + ConfigSetting(ConfigSetting::ComboBox, "DMAAccuracy", true, SystemConfig::TranslationType::DmaAccuracy, NXVideoSetting::DMAAccuracy), + ConfigSetting(ConfigSetting::CheckBox, "SyncMemoryOperations", true, NXVideoSetting::SyncMemoryOperations), ConfigSetting(ConfigSetting::ComboBox, "AnisotropicFiltering", false, SystemConfig::TranslationType::AnisotropyMode, NXVideoSetting::AnisotropicFiltering), ConfigSetting(ConfigSetting::ComboBox, "ASTCRecompressionMethod", false, SystemConfig::TranslationType::AstcRecompression, NXVideoSetting::ASTCRecompressionMethod), ConfigSetting(ConfigSetting::ComboBox, "VRAMUsageMode", false, SystemConfig::TranslationType::VramUsageMode, NXVideoSetting::VRAMUsageMode), diff --git a/src/yuzu_video_core/dma_pusher.cpp b/src/yuzu_video_core/dma_pusher.cpp index a4728ae5..f9161517 100644 --- a/src/yuzu_video_core/dma_pusher.cpp +++ b/src/yuzu_video_core/dma_pusher.cpp @@ -2,19 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "yuzu_video_core/dma_pusher.h" + #include "video_settings.h" #include "yuzu_common/cityhash.h" #include "yuzu_common/settings.h" +#include "yuzu_common/yuzu_assert.h" #include "yuzu_video_core/engines/maxwell_3d.h" #include "yuzu_video_core/gpu.h" #include "yuzu_video_core/guest_memory.h" #include "yuzu_video_core/memory_manager.h" +#include "yuzu_video_core/rasterizer_interface.h" namespace Tegra { constexpr u32 MacroRegistersStart = 0xE00; -constexpr u32 ComputeInline = 0x6D; +[[maybe_unused]] constexpr u32 ComputeInline = 0x6D; DmaPusher::DmaPusher(GPU & gpu_, MemoryManager & memory_manager_, Control::ChannelState & channel_state_) : gpu{gpu_}, @@ -46,81 +49,76 @@ bool DmaPusher::Step() { if (!ib_enable || dma_pushbuffer.empty()) { - // pushbuffer empty and IB empty or nonexistent - nothing to do return false; } - CommandList & command_list{dma_pushbuffer.front()}; + CommandList & command_list = dma_pushbuffer.front(); - ASSERT_OR_EXECUTE( - command_list.command_lists.size() || command_list.prefetch_command_list.size(), { - // Somehow the command_list is empty, in order to avoid a crash - // We ignore it and assume its size is 0. - dma_pushbuffer.pop(); - dma_pushbuffer_subindex = 0; - return true; - }); + const size_t prefetch_size = command_list.prefetch_command_list.size(); + const size_t command_list_size = command_list.command_lists.size(); - if (command_list.prefetch_command_list.size()) + if (prefetch_size == 0 && command_list_size == 0) + { + dma_pushbuffer.pop(); + dma_pushbuffer_subindex = 0; + return true; + } + + if (prefetch_size > 0) { - // Prefetched command list from nvdrv, used for things like synchronization ProcessCommands(command_list.prefetch_command_list); dma_pushbuffer.pop(); + return true; } - else + + const CommandListHeader & header = command_list.command_lists[dma_pushbuffer_subindex]; + dma_state.dma_get = header.Address(); + + // SYNC_WAIT applies to the current GP entry, including zero-length entries. + if (header.sync && Settings::IsSyncMemoryOperationsEnabled()) { - const CommandListHeader command_list_header{command_list.command_lists[dma_pushbuffer_subindex++]}; - dma_state.dma_get = command_list_header.addr; - - if (dma_pushbuffer_subindex >= command_list.command_lists.size()) - { - // We've gone through the current list, remove it from the queue - dma_pushbuffer.pop(); - dma_pushbuffer_subindex = 0; - } - - if (command_list_header.size == 0) - { - return true; - } - - // Push buffer non-empty, read a word - if (Settings::IsGPULevelHigh() && dma_state.method >= MacroRegistersStart) - { - if (subchannels[dma_state.subchannel]) - { - subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, command_list_header.size * sizeof(u32)); - } - } - const auto safe_process = [&] { - Tegra::Memory::GpuGuestMemory headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); - ProcessCommands(headers); - }; - const auto unsafe_process = [&] { - Tegra::Memory::GpuGuestMemory headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); - ProcessCommands(headers); - }; - if (Settings::IsGPULevelHigh()) - { - if (dma_state.method >= MacroRegistersStart) - { - unsafe_process(); - return true; - } - if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute && - dma_state.method == ComputeInline) - { - unsafe_process(); - return true; - } - safe_process(); - return true; - } - unsafe_process(); + SynchronizeMemoryOperations(); } + + if (header.size > 0 && Settings::IsGPULevelHigh() && + dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) + { + subchannels[dma_state.subchannel]->current_dirty = + memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32)); + } + + if (header.size > 0) + { + const bool use_safe = Settings::UseSafeDMAReads(); + if (use_safe) + { + Tegra::Memory::GpuGuestMemory headers( + memory_manager, dma_state.dma_get, header.size, &command_headers); + ProcessCommands(headers); + } + else + { + Tegra::Memory::GpuGuestMemory headers( + memory_manager, dma_state.dma_get, header.size, &command_headers); + ProcessCommands(headers); + } + } + + if (++dma_pushbuffer_subindex >= command_list_size) + { + dma_pushbuffer.pop(); + dma_pushbuffer_subindex = 0; + } + return true; } +void DmaPusher::SynchronizeMemoryOperations() +{ + ASSERT(rasterizer != nullptr); + rasterizer->WaitForFence(); +} + void DmaPusher::ProcessCommands(std::span commands) { for (std::size_t index = 0; index < commands.size();) @@ -273,9 +271,10 @@ void DmaPusher::CallMultiMethod(const u32 * base_start, u32 num_methods) const } } -void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface * rasterizer) +void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface * rasterizer_) { - puller.BindRasterizer(rasterizer); + rasterizer = rasterizer_; + puller.BindRasterizer(rasterizer_); } } // namespace Tegra diff --git a/src/yuzu_video_core/dma_pusher.h b/src/yuzu_video_core/dma_pusher.h index 003ebb76..ade271de 100644 --- a/src/yuzu_video_core/dma_pusher.h +++ b/src/yuzu_video_core/dma_pusher.h @@ -73,10 +73,17 @@ enum class BufferMethods : u32 { struct CommandListHeader { union { u64 raw; - BitField<0, 40, GPUVAddr> addr; + BitField<0, 1, u64> conditional_fetch; + BitField<40, 1, u64> is_privileged; BitField<41, 1, u64> is_non_main; BitField<42, 21, u64> size; + BitField<63, 1, u64> sync; }; + + [[nodiscard]] GPUVAddr Address() const { + // GP_ENTRY0 bits 0-1 are not part of the byte address. + return static_cast(raw & 0xFFFFFFFFFCULL); + } }; static_assert(sizeof(CommandListHeader) == sizeof(u64), "CommandListHeader is incorrect size"); @@ -141,6 +148,7 @@ private: static constexpr u32 non_puller_methods = 0x40; static constexpr u32 max_subchannels = 8; bool Step(); + void SynchronizeMemoryOperations(); void ProcessCommands(std::span commands); void SetState(const CommandHeader& command_header); @@ -176,6 +184,8 @@ private: GPU& gpu; MemoryManager& memory_manager; mutable Engines::Puller puller; + + VideoCore::RasterizerInterface* rasterizer{}; }; } // namespace Tegra diff --git a/src/yuzu_video_core/fence_manager.h b/src/yuzu_video_core/fence_manager.h index 74a6e452..055cf9dc 100644 --- a/src/yuzu_video_core/fence_manager.h +++ b/src/yuzu_video_core/fence_manager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -71,34 +72,8 @@ public: uncommitted_operations.emplace_back(std::move(func)); } - void SignalFence(std::function&& func) { - bool delay_fence = Settings::IsGPULevelHigh(); - if constexpr (!can_async_check) { - TryReleasePendingFences(); - } - const bool should_flush = ShouldFlush(); - CommitAsyncFlushes(); - TFence new_fence = CreateFence(!should_flush); - if constexpr (can_async_check) { - guard.lock(); - } - if (delay_fence) { - uncommitted_operations.emplace_back(std::move(func)); - } - pending_operations.emplace_back(std::move(uncommitted_operations)); - QueueFence(new_fence); - if (!delay_fence) { - func(); - } - fences.push(std::move(new_fence)); - if (should_flush) { - rasterizer.FlushCommands(); - } - if constexpr (can_async_check) { - guard.unlock(); - cv.notify_all(); - } - rasterizer.InvalidateGPUCache(); + void SignalFence(std::function&& func, bool force_delay = false) { + (void)SubmitFence(std::move(func), force_delay, false, nullptr); } void SignalSyncPoint(u32 value) { @@ -107,25 +82,28 @@ public: SignalFence(std::move(func)); } + // Explicit synchronization remains blocking at every GPU accuracy level. + void WaitForFence() { + // Fence submission has one GPU-thread producer; keep its waiter alive through notify_one. + wait_finished.store(false, std::memory_order_relaxed); + + const bool completed_inline = SubmitFence({}, true, true, &wait_finished); + if (completed_inline) { + return; + } + + if constexpr (!can_async_check) { + TryReleasePendingFences(); + } + + wait_finished.wait(false, std::memory_order_acquire); + } + void WaitPendingFences([[maybe_unused]] bool force) { if constexpr (!can_async_check) { TryReleasePendingFences(); - } else { - if (!force) { - return; - } - std::mutex wait_mutex; - std::condition_variable wait_cv; - std::atomic wait_finished{}; - std::function func([&] { - std::scoped_lock lk(wait_mutex); - wait_finished.store(true, std::memory_order_relaxed); - wait_cv.notify_all(); - }); - SignalFence(std::move(func)); - std::unique_lock lk(wait_mutex); - wait_cv.wait( - lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); }); + } else if (force) { + WaitForFence(); } } @@ -167,6 +145,89 @@ protected: TQueryCache& query_cache; private: + static void CompleteWaiter(std::atomic* waiter) { + if (waiter == nullptr) { + return; + } + waiter->store(true, std::memory_order_release); + if constexpr (can_async_check) { + waiter->notify_one(); + } + } + + bool SubmitFence(std::function&& func, bool force_delay, + bool allow_stub_fast_path, std::atomic* waiter) { + const bool delay_fence = force_delay || Settings::IsGPULevelHigh(); + if constexpr (!can_async_check) { + TryReleasePendingFences(); + } + + const bool should_flush = ShouldFlush(); + CommitAsyncFlushes(); + + if constexpr (can_async_check) { + guard.lock(); + } + + const bool can_complete_stub_inline = + allow_stub_fast_path && delay_fence && !should_flush && fences.empty() && + pending_operations.empty() && !fence_in_flight; + + if (can_complete_stub_inline) { + auto operations = std::move(uncommitted_operations); + if (func) { + operations.emplace_back(std::move(func)); + } + + if constexpr (can_async_check) { + // Keep later submissions behind the inline retirement. + fence_in_flight = true; + guard.unlock(); + } + + // Retire the logical flush slot inline when no older fence exists. + PopAsyncFlushes(); + for (auto& operation : operations) { + operation(); + } + rasterizer.InvalidateGPUCache(); + + if constexpr (can_async_check) { + bool has_pending_fences; + { + std::scoped_lock lock(guard); + fence_in_flight = false; + has_pending_fences = !fences.empty(); + } + if (has_pending_fences) { + cv.notify_one(); + } + } + return true; + } + + TFence new_fence = CreateFence(!should_flush); + if (delay_fence && func) { + uncommitted_operations.emplace_back(std::move(func)); + } + pending_operations.emplace_back(std::move(uncommitted_operations)); + pending_waiters.emplace_back(waiter); + QueueFence(new_fence); + if (!delay_fence && func) { + func(); + } + fences.push(std::move(new_fence)); + if (should_flush) { + rasterizer.FlushCommands(); + } + if constexpr (can_async_check) { + guard.unlock(); + cv.notify_one(); + } + rasterizer.InvalidateGPUCache(); + return false; + } + template void TryReleasePendingFences() { while (!fences.empty()) { @@ -181,6 +242,8 @@ private: PopAsyncFlushes(); auto operations = std::move(pending_operations.front()); pending_operations.pop_front(); + auto* waiter = pending_waiters.front(); + pending_waiters.pop_front(); for (auto& operation : operations) { operation(); } @@ -189,6 +252,7 @@ private: delayed_destruction_ring.Push(std::move(current_fence)); } fences.pop(); + CompleteWaiter(waiter); } } @@ -199,17 +263,23 @@ private: TFence current_fence; std::deque> current_operations; + std::atomic* current_waiter{}; while (!stop_token.stop_requested()) { { std::unique_lock lock(guard); - cv.wait(lock, [&] { return stop_token.stop_requested() || !fences.empty(); }); + cv.wait(lock, [&] { + return stop_token.stop_requested() || (!fence_in_flight && !fences.empty()); + }); if (stop_token.stop_requested()) [[unlikely]] { return; } current_fence = std::move(fences.front()); current_operations = std::move(pending_operations.front()); + current_waiter = pending_waiters.front(); fences.pop(); pending_operations.pop_front(); + pending_waiters.pop_front(); + fence_in_flight = true; } if (!current_fence->IsStubbed()) { WaitFence(current_fence); @@ -222,6 +292,13 @@ private: std::unique_lock lock(ring_guard); delayed_destruction_ring.Push(std::move(current_fence)); } + { + std::scoped_lock lock(guard); + fence_in_flight = false; + } + // Complete after retirement to avoid SMO-to-SMO stub convoys. + CompleteWaiter(current_waiter); + current_waiter = nullptr; } } @@ -258,6 +335,9 @@ private: std::queue fences; std::deque> uncommitted_operations; std::deque>> pending_operations; + std::deque*> pending_waiters; + bool fence_in_flight{}; + std::atomic wait_finished{}; std::mutex guard; std::mutex ring_guard; diff --git a/src/yuzu_video_core/rasterizer_interface.h b/src/yuzu_video_core/rasterizer_interface.h index 903e1786..d5448d2d 100644 --- a/src/yuzu_video_core/rasterizer_interface.h +++ b/src/yuzu_video_core/rasterizer_interface.h @@ -68,6 +68,9 @@ public: /// Signal a GPU based semaphore as a fence virtual void SignalFence(std::function&& func) = 0; + /// Signal a fence and wait for its deferred synchronization operations to complete. + virtual void WaitForFence() = 0; + /// Send an operation to be done after a certain amount of flushes. virtual void SyncOperation(std::function&& func) = 0; diff --git a/src/yuzu_video_core/renderer_null/null_rasterizer.cpp b/src/yuzu_video_core/renderer_null/null_rasterizer.cpp index 32729991..6ac639ba 100644 --- a/src/yuzu_video_core/renderer_null/null_rasterizer.cpp +++ b/src/yuzu_video_core/renderer_null/null_rasterizer.cpp @@ -66,6 +66,7 @@ void RasterizerNull::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {} void RasterizerNull::SignalFence(std::function&& func) { func(); } +void RasterizerNull::WaitForFence() {} void RasterizerNull::SyncOperation(std::function&& func) { func(); } diff --git a/src/yuzu_video_core/renderer_null/null_rasterizer.h b/src/yuzu_video_core/renderer_null/null_rasterizer.h index 4d227167..67c250b5 100644 --- a/src/yuzu_video_core/renderer_null/null_rasterizer.h +++ b/src/yuzu_video_core/renderer_null/null_rasterizer.h @@ -60,6 +60,7 @@ public: void UnmapMemory(DAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void SignalFence(std::function&& func) override; + void WaitForFence() override; void SyncOperation(std::function&& func) override; void SignalSyncPoint(u32 value) override; void SignalReference() override; diff --git a/src/yuzu_video_core/renderer_opengl/gl_rasterizer.cpp b/src/yuzu_video_core/renderer_opengl/gl_rasterizer.cpp index 702af221..5740df9a 100644 --- a/src/yuzu_video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/yuzu_video_core/renderer_opengl/gl_rasterizer.cpp @@ -615,6 +615,10 @@ void RasterizerOpenGL::SignalFence(std::function&& func) { fence_manager.SignalFence(std::move(func)); } +void RasterizerOpenGL::WaitForFence() { + fence_manager.WaitForFence(); +} + void RasterizerOpenGL::SyncOperation(std::function&& func) { fence_manager.SyncOperation(std::move(func)); } diff --git a/src/yuzu_video_core/renderer_opengl/gl_rasterizer.h b/src/yuzu_video_core/renderer_opengl/gl_rasterizer.h index c4ecb0ad..0865c100 100644 --- a/src/yuzu_video_core/renderer_opengl/gl_rasterizer.h +++ b/src/yuzu_video_core/renderer_opengl/gl_rasterizer.h @@ -105,6 +105,7 @@ public: void UnmapMemory(DAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void SignalFence(std::function&& func) override; + void WaitForFence() override; void SyncOperation(std::function&& func) override; void SignalSyncPoint(u32 value) override; void SignalReference() override; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp index bef0fa78..d46a7066 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp @@ -685,6 +685,10 @@ void RasterizerVulkan::SignalFence(std::function&& func) { fence_manager.SignalFence(std::move(func)); } +void RasterizerVulkan::WaitForFence() { + fence_manager.WaitForFence(); +} + void RasterizerVulkan::SyncOperation(std::function&& func) { fence_manager.SyncOperation(std::move(func)); } diff --git a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h index acf5586a..79fffe06 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h @@ -107,6 +107,7 @@ public: void UnmapMemory(DAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void SignalFence(std::function&& func) override; + void WaitForFence() override; void SyncOperation(std::function&& func) override; void SignalSyncPoint(u32 value) override; void SignalReference() override;