mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-25 00:40:32 +00:00
a27d35463e
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 <xbzk@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4182 Reviewed-by: Lizzie <lizzie@eden-emu.dev>
271 lines
8.9 KiB
C++
271 lines
8.9 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <condition_variable>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <queue>
|
|
|
|
#include "common/common_types.h"
|
|
#include "common/settings.h"
|
|
#include "common/thread.h"
|
|
#include "video_core/delayed_destruction_ring.h"
|
|
#include "video_core/gpu.h"
|
|
#include "video_core/host1x/host1x.h"
|
|
#include "video_core/host1x/syncpoint_manager.h"
|
|
#include "video_core/rasterizer_interface.h"
|
|
|
|
namespace VideoCommon {
|
|
|
|
class FenceBase {
|
|
public:
|
|
explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
|
|
|
|
bool IsStubbed() const {
|
|
return is_stubbed;
|
|
}
|
|
|
|
protected:
|
|
bool is_stubbed;
|
|
};
|
|
|
|
template <typename Traits>
|
|
class FenceManager {
|
|
using TFence = typename Traits::FenceType;
|
|
using TTextureCache = typename Traits::TextureCacheType;
|
|
using TBufferCache = typename Traits::BufferCacheType;
|
|
using TQueryCache = typename Traits::QueryCacheType;
|
|
static constexpr bool can_async_check = Traits::HAS_ASYNC_CHECK;
|
|
|
|
public:
|
|
/// Notify the fence manager about a new frame
|
|
void TickFrame() {
|
|
std::unique_lock lock(ring_guard);
|
|
delayed_destruction_ring.Tick();
|
|
}
|
|
|
|
// Unlike other fences, this one doesn't
|
|
void SignalOrdering() {
|
|
if constexpr (!can_async_check) {
|
|
TryReleasePendingFences<false>();
|
|
}
|
|
std::scoped_lock lock{buffer_cache.mutex};
|
|
buffer_cache.AccumulateFlushes();
|
|
}
|
|
|
|
void SignalReference() {
|
|
std::function<void()> do_nothing([] {});
|
|
SignalFence(std::move(do_nothing));
|
|
}
|
|
|
|
void SyncOperation(std::function<void()>&& func) {
|
|
uncommitted_operations.emplace_back(std::move(func));
|
|
}
|
|
|
|
void SignalFence(std::function<void()>&& 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<false>();
|
|
}
|
|
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 SignalSyncPoint(u32 value) {
|
|
syncpoint_manager.IncrementGuest(value);
|
|
std::function<void()> func([this, value] { syncpoint_manager.IncrementHost(value); });
|
|
SignalFence(std::move(func));
|
|
}
|
|
|
|
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); });
|
|
}
|
|
}
|
|
|
|
protected:
|
|
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
|
|
TTextureCache& texture_cache_, TBufferCache& buffer_cache_,
|
|
TQueryCache& query_cache_)
|
|
: rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
|
|
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {
|
|
if constexpr (can_async_check) {
|
|
fence_thread =
|
|
std::jthread([this](std::stop_token token) { ReleaseThreadFunc(token); });
|
|
}
|
|
}
|
|
|
|
virtual ~FenceManager() {
|
|
if constexpr (can_async_check) {
|
|
fence_thread.request_stop();
|
|
cv.notify_all();
|
|
fence_thread.join();
|
|
}
|
|
}
|
|
|
|
/// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
|
|
/// true
|
|
virtual TFence CreateFence(bool is_stubbed) = 0;
|
|
/// Queues a fence into the backend if the fence isn't stubbed.
|
|
virtual void QueueFence(TFence& fence) = 0;
|
|
/// Notifies that the backend fence has been signaled/reached in host GPU.
|
|
virtual bool IsFenceSignaled(TFence& fence) const = 0;
|
|
/// Waits until a fence has been signalled by the host GPU.
|
|
virtual void WaitFence(TFence& fence) = 0;
|
|
|
|
VideoCore::RasterizerInterface& rasterizer;
|
|
Tegra::GPU& gpu;
|
|
Tegra::Host1x::SyncpointManager& syncpoint_manager;
|
|
TTextureCache& texture_cache;
|
|
TBufferCache& buffer_cache;
|
|
TQueryCache& query_cache;
|
|
|
|
private:
|
|
template <bool force_wait>
|
|
void TryReleasePendingFences() {
|
|
while (!fences.empty()) {
|
|
TFence& current_fence = fences.front();
|
|
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
|
|
if constexpr (force_wait) {
|
|
WaitFence(current_fence);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
PopAsyncFlushes();
|
|
auto operations = std::move(pending_operations.front());
|
|
pending_operations.pop_front();
|
|
for (auto& operation : operations) {
|
|
operation();
|
|
}
|
|
{
|
|
std::unique_lock lock(ring_guard);
|
|
delayed_destruction_ring.Push(std::move(current_fence));
|
|
}
|
|
fences.pop();
|
|
}
|
|
}
|
|
|
|
void ReleaseThreadFunc(std::stop_token stop_token) {
|
|
Common::SetCurrentThreadName("GPUFencingThread");
|
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
|
|
|
TFence current_fence;
|
|
std::deque<std::function<void()>> current_operations;
|
|
while (!stop_token.stop_requested()) {
|
|
{
|
|
std::unique_lock lock(guard);
|
|
cv.wait(lock, [&] { return stop_token.stop_requested() || !fences.empty(); });
|
|
if (stop_token.stop_requested()) [[unlikely]] {
|
|
return;
|
|
}
|
|
current_fence = std::move(fences.front());
|
|
current_operations = std::move(pending_operations.front());
|
|
fences.pop();
|
|
pending_operations.pop_front();
|
|
}
|
|
if (!current_fence->IsStubbed()) {
|
|
WaitFence(current_fence);
|
|
}
|
|
PopAsyncFlushes();
|
|
for (auto& operation : current_operations) {
|
|
operation();
|
|
}
|
|
{
|
|
std::unique_lock lock(ring_guard);
|
|
delayed_destruction_ring.Push(std::move(current_fence));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShouldWait() const {
|
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
|
return texture_cache.ShouldWaitAsyncFlushes() || buffer_cache.ShouldWaitAsyncFlushes() ||
|
|
query_cache.ShouldWaitAsyncFlushes();
|
|
}
|
|
|
|
bool ShouldFlush() const {
|
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
|
return texture_cache.HasUncommittedFlushes() || buffer_cache.HasUncommittedFlushes() ||
|
|
query_cache.HasUncommittedFlushes();
|
|
}
|
|
|
|
void PopAsyncFlushes() {
|
|
{
|
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
|
texture_cache.PopAsyncFlushes();
|
|
buffer_cache.PopAsyncFlushes();
|
|
}
|
|
query_cache.PopAsyncFlushes();
|
|
}
|
|
|
|
void CommitAsyncFlushes() {
|
|
{
|
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
|
texture_cache.CommitAsyncFlushes();
|
|
buffer_cache.CommitAsyncFlushes();
|
|
}
|
|
query_cache.CommitAsyncFlushes();
|
|
}
|
|
|
|
std::queue<TFence> fences;
|
|
std::deque<std::function<void()>> uncommitted_operations;
|
|
std::deque<std::deque<std::function<void()>>> pending_operations;
|
|
|
|
std::mutex guard;
|
|
std::mutex ring_guard;
|
|
std::condition_variable cv;
|
|
|
|
std::jthread fence_thread;
|
|
|
|
DelayedDestructionRing<TFence, 8> delayed_destruction_ring;
|
|
};
|
|
|
|
} // namespace VideoCommon
|