diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index eca1d00fbe..88c9fb9ab0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -23,6 +23,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"),
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
+ RENDERER_EARLY_RELEASE_FENCES("early_release_fences"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 02289edeae..61248b35d8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -679,6 +679,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description
)
)
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_EARLY_RELEASE_FENCES,
+ titleId = R.string.renderer_early_release_fences,
+ descriptionId = R.string.renderer_early_release_fences_description
+ )
+ )
put(
SingleChoiceSetting(
IntSetting.FAST_GPU_TIME,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 61b86c70d0..0024ab8e80 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -285,6 +285,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
+ add(BooleanSetting.RENDERER_EARLY_RELEASE_FENCES.key)
add(SettingsItem.GPU_UNSWIZZLE_COMBINED)
add(HeaderSetting(R.string.extensions))
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b703575cc5..5c3d7a966e 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -510,6 +510,8 @@
Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.
Use asynchronous shaders
Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.
+ Release Fences Early
+ Fixes crashes and freezes in some games, may cause issues with Unreal Engine games.
GPU Unswizzle Settings
Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.
Enable GPU Unswizzle
diff --git a/src/common/settings.h b/src/common/settings.h
index de7387c4f8..7077046e34 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -546,6 +546,16 @@ struct Values {
SwitchableSetting use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
+#ifdef ANDROID
+ SwitchableSetting early_release_fences{linkage,
+ false,
+ "early_release_fences",
+ Category::RendererAdvanced,
+ Specialization::Default,
+ true,
+ true};
+#endif
+
SwitchableSetting gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size",
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index e4c4329e81..dc7711a6cf 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -72,32 +72,49 @@ public:
}
void SignalFence(std::function&& func) {
- if constexpr (!can_async_check) {
- TryReleasePendingFences();
- }
+ const bool delay_fence = Settings::IsGPULevelHigh();
const bool should_flush = ShouldFlush();
- const bool delay_fence = Settings::IsGPULevelHigh() || (Settings::IsGPULevelMedium() && should_flush);
+ #ifdef __ANDROID__
+ const bool early_release_fences = Settings::values.early_release_fences.GetValue();
+ #else
+ constexpr bool early_release_fences = false;
+ #endif
+ constexpr bool async_supported = can_async_check;
CommitAsyncFlushes();
TFence new_fence = CreateFence(!should_flush);
- if constexpr (can_async_check) {
- guard.lock();
+ std::unique_lock lock(guard, std::defer_lock);
+
+ const bool needs_lock = (early_release_fences && delay_fence) ||
+ (!early_release_fences && async_supported);
+ if (needs_lock) {
+ lock.lock();
+ }
+ if (!delay_fence) {
+ if (early_release_fences) {
+ TryReleasePendingFences();
+ } else if constexpr (!async_supported) {
+ TryReleasePendingFences();
+ }
}
if (delay_fence) {
uncommitted_operations.emplace_back(std::move(func));
}
- pending_operations.emplace_back(std::move(uncommitted_operations));
+ if (!uncommitted_operations.empty()) {
+ pending_operations.emplace_back(std::move(uncommitted_operations));
+ uncommitted_operations.clear();
+ }
QueueFence(new_fence);
if (!delay_fence) {
func();
}
fences.push(std::move(new_fence));
+ if (needs_lock) {
+ lock.unlock();
+ cv.notify_all();
+ }
if (should_flush) {
rasterizer.FlushCommands();
}
- if constexpr (can_async_check) {
- guard.unlock();
- cv.notify_all();
- }
rasterizer.InvalidateGPUCache();
}
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index a82e2c73fa..2dce02e060 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -80,9 +80,7 @@ void MasterSemaphore::Wait(u64 tick) {
if (!semaphore) {
// If we don't support timeline semaphores, wait for the value normally
std::unique_lock lk{free_mutex};
- free_cv.wait(lk, [&] {
- return gpu_tick.load(std::memory_order_acquire) >= tick;
- });
+ free_cv.wait(lk, [&] { return gpu_tick.load(std::memory_order_relaxed) >= tick; });
return;
}
@@ -218,30 +216,13 @@ void MasterSemaphore::WaitThread(std::stop_token token) {
wait_queue.pop();
}
-#ifdef ANDROID
- VkResult status;
- do {
- status = fence.GetStatus();
- if (status == VK_NOT_READY) {
- std::this_thread::sleep_for(std::chrono::microseconds(100));
- }
- } while (status == VK_NOT_READY);
-
- if (status == VK_SUCCESS) {
- fence.Reset();
- } else {
- vk::Check(status);
- continue;
- }
-#else
fence.Wait();
fence.Reset();
-#endif
{
std::scoped_lock lock{free_mutex};
free_queue.push_front(std::move(fence));
- gpu_tick.store(host_tick, std::memory_order_release);
+ gpu_tick.store(host_tick);
}
free_cv.notify_all();
}