Experiments with gpu stalls per fence wait

This commit is contained in:
CamilleLaVey 2026-10-01 22:52:42 -04:00
parent 5c7520f572
commit 165a1d1da1
22 changed files with 128 additions and 66 deletions

View file

@ -34,6 +34,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
ENABLE_GPU_BUFFER_READBACK("enable_gpu_buffer_readback"), ENABLE_GPU_BUFFER_READBACK("enable_gpu_buffer_readback"),
USE_UNIFIED_MEMORY("use_unified_memory"), USE_UNIFIED_MEMORY("use_unified_memory"),
SYNC_MEMORY_OPERATIONS("sync_memory_operations"), SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
STALL_ON_GPU_FENCE_WAIT("stall_on_gpu_fence_wait"),
BUFFER_REORDER_DISABLE("disable_buffer_reorder"), BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
RENDERER_DEBUG("debug"), RENDERER_DEBUG("debug"),
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"), RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),

View file

@ -902,6 +902,13 @@ abstract class SettingsItem(
descriptionId = R.string.sync_memory_operations_description descriptionId = R.string.sync_memory_operations_description
) )
) )
put(
SwitchSetting(
BooleanSetting.STALL_ON_GPU_FENCE_WAIT,
titleId = R.string.stall_on_gpu_fence_wait,
descriptionId = R.string.stall_on_gpu_fence_wait_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.BUFFER_REORDER_DISABLE, BooleanSetting.BUFFER_REORDER_DISABLE,

View file

@ -545,6 +545,7 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_NVDEC_EMULATION.key) add(IntSetting.RENDERER_NVDEC_EMULATION.key)
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key) add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.STALL_ON_GPU_FENCE_WAIT.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)

View file

@ -575,6 +575,8 @@
<string name="accelerate_astc_description">Pick how ASTC-compressed textures are decoded for rendering: CPU (slow, safe), GPU (fast, recommended), or CPU Async (no stutters, may cause issues)</string> <string name="accelerate_astc_description">Pick how ASTC-compressed textures are decoded for rendering: CPU (slow, safe), GPU (fast, recommended), or CPU Async (no stutters, may cause issues)</string>
<string name="sync_memory_operations">Sync Memory Operations</string> <string name="sync_memory_operations">Sync Memory Operations</string>
<string name="stall_on_gpu_fence_wait">Pause CPU on GPU fence timeouts</string>
<string name="stall_on_gpu_fence_wait_description">When a game keeps timing out while waiting for the GPU, pauses every CPU thread until the GPU catches up. Disabling it keeps the other threads running, which can reduce stutter but may break games that rely on it.</string>
<string name="sync_memory_operations_description">Ensures data consistency between compute and memory operations. This option should fix issues in some games, but may also reduce performance in some cases. Unreal Engine 4 games often see the most significant changes thereof.</string> <string name="sync_memory_operations_description">Ensures data consistency between compute and memory operations. This option should fix issues in some games, but may also reduce performance in some cases. Unreal Engine 4 games often see the most significant changes thereof.</string>
<string name="use_disk_shader_cache">Disk shader cache</string> <string name="use_disk_shader_cache">Disk shader cache</string>
<string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>

View file

@ -561,6 +561,8 @@ struct Values {
Specialization::Default, Specialization::Default,
true, true,
true}; true};
SwitchableSetting<bool> stall_on_gpu_fence_wait{linkage, true, "stall_on_gpu_fence_wait",
Category::RendererAdvanced};
SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock", SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock",
Category::RendererAdvanced}; Category::RendererAdvanced};

View file

@ -13,6 +13,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_event.h"
#include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/container.h"
@ -147,9 +148,12 @@ NvResult nvhost_ctrl::IocCtrlEventWait(IocCtrlEventWaitParams& params, bool is_a
const auto check_failing = [&]() { const auto check_failing = [&]() {
if (events[slot].fails > 2) { if (events[slot].fails > 2) {
{ std::unique_lock<std::mutex> stall;
auto lk = system.StallApplication(); if (Settings::values.stall_on_gpu_fence_wait.GetValue()) {
host1x_syncpoint_manager.WaitHost(fence_id, target_value); stall = system.StallApplication();
}
host1x_syncpoint_manager.WaitHost(fence_id, target_value);
if (stall.owns_lock()) {
system.UnstallApplication(); system.UnstallApplication();
} }
params.value.raw = target_value; params.value.raw = target_value;

View file

@ -312,19 +312,17 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildWaitComman
static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementCommandList( static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementCommandList(
NvFence fence) { NvFence fence) {
boost::container::small_vector<Tegra::CommandHeader, 512> result{ const Tegra::CommandHeader increment =
BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id);
return {
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1, Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
Tegra::SubmissionMode::Increasing), Tegra::SubmissionMode::Increasing),
{}}; {},
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 2,
for (u32 count = 0; count < 2; ++count) { Tegra::SubmissionMode::NonIncreasing),
result.push_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1, increment,
Tegra::SubmissionMode::Increasing)); increment,
result.push_back( };
BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
}
return result;
} }
static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementWithWfiCommandList( static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementWithWfiCommandList(
@ -370,17 +368,14 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) + u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) +
(flags.increment_value.Value() != 0 ? params.fence.value : 0)}; (flags.increment_value.Value() != 0 ? params.fence.value : 0)};
params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment); params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
gpu.PushGPUEntries(bind_id, std::move(entries));
if (flags.fence_increment.Value()) { if (flags.fence_increment.Value()) {
if (flags.suppress_wfi.Value()) { if (flags.suppress_wfi.Value()) {
gpu.PushGPUEntries(bind_id, entries.prefetch_command_list = BuildIncrementCommandList(params.fence);
Tegra::CommandList{BuildIncrementCommandList(params.fence)});
} else { } else {
gpu.PushGPUEntries(bind_id, entries.prefetch_command_list = BuildIncrementWithWfiCommandList(params.fence);
Tegra::CommandList{BuildIncrementWithWfiCommandList(params.fence)});
} }
} }
gpu.PushGPUEntries(bind_id, std::move(entries));
flags.raw = 0; flags.raw = 0;

View file

@ -165,13 +165,22 @@ Module::Module(Core::System& system)
Module::~Module() {} Module::~Module() {}
std::shared_ptr<Devices::nvdevice> Module::FindDevice(DeviceFD fd) const {
std::scoped_lock lock(open_files_mutex);
const auto itr = open_files.find(fd);
if (itr == open_files.end()) {
return nullptr;
}
return itr->second;
}
NvResult Module::VerifyFD(DeviceFD fd) const { NvResult Module::VerifyFD(DeviceFD fd) const {
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
return NvResult::InvalidState; return NvResult::InvalidState;
} }
if (open_files.find(fd) == open_files.end()) { if (!FindDevice(fd)) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
@ -187,8 +196,11 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_
} }
const DeviceFD fd = next_fd++; const DeviceFD fd = next_fd++;
auto& builder = it->second; std::shared_ptr<Devices::nvdevice> device;
auto device = builder(fd)->second; {
std::scoped_lock lock(open_files_mutex);
device = it->second(fd)->second;
}
device->OnOpen(session_id, fd); device->OnOpen(session_id, fd);
@ -202,14 +214,13 @@ NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
return NvResult::InvalidState; return NvResult::InvalidState;
} }
const auto itr = open_files.find(fd); const auto device = FindDevice(fd);
if (!device) {
if (itr == open_files.end()) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl1(fd, command, input, output); return device->Ioctl1(fd, command, input, output);
} }
NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
@ -219,14 +230,13 @@ NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
return NvResult::InvalidState; return NvResult::InvalidState;
} }
const auto itr = open_files.find(fd); const auto device = FindDevice(fd);
if (!device) {
if (itr == open_files.end()) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl2(fd, command, input, inline_input, output); return device->Ioctl2(fd, command, input, inline_input, output);
} }
NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
@ -236,14 +246,13 @@ NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, s
return NvResult::InvalidState; return NvResult::InvalidState;
} }
const auto itr = open_files.find(fd); const auto device = FindDevice(fd);
if (!device) {
if (itr == open_files.end()) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl3(fd, command, input, output, inline_output); return device->Ioctl3(fd, command, input, output, inline_output);
} }
NvResult Module::Close(DeviceFD fd) { NvResult Module::Close(DeviceFD fd) {
@ -252,16 +261,17 @@ NvResult Module::Close(DeviceFD fd) {
return NvResult::InvalidState; return NvResult::InvalidState;
} }
const auto itr = open_files.find(fd); const auto device = FindDevice(fd);
if (!device) {
if (itr == open_files.end()) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
itr->second->OnClose(fd); {
std::scoped_lock lock(open_files_mutex);
open_files.erase(itr); open_files.erase(fd);
}
device->OnClose(fd);
return NvResult::Success; return NvResult::Success;
} }
@ -272,14 +282,13 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
return NvResult::InvalidState; return NvResult::InvalidState;
} }
const auto itr = open_files.find(fd); const auto device = FindDevice(fd);
if (!device) {
if (itr == open_files.end()) {
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
event = itr->second->QueryEvent(event_id); event = device->QueryEvent(event_id);
if (!event) { if (!event) {
return NvResult::BadParameter; return NvResult::BadParameter;
} }

View file

@ -65,10 +65,7 @@ public:
/// Returns a pointer to one of the available devices, identified by its name. /// Returns a pointer to one of the available devices, identified by its name.
template <typename T> template <typename T>
std::shared_ptr<T> GetDevice(DeviceFD fd) { std::shared_ptr<T> GetDevice(DeviceFD fd) {
auto itr = open_files.find(fd); return std::static_pointer_cast<T>(FindDevice(fd));
if (itr == open_files.end())
return nullptr;
return std::static_pointer_cast<T>(itr->second);
} }
NvResult VerifyFD(DeviceFD fd) const; NvResult VerifyFD(DeviceFD fd) const;
@ -97,6 +94,8 @@ public:
private: private:
friend class EventInterface; friend class EventInterface;
std::shared_ptr<Devices::nvdevice> FindDevice(DeviceFD fd) const;
/// Manages syncpoints on the host /// Manages syncpoints on the host
NvCore::Container container; NvCore::Container container;
@ -106,6 +105,7 @@ private:
using FilesContainerType = ::Common::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>; using FilesContainerType = ::Common::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
/// Mapping of file descriptors to the devices they reference. /// Mapping of file descriptors to the devices they reference.
FilesContainerType open_files; FilesContainerType open_files;
mutable std::mutex open_files_mutex;
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;

View file

@ -222,6 +222,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Ensures data consistency between compute and memory operations.\nThis option fixes " tr("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 " "issues in games, but may degrade performance.\nUnreal Engine 4 games often see the "
"most significant changes thereof.")); "most significant changes thereof."));
INSERT(Settings, stall_on_gpu_fence_wait, tr("Pause CPU on GPU fence timeouts"),
tr("When a game keeps timing out while waiting for the GPU, pauses every CPU thread "
"until the GPU catches up.\nDisabling it keeps the other threads running, which can "
"reduce stutter but may break games that rely on it."));
INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
tr("Slightly improves performance by moving presentation to a separate CPU thread.")); tr("Slightly improves performance by moving presentation to a separate CPU thread."));
INSERT( INSERT(

View file

@ -56,7 +56,7 @@ bool DmaPusher::Step() {
return true; return true;
} }
if (prefetch_size > 0) { if (command_list_size == 0) {
ProcessCommands(command_list.prefetch_command_list); ProcessCommands(command_list.prefetch_command_list);
dma_pushbuffer.pop(); dma_pushbuffer.pop();
return true; return true;
@ -89,6 +89,7 @@ bool DmaPusher::Step() {
} }
if (++dma_pushbuffer_subindex >= command_list_size) { if (++dma_pushbuffer_subindex >= command_list_size) {
ProcessCommands(command_list.prefetch_command_list);
dma_pushbuffer.pop(); dma_pushbuffer.pop();
dma_pushbuffer_subindex = 0; dma_pushbuffer_subindex = 0;
} else { } else {

View file

@ -588,7 +588,7 @@ void Maxwell3D::ProcessCounterReset() {
void Maxwell3D::ProcessSyncPoint() { void Maxwell3D::ProcessSyncPoint() {
const u32 sync_point = regs.sync_info.sync_point.Value(); const u32 sync_point = regs.sync_info.sync_point.Value();
[[maybe_unused]] const u32 cache_flush = regs.sync_info.clean_l2.Value(); [[maybe_unused]] const u32 cache_flush = regs.sync_info.clean_l2.Value();
rasterizer->SignalSyncPoint(sync_point); rasterizer->SignalSyncPoint(sync_point, 1);
} }
void Maxwell3D::ProcessCBBind(size_t stage_index) { void Maxwell3D::ProcessCBBind(size_t stage_index) {

View file

@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/settings.h" #include "common/settings.h"
@ -71,7 +73,7 @@ void Puller::ProcessFenceActionMethod(DmaPusher& dma_pusher) {
dma_pusher.rasterizer->ReleaseFences(); dma_pusher.rasterizer->ReleaseFences();
break; break;
case Puller::FenceOperation::Increment: case Puller::FenceOperation::Increment:
dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id); dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id, 1);
break; break;
default: default:
UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value()); UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
@ -278,6 +280,9 @@ void Puller::CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel,
ASSERT(subchannel < bound_engines.size()); ASSERT(subchannel < bound_engines.size());
if (ExecuteMethodOnEngine(dma_pusher, method)) { if (ExecuteMethodOnEngine(dma_pusher, method)) {
CallEngineMultiMethod(dma_pusher, method, subchannel, base_start, amount, methods_pending); CallEngineMultiMethod(dma_pusher, method, subchannel, base_start, amount, methods_pending);
} else if (IsIncrementRun(method, base_start, amount)) {
regs.reg_array[method] = base_start[0];
dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id, amount);
} else { } else {
for (u32 i = 0; i < amount; i++) { for (u32 i = 0; i < amount; i++) {
CallPullerMethod(dma_pusher, MethodCall{ CallPullerMethod(dma_pusher, MethodCall{
@ -290,6 +295,16 @@ void Puller::CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel,
} }
} }
bool Puller::IsIncrementRun(u32 method, const u32* base_start, u32 amount) const {
if (BufferMethods(method) != BufferMethods::SyncpointOperation || amount < 2) {
return false;
}
const FenceAction action{.raw = base_start[0]};
return action.op == FenceOperation::Increment &&
std::all_of(base_start, base_start + amount,
[first = base_start[0]](u32 argument) { return argument == first; });
}
/// Determines where the method should be executed. /// Determines where the method should be executed.
[[nodiscard]] bool Puller::ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method) { [[nodiscard]] bool Puller::ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method) {
const auto buffer_method = BufferMethods(method); const auto buffer_method = BufferMethods(method);

View file

@ -72,6 +72,7 @@ public:
void CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call); void CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending); void CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending);
[[nodiscard]] bool IsIncrementRun(u32 method, const u32* base_start, u32 amount) const;
void BindRasterizer(DmaPusher& dma_pusher, VideoCore::RasterizerInterface* rasterizer); void BindRasterizer(DmaPusher& dma_pusher, VideoCore::RasterizerInterface* rasterizer);
void CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call); void CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call); void CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call);

View file

@ -101,9 +101,15 @@ public:
rasterizer.InvalidateGPUCache(); rasterizer.InvalidateGPUCache();
} }
void SignalSyncPoint(u32 value) { void SignalSyncPoint(u32 value, u32 count) {
syncpoint_manager.IncrementGuest(value); for (u32 i = 0; i < count; ++i) {
std::function<void()> func([this, value] { syncpoint_manager.IncrementHost(value); }); syncpoint_manager.IncrementGuest(value);
}
std::function<void()> func([this, value, count] {
for (u32 i = 0; i < count; ++i) {
syncpoint_manager.IncrementHost(value);
}
});
SignalFence(std::move(func)); SignalFence(std::move(func));
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -75,7 +78,7 @@ public:
virtual void SyncOperation(std::function<void()>&& func) = 0; virtual void SyncOperation(std::function<void()>&& func) = 0;
/// Signal a GPU based syncpoint as a fence /// Signal a GPU based syncpoint as a fence
virtual void SignalSyncPoint(u32 value) = 0; virtual void SignalSyncPoint(u32 value, u32 count) = 0;
/// Signal a GPU based reference as point /// Signal a GPU based reference as point
virtual void SignalReference() = 0; virtual void SignalReference() = 0;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -69,10 +72,12 @@ void RasterizerNull::SignalFence(std::function<void()>&& func) {
void RasterizerNull::SyncOperation(std::function<void()>&& func) { void RasterizerNull::SyncOperation(std::function<void()>&& func) {
func(); func();
} }
void RasterizerNull::SignalSyncPoint(u32 value) { void RasterizerNull::SignalSyncPoint(u32 value, u32 count) {
auto& syncpoint_manager = m_gpu.Host1x().GetSyncpointManager(); auto& syncpoint_manager = m_gpu.Host1x().GetSyncpointManager();
syncpoint_manager.IncrementGuest(value); for (u32 i = 0; i < count; ++i) {
syncpoint_manager.IncrementHost(value); syncpoint_manager.IncrementGuest(value);
syncpoint_manager.IncrementHost(value);
}
} }
void RasterizerNull::SignalReference() {} void RasterizerNull::SignalReference() {}
void RasterizerNull::ReleaseFences(bool) {} void RasterizerNull::ReleaseFences(bool) {}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -61,7 +64,7 @@ public:
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override; void SignalFence(std::function<void()>&& func) override;
void SyncOperation(std::function<void()>&& func) override; void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override; void SignalSyncPoint(u32 value, u32 count) override;
void SignalReference() override; void SignalReference() override;
void ReleaseFences(bool force) override; void ReleaseFences(bool force) override;
void FlushAndInvalidateRegion( void FlushAndInvalidateRegion(

View file

@ -615,8 +615,8 @@ void RasterizerOpenGL::SyncOperation(std::function<void()>&& func) {
fence_manager.SyncOperation(std::move(func)); fence_manager.SyncOperation(std::move(func));
} }
void RasterizerOpenGL::SignalSyncPoint(u32 value) { void RasterizerOpenGL::SignalSyncPoint(u32 value, u32 count) {
fence_manager.SignalSyncPoint(value); fence_manager.SignalSyncPoint(value, count);
} }
void RasterizerOpenGL::SignalReference() { void RasterizerOpenGL::SignalReference() {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -106,7 +109,7 @@ public:
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override; void SignalFence(std::function<void()>&& func) override;
void SyncOperation(std::function<void()>&& func) override; void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override; void SignalSyncPoint(u32 value, u32 count) override;
void SignalReference() override; void SignalReference() override;
void ReleaseFences(bool force = true) override; void ReleaseFences(bool force = true) override;
void FlushAndInvalidateRegion( void FlushAndInvalidateRegion(

View file

@ -862,8 +862,8 @@ void RasterizerVulkan::SyncOperation(std::function<void()>&& func) {
fence_manager.SyncOperation(std::move(func)); fence_manager.SyncOperation(std::move(func));
} }
void RasterizerVulkan::SignalSyncPoint(u32 value) { void RasterizerVulkan::SignalSyncPoint(u32 value, u32 count) {
fence_manager.SignalSyncPoint(value); fence_manager.SignalSyncPoint(value, count);
} }
void RasterizerVulkan::SignalReference() { void RasterizerVulkan::SignalReference() {

View file

@ -111,7 +111,7 @@ public:
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
void SignalFence(std::function<void()>&& func) override; void SignalFence(std::function<void()>&& func) override;
void SyncOperation(std::function<void()>&& func) override; void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override; void SignalSyncPoint(u32 value, u32 count) override;
void SignalReference() override; void SignalReference() override;
void ReleaseFences(bool force = true) override; void ReleaseFences(bool force = true) override;
void FlushAndInvalidateRegion( void FlushAndInvalidateRegion(