mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-25 00:40:32 +00:00
[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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
void ProcessVsync();
|
||||
void VsyncThread(std::stop_token token);
|
||||
s64 GetNextTicks() const;
|
||||
s64 GetFramePeriodNs() const;
|
||||
|
||||
private:
|
||||
Core::System& m_system;
|
||||
|
||||
Reference in New Issue
Block a user