Port Eden SMO/DMA synchronization improvements (#284)

This commit is contained in:
Bruno 2026-09-22 12:44:59 +02:00 committed by GitHub
parent ef113f81bb
commit 0dc0704f48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 314 additions and 111 deletions

View File

@ -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);

View File

@ -43,6 +43,7 @@
<div id="StatusBar" >
<span id="dockedMode" />
<span id="renderer" />
<span id="syncMemoryOperations" tooltip="Allow Sync Memory Operations to be toggled at runtime for performance testing." />
<span id="volumeAnchor">
<span id="volume" />
<span id="volumePopupBtn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M104.704 685.248a64 64 0 0 0 90.496 0l316.8-316.8 316.8 316.8a64 64 0 0 0 90.496-90.496L557.248 232.704a64 64 0 0 0-90.496 0L104.704 594.752a64 64 0 0 0 0 90.496"/></svg></span>

View File

@ -66,6 +66,14 @@
<div class="left"><label for="AccuracyLevel">Accuracy Level:</label></div>
<div class="right"><ComboBox id="AccuracyLevel" tooltip="GPU emulation accuracy.\nMost games render fine with Normal, but High is still required for some.\nParticles tend to only render correctly with High accuracy.\nExtreme should only be used for debugging.\nThis option can be changed while playing.\nSome games may require booting on high to render properly."/></div>
</div>
<div class="form-row">
<div class="left"><label for="DMAAccuracy">DMA Accuracy:</label></div>
<div class="right"><ComboBox id="DMAAccuracy" tooltip="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."/></div>
</div>
<div class="form-row checkbox-row">
<div class="left"><label for="SyncMemoryOperations">Sync Memory Operations</label></div>
<div class="right"><input type="checkbox" id="SyncMemoryOperations" tooltip="Ensures data consistency between compute and memory operations.\nThis option fixes issues in games, but may degrade performance.\nUnreal Engine 4 games often see the most significant changes thereof."></div>
</div>
<div class="form-row">
<div class="left"><label for="AnisotropicFiltering">Anisotropic Filtering:</label></div>
<div class="right"><ComboBox id="AnisotropicFiltering" tooltip="Controls the quality of texture rendering at oblique angles.\nIt’s a light setting and safe to set at 16x on most GPUs."/></div>

View File

@ -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<GPUVAddr>(raw & 0xFFFFFFFFFCULL);
}
};
static_assert(sizeof(CommandListHeader) == sizeof(u64), "CommandListHeader is incorrect size");

View File

@ -2,6 +2,7 @@
#include "video_enum_strings.h"
#include "video_settings_identifiers.h"
#include <algorithm>
#include <atomic>
#include <common/json.h>
#include <cstring>
#include <nxemu-module-spec/base.h>
@ -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<int32_t> runtime_dma_accuracy{0};
std::atomic<bool> 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();
}

View File

@ -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;

View File

@ -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";

View File

@ -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);

View File

@ -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"},

View File

@ -38,6 +38,7 @@ public:
ScalingFilter,
AntiAliasing,
GpuAccuracy,
DmaAccuracy,
AnisotropyMode,
AstcRecompression,
VramUsageMode,

View File

@ -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),

View File

@ -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<Tegra::CommandHeader, GuestMemoryFlags::SafeRead> headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers);
ProcessCommands(headers);
};
const auto unsafe_process = [&] {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, GuestMemoryFlags::UnsafeRead> 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<Tegra::CommandHeader, GuestMemoryFlags::SafeRead> headers(
memory_manager, dma_state.dma_get, header.size, &command_headers);
ProcessCommands(headers);
}
else
{
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, GuestMemoryFlags::UnsafeRead> 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<const CommandHeader> 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

View File

@ -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<GPUVAddr>(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<const CommandHeader> 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

View File

@ -4,6 +4,7 @@
#pragma once
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstring>
#include <deque>
@ -71,34 +72,8 @@ public:
uncommitted_operations.emplace_back(std::move(func));
}
void SignalFence(std::function<void()>&& func) {
bool delay_fence = Settings::IsGPULevelHigh();
if constexpr (!can_async_check) {
TryReleasePendingFences<false>();
}
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<void()>&& 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<true>();
}
wait_finished.wait(false, std::memory_order_acquire);
}
void WaitPendingFences([[maybe_unused]] bool force) {
if constexpr (!can_async_check) {
TryReleasePendingFences<true>();
} else {
if (!force) {
return;
}
std::mutex wait_mutex;
std::condition_variable wait_cv;
std::atomic<bool> wait_finished{};
std::function<void()> 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<bool>* waiter) {
if (waiter == nullptr) {
return;
}
waiter->store(true, std::memory_order_release);
if constexpr (can_async_check) {
waiter->notify_one();
}
}
bool SubmitFence(std::function<void()>&& func, bool force_delay,
bool allow_stub_fast_path, std::atomic<bool>* waiter) {
const bool delay_fence = force_delay || Settings::IsGPULevelHigh();
if constexpr (!can_async_check) {
TryReleasePendingFences<false>();
}
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 <bool force_wait>
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<std::function<void()>> current_operations;
std::atomic<bool>* 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<TFence> fences;
std::deque<std::function<void()>> uncommitted_operations;
std::deque<std::deque<std::function<void()>>> pending_operations;
std::deque<std::atomic<bool>*> pending_waiters;
bool fence_in_flight{};
std::atomic<bool> wait_finished{};
std::mutex guard;
std::mutex ring_guard;

View File

@ -68,6 +68,9 @@ public:
/// Signal a GPU based semaphore as a fence
virtual void SignalFence(std::function<void()>&& 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<void()>&& func) = 0;

View File

@ -66,6 +66,7 @@ void RasterizerNull::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {}
void RasterizerNull::SignalFence(std::function<void()>&& func) {
func();
}
void RasterizerNull::WaitForFence() {}
void RasterizerNull::SyncOperation(std::function<void()>&& func) {
func();
}

View File

@ -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<void()>&& func) override;
void WaitForFence() override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;

View File

@ -615,6 +615,10 @@ void RasterizerOpenGL::SignalFence(std::function<void()>&& func) {
fence_manager.SignalFence(std::move(func));
}
void RasterizerOpenGL::WaitForFence() {
fence_manager.WaitForFence();
}
void RasterizerOpenGL::SyncOperation(std::function<void()>&& func) {
fence_manager.SyncOperation(std::move(func));
}

View File

@ -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<void()>&& func) override;
void WaitForFence() override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;

View File

@ -685,6 +685,10 @@ void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
fence_manager.SignalFence(std::move(func));
}
void RasterizerVulkan::WaitForFence() {
fence_manager.WaitForFence();
}
void RasterizerVulkan::SyncOperation(std::function<void()>&& func) {
fence_manager.SyncOperation(std::move(func));
}

View File

@ -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<void()>&& func) override;
void WaitForFence() override;
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;