aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorCamilleLaVey <camillelavey99@gmail.com>2026-08-08 04:39:47 +0200
committercrueter <crueter@eden-emu.dev>2026-08-08 04:39:47 +0200
commit3f52bf4b4b34922f9991e4696d27a8f432fe6ea2 (patch)
tree9fee1f6e95de43716b56682e1bcabf1cb90f30b7 /src/core
parent5ec94b19714f75489cdfc62d890d2f94d45514ce (diff)
[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
Diffstat (limited to 'src/core')
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/core_timing.cpp23
-rw-r--r--src/core/cpu_manager.cpp7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h5
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.cpp63
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.h3
-rw-r--r--src/core/hle/service/vi/conductor.cpp32
-rw-r--r--src/core/hle/service/vi/conductor.h1
9 files changed, 83 insertions, 59 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3ff42cf6f5..d110aa5f8b 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -10,6 +10,7 @@
#include "audio_core/audio_core.h"
#include "common/fs/fs.h"
#include "common/logging.h"
+#include "common/adpf.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "common/string_util.h"
@@ -385,6 +386,7 @@ struct System::Impl {
void ShutdownMainProcess() {
SetShuttingDown(true);
+ Common::ADPF::Shutdown();
// Reset per-game flags
Settings::values.use_squashed_iterated_blend = false;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 3e4e7bdc68..d2ee6769cc 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -23,6 +23,21 @@ namespace Core::Timing {
constexpr s64 MAX_SLICE_LENGTH = 10000;
+constexpr u32 CPU_CLOCK_BASE_MHZ = 1020;
+constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734;
+constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040;
+
+constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) {
+ switch (clock) {
+ case Settings::CpuClock::Boost:
+ return CPU_CLOCK_BOOST_MHZ;
+ case Settings::CpuClock::Overclock:
+ return CPU_CLOCK_OVERCLOCK_MHZ;
+ default:
+ return CPU_CLOCK_BASE_MHZ;
+ }
+}
+
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
return std::make_shared<EventType>(std::move(callback), std::move(name));
}
@@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
if (is_multicore) {
timer_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("HostTiming");
- Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
+ Common::SetCurrentThreadToPerformanceCores();
on_thread_init();
has_started = true;
@@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() {
u64 CoreTiming::GetClockTicks() const {
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
- if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
- fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
+ if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue());
+ target != CPU_CLOCK_BASE_MHZ) {
+ fres = fres * target / CPU_CLOCK_BASE_MHZ;
}
if (::Settings::values.sync_core_speed.GetValue()) {
auto const ticks = f64(fres);
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 7194fa5022..23d5ac3dfd 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
-#ifdef __ANDROID__
- // Aimed specifically for Snapdragon 8 Elite devices
- // This kills performance on desktop, but boosts perf for UMA devices
- // like the S8E. Mediatek and Mali likely won't suffer.
- Common::PinCurrentThreadToPerformanceCore(core);
-#endif
+ Common::SetCurrentThreadToPerformanceCores();
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index bdbb16ed04..6e9fe9ec07 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -6,6 +6,7 @@
#include <boost/container/small_vector.hpp>
+#include "common/adpf.h"
#include "common/assert.h"
#include "common/logging.h"
#include "core/core.h"
@@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
}
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
+ Common::ADPF::ReportFrameInterval();
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
system.GetPerfStats().EndSystemFrame();
system.GetPerfStats().BeginSystemFrame();
}
+void nvdisp_disp0::WaitForComposite() {
+ system.GPU().WaitForComposite();
+}
+
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
return nullptr;
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 1082b85c2b..ff0bfb2ec9 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -37,6 +40,8 @@ public:
/// Performs a screen flip, compositing each buffer.
void Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers);
+ void WaitForComposite();
+
Kernel::KEvent* QueryEvent(u32 event_id) override;
private:
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp
index cfd999497b..e491b24a8f 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.cpp
+++ b/src/core/hle/service/nvnflinger/hardware_composer.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 2024 yuzu Emulator Project
@@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
// Set default speed limit to 100%.
*out_speed_scale = 1.0f;
- // If no layers are available, skip the logic.
- bool any_visible = false;
- for (auto& layer : display.stack.layers) {
- if (layer->visible) {
- any_visible = true;
- break;
- }
- }
- if (!any_visible) {
- *out_speed_scale = 1.0f;
- return 1;
- }
+ nvdisp.WaitForComposite();
+ this->ReleaseFramebuffersLocked(display);
// Determine the number of vsync periods to wait before composing again.
std::optional<s32> swap_interval{};
@@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
nvdisp.Composite(composition_stack);
}
- // Batch framebuffer releases, instead of one-into-one.
- std::vector<std::pair<Layer*, Framebuffer*>> to_release;
- for (auto& [layer_id, framebuffer] : m_framebuffers) {
- if (!framebuffer.is_acquired)
- continue;
-
- auto layer = display.stack.FindLayer(layer_id);
- if (!layer)
- continue;
-
- // Overlay layers always release after every compose
- // Non-overlay layers release based on their swap interval
- if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
- to_release.emplace_back(layer.get(), &framebuffer);
- }
- }
- for (auto& [layer, framebuffer] : to_release) {
- layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
- framebuffer->is_acquired = false;
- }
-
// Advance by 1 frame (60 FPS compositing)
m_frame_number += 1;
- // Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
+ return 1;
+}
+
+void HardwareComposer::ReleaseFramebuffersLocked(Display& display) {
for (auto& [layer_id, framebuffer] : m_framebuffers) {
if (!framebuffer.is_acquired) {
- // Already released.
continue;
}
- if (framebuffer.release_frame_number > m_frame_number) {
+ const auto layer = display.stack.FindLayer(layer_id);
+ if (!layer) {
continue;
}
- if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
- // Skip overlay layers as they were already released above
- if (layer->is_overlay) {
- continue;
- }
-
- // TODO: support release fence
- // This is needed to prevent screen tearing
- layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
- framebuffer.is_acquired = false;
+ if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) {
+ continue;
}
- }
- return 1;
+ layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
+ framebuffer.is_acquired = false;
+ }
}
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.h b/src/core/hle/service/nvnflinger/hardware_composer.h
index e9b7194612..9702e0584b 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.h
+++ b/src/core/hle/service/nvnflinger/hardware_composer.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 2024 yuzu Emulator Project
@@ -52,6 +52,7 @@ private:
private:
bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
+ void ReleaseFramebuffersLocked(Display& display);
};
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/vi/conductor.cpp b/src/core/hle/service/vi/conductor.cpp
index 002c653b8c..fd7233cf93 100644
--- a/src/core/hle/service/vi/conductor.cpp
+++ b/src/core/hle/service/vi/conductor.cpp
@@ -4,7 +4,11 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+
+#include "common/adpf.h"
#include "common/settings.h"
+#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/vi/conductor.h"
@@ -14,6 +18,8 @@
constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
+constexpr s64 UNLOCKED_TARGET_DIVISOR = 4;
+
namespace Service::VI {
Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
@@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
}
void Conductor::ProcessVsync() {
+ Common::PollThreadPolicies();
+ Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()});
+
for (auto& [display_id, manager] : m_vsync_managers) {
m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
manager.SignalVsync(m_system.Kernel());
@@ -76,6 +85,8 @@ void Conductor::ProcessVsync() {
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
+ Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
@@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const {
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
+s64 Conductor::GetFramePeriodNs() const {
+ const auto& settings = Settings::values;
+ f32 speed_scale = 1.f;
+ bool unlocked = false;
+ if (settings.use_multi_core.GetValue()) {
+ if (settings.use_speed_limit.GetValue()) {
+ speed_scale = 100.f / Settings::SpeedLimit();
+ } else {
+ unlocked = true;
+ }
+ }
+ speed_scale /= m_compose_speed_scale;
+
+ const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
+ s64 period = static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
+ if (unlocked) {
+ period /= UNLOCKED_TARGET_DIVISOR;
+ }
+ return std::clamp<s64>(period, 1'000'000, 100'000'000);
+}
+
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/conductor.h b/src/core/hle/service/vi/conductor.h
index 59eb2fd022..7c93c230f7 100644
--- a/src/core/hle/service/vi/conductor.h
+++ b/src/core/hle/service/vi/conductor.h
@@ -44,6 +44,7 @@ private:
void ProcessVsync();
void VsyncThread(std::stop_token token);
s64 GetNextTicks() const;
+ s64 GetFramePeriodNs() const;
private:
Core::System& m_system;