[core, gpu, threads] Multithreading refactor (#4254)

This insufferable work tries to cover some holes on previous threading implementation from yuzu's team, starting with Windows and Linux reordering of priorities (NICE), reworks previous Android's threading and cpu affinity with adpf, adjust emulated clocks/gpu for better "accuracy" with their work, bumps android minSDK for all flavors, legacy will now work with AP 29 to cover A10 - A12, standard will reach A13 as base and finally the optimized build will come with API 35, mostly targeted on devices with A15 support and newer, NDK and AGP wasn't upgraded yet. The performance cost efficiency have been improved based on device power configuration; preventing overheating if certain devices tended to fall into NICE0 (not allocated threads priority, all task ran with higher priority, 11 tasks running within the limited 2 - 7 threads available on the most common configuration 1x3x4 or 1x4x3).

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4254
This commit is contained in:
CamilleLaVey
2026-08-08 04:39:47 +02:00
committed by crueter
parent 5ec94b1971
commit 3f52bf4b4b
45 changed files with 1097 additions and 191 deletions
+1
View File
@@ -195,6 +195,7 @@ private:
void ReleaseThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("GPUFencingThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
Common::SetCurrentThreadToPerformanceCores();
TFence current_fence;
std::deque<std::function<void()>> current_operations;
+58 -30
View File
@@ -10,6 +10,7 @@
#include <condition_variable>
#include <list>
#include <memory>
#include <utility>
#include "common/assert.h"
#include "common/settings.h"
@@ -39,6 +40,19 @@
namespace Tegra {
namespace {
constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) {
switch (clock) {
case Settings::GpuClock::Boost:
return 256;
case Settings::GpuClock::Overclock:
return 512;
default:
return 1;
}
}
} // Anonymous namespace
struct GPU::Impl {
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
: system{system_}
@@ -116,7 +130,7 @@ struct GPU::Impl {
[[nodiscard]] u64 RequestSyncOperation(Func&& action) {
std::unique_lock lck{sync_request_mutex};
const u64 fence = ++last_sync_fence;
sync_requests.emplace_back(action);
sync_requests.emplace_back(std::forward<Func>(action));
return fence;
}
@@ -145,14 +159,8 @@ struct GPU::Impl {
}
[[nodiscard]] u64 GetTicks() const {
u64 gpu_tick = system.CoreTiming().GetGPUTicks();
Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue();
if (overclock != Settings::GpuOverclock::Normal) {
gpu_tick /= 256 * u64(overclock);
}
return gpu_tick;
const u64 gpu_tick = system.CoreTiming().GetGPUTicks();
return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue());
}
void RendererFrameEndNotify() {
@@ -225,9 +233,9 @@ struct GPU::Impl {
}
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, std::vector<Service::Nvidia::NvFence>&& fences) {
size_t num_fences{fences.size()};
const size_t num_fences{fences.size()};
size_t current_request_counter{};
{
if (num_fences != 0) {
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (free_swap_counters.empty()) {
current_request_counter = request_swap_counters.size();
@@ -238,27 +246,42 @@ struct GPU::Impl {
free_swap_counters.pop_front();
}
}
const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
if (num_fences == 0) {
renderer->Composite(layers);
}
const auto executer = [this, current_request_counter, layers_copy = layers]() {
{
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (--request_swap_counters[current_request_counter] != 0) {
return;
}
free_swap_counters.push_back(current_request_counter);
pending_composite_fence = RequestSyncOperation(
[this, current_request_counter, num_fences, composite_layers = std::move(layers),
composite_fences = std::move(fences)] {
if (num_fences == 0) {
renderer->Composite(composite_layers);
return;
}
renderer->Composite(layers_copy);
};
for (size_t i = 0; i < num_fences; i++) {
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
}
});
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
const auto executer = [this, current_request_counter, composite_layers]() {
{
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (--request_swap_counters[current_request_counter] != 0) {
return;
}
free_swap_counters.push_back(current_request_counter);
}
renderer->Composite(composite_layers);
};
for (size_t i = 0; i < num_fences; i++) {
syncpoint_manager.RegisterGuestAction(composite_fences[i].id,
composite_fences[i].value, executer);
}
});
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(wait_fence);
}
void WaitForComposite() {
const u64 fence = pending_composite_fence;
if (fence == 0) {
return;
}
pending_composite_fence = 0;
if (shutting_down.load(std::memory_order_relaxed)) {
return;
}
WaitForSyncOperation(fence);
}
std::vector<u8> GetAppletCaptureBuffer() {
@@ -311,6 +334,7 @@ struct GPU::Impl {
std::deque<size_t> free_swap_counters;
std::deque<size_t> request_swap_counters;
std::mutex request_swap_mutex;
u64 pending_composite_fence{};
};
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
@@ -428,6 +452,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
impl->RequestComposite(std::move(layers), std::move(fences));
}
void GPU::WaitForComposite() {
impl->WaitForComposite();
}
std::vector<u8> GPU::GetAppletCaptureBuffer() {
return impl->GetAppletCaptureBuffer();
}
+3 -1
View File
@@ -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 2018 yuzu Emulator Project
@@ -218,6 +218,8 @@ public:
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
std::vector<Service::Nvidia::NvFence>&& fences);
void WaitForComposite();
std::vector<u8> GetAppletCaptureBuffer();
/// Performs any additional setup necessary in order to begin GPU emulation.
+1
View File
@@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
thread = std::jthread([&](std::stop_token stop_token) {
Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadToPerformanceCores();
system.RegisterHostThread();
auto current_context = context.Acquire();
@@ -305,7 +305,7 @@ size_t GetTotalPipelineWorkers() {
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
#ifdef __ANDROID__
const int configured = AndroidSettings::values.pipeline_worker_count.GetValue();
const int clamped = std::clamp(configured, 4, 8);
const int clamped = std::clamp(configured, 2, 8);
const size_t desired = static_cast<size_t>(clamped);
if (desired == 0) {
return 1ULL;
@@ -351,8 +351,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(),
"VkPipelineBuilder"),
serialization_thread(1, "VkPipelineSerialization") {
"VkPipelineBuilder", {}, Common::ThreadPlacement::Background),
serialization_thread(1, "VkPipelineSerialization", {},
Common::ThreadPlacement::Background) {
const auto& float_control{device.FloatControlProperties()};
const VkDriverId driver_id{device.GetDriverID()};
const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()};
@@ -266,6 +266,8 @@ void PresentManager::WaitPresent() {
void PresentManager::PresentThread(std::stop_token token) {
Common::SetCurrentThreadName("VulkanPresent");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
std::unique_lock lock{queue_mutex};
// Wait for presentation frames
@@ -258,6 +258,8 @@ bool Scheduler::UpdateDescriptorBufferChunk(u32 descriptor_chunk) {
void Scheduler::WorkerThread(std::stop_token stop_token) {
Common::SetCurrentThreadName("VulkanWorker");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadToPerformanceCores();
const auto TryPopQueue{[this](auto& work) -> bool {
if (work_queue.empty()) {
@@ -509,7 +509,8 @@ private:
u64 frame_tick = 0;
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"};
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
Common::ThreadPlacement::Efficiency};
std::vector<std::unique_ptr<AsyncDecodeContext>> async_decodes;
std::deque<PendingUnswizzle> unswizzle_queue;
+1 -2
View File
@@ -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 2023 yuzu Emulator Project
@@ -11,7 +11,6 @@ namespace Tegra::Texture {
Common::ThreadWorker& GetThreadWorkers() {
static Common::ThreadWorker workers{(std::max)(std::thread::hardware_concurrency(), 2U) / 2,
"ImageTranscode"};
return workers;
}