summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/stream.cpp23
-rw-r--r--src/audio_core/stream.h10
-rw-r--r--src/common/alignment.h48
-rw-r--r--src/core/core_timing.cpp15
-rw-r--r--src/core/core_timing.h20
-rw-r--r--src/core/hardware_interrupt_manager.cpp15
-rw-r--r--src/core/hle/kernel/kernel.cpp8
-rw-r--r--src/core/hle/kernel/server_session.cpp6
-rw-r--r--src/core/hle/kernel/time_manager.cpp5
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp4
-rw-r--r--src/core/hle/service/hid/hid.cpp17
-rw-r--r--src/core/hle/service/hid/hid.h7
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp19
-rw-r--r--src/core/memory/cheat_engine.cpp16
-rw-r--r--src/core/memory/cheat_engine.h3
-rw-r--r--src/core/tools/freezer.cpp17
-rw-r--r--src/core/tools/freezer.h3
-rw-r--r--src/tests/core/core_timing.cpp27
-rw-r--r--src/yuzu/CMakeLists.txt33
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp9
-rw-r--r--src/yuzu/configuration/configure_dialog.h6
-rw-r--r--src/yuzu/configuration/configure_ui.cpp31
-rw-r--r--src/yuzu/configuration/configure_ui.h7
-rw-r--r--src/yuzu/configuration/configure_ui.ui188
-rw-r--r--src/yuzu/debugger/wait_tree.cpp52
-rw-r--r--src/yuzu/main.cpp42
-rw-r--r--src/yuzu/main.h5
-rw-r--r--src/yuzu/uisettings.h1
29 files changed, 401 insertions, 238 deletions
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index aab3e979a..f80ab92e4 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -38,7 +38,7 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
38 sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} { 38 sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
39 39
40 release_event = Core::Timing::CreateEvent( 40 release_event = Core::Timing::CreateEvent(
41 name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(cycles_late); }); 41 name, [this](u64, std::chrono::nanoseconds ns_late) { ReleaseActiveBuffer(ns_late); });
42} 42}
43 43
44void Stream::Play() { 44void Stream::Play() {
@@ -59,11 +59,9 @@ Stream::State Stream::GetState() const {
59 return state; 59 return state;
60} 60}
61 61
62s64 Stream::GetBufferReleaseNS(const Buffer& buffer) const { 62std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const {
63 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; 63 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
64 const auto ns = 64 return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
65 std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
66 return ns.count();
67} 65}
68 66
69static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { 67static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
@@ -80,7 +78,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
80 } 78 }
81} 79}
82 80
83void Stream::PlayNextBuffer(s64 cycles_late) { 81void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
84 if (!IsPlaying()) { 82 if (!IsPlaying()) {
85 // Ensure we are in playing state before playing the next buffer 83 // Ensure we are in playing state before playing the next buffer
86 sink_stream.Flush(); 84 sink_stream.Flush();
@@ -105,17 +103,18 @@ void Stream::PlayNextBuffer(s64 cycles_late) {
105 103
106 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); 104 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
107 105
108 core_timing.ScheduleEvent( 106 const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue()
109 GetBufferReleaseNS(*active_buffer) - 107 ? std::chrono::nanoseconds::zero()
110 (Settings::values.enable_audio_stretching.GetValue() ? 0 : cycles_late), 108 : ns_late;
111 release_event, {}); 109 const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
110 core_timing.ScheduleEvent(future_time, release_event, {});
112} 111}
113 112
114void Stream::ReleaseActiveBuffer(s64 cycles_late) { 113void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
115 ASSERT(active_buffer); 114 ASSERT(active_buffer);
116 released_buffers.push(std::move(active_buffer)); 115 released_buffers.push(std::move(active_buffer));
117 release_callback(); 116 release_callback();
118 PlayNextBuffer(cycles_late); 117 PlayNextBuffer(ns_late);
119} 118}
120 119
121bool Stream::QueueBuffer(BufferPtr&& buffer) { 120bool Stream::QueueBuffer(BufferPtr&& buffer) {
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 524376257..6437b8591 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <chrono>
7#include <functional> 8#include <functional>
8#include <memory> 9#include <memory>
9#include <string> 10#include <string>
@@ -90,16 +91,13 @@ public:
90 91
91private: 92private:
92 /// Plays the next queued buffer in the audio stream, starting playback if necessary 93 /// Plays the next queued buffer in the audio stream, starting playback if necessary
93 void PlayNextBuffer(s64 cycles_late = 0); 94 void PlayNextBuffer(std::chrono::nanoseconds ns_late = {});
94 95
95 /// Releases the actively playing buffer, signalling that it has been completed 96 /// Releases the actively playing buffer, signalling that it has been completed
96 void ReleaseActiveBuffer(s64 cycles_late = 0); 97 void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {});
97 98
98 /// Gets the number of core cycles when the specified buffer will be released 99 /// Gets the number of core cycles when the specified buffer will be released
99 s64 GetBufferReleaseNS(const Buffer& buffer) const; 100 std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
100
101 /// Gets the number of core cycles when the specified buffer will be released
102 s64 GetBufferReleaseNSHostTiming(const Buffer& buffer) const;
103 101
104 u32 sample_rate; ///< Sample rate of the stream 102 u32 sample_rate; ///< Sample rate of the stream
105 Format format; ///< Format of the stream 103 Format format; ///< Format of the stream
diff --git a/src/common/alignment.h b/src/common/alignment.h
index b37044bb6..ef4d6f896 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -3,7 +3,7 @@
3#pragma once 3#pragma once
4 4
5#include <cstddef> 5#include <cstddef>
6#include <memory> 6#include <new>
7#include <type_traits> 7#include <type_traits>
8 8
9namespace Common { 9namespace Common {
@@ -54,66 +54,28 @@ public:
54 using size_type = std::size_t; 54 using size_type = std::size_t;
55 using difference_type = std::ptrdiff_t; 55 using difference_type = std::ptrdiff_t;
56 56
57 using pointer = T*;
58 using const_pointer = const T*;
59
60 using reference = T&;
61 using const_reference = const T&;
62
63 using propagate_on_container_copy_assignment = std::true_type; 57 using propagate_on_container_copy_assignment = std::true_type;
64 using propagate_on_container_move_assignment = std::true_type; 58 using propagate_on_container_move_assignment = std::true_type;
65 using propagate_on_container_swap = std::true_type; 59 using propagate_on_container_swap = std::true_type;
66 using is_always_equal = std::true_type; 60 using is_always_equal = std::true_type;
67 61
68public:
69 constexpr AlignmentAllocator() noexcept = default; 62 constexpr AlignmentAllocator() noexcept = default;
70 63
71 template <typename T2> 64 template <typename T2>
72 constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {} 65 constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {}
73 66
74 pointer address(reference r) noexcept { 67 T* allocate(size_type n) {
75 return std::addressof(r); 68 return static_cast<T*>(::operator new (n * sizeof(T), std::align_val_t{Align}));
76 }
77
78 const_pointer address(const_reference r) const noexcept {
79 return std::addressof(r);
80 }
81
82 pointer allocate(size_type n) {
83 return static_cast<pointer>(::operator new (n, std::align_val_t{Align}));
84 }
85
86 void deallocate(pointer p, size_type) {
87 ::operator delete (p, std::align_val_t{Align});
88 } 69 }
89 70
90 void construct(pointer p, const value_type& wert) { 71 void deallocate(T* p, size_type n) {
91 new (p) value_type(wert); 72 ::operator delete (p, n * sizeof(T), std::align_val_t{Align});
92 }
93
94 void destroy(pointer p) {
95 p->~value_type();
96 }
97
98 size_type max_size() const noexcept {
99 return size_type(-1) / sizeof(value_type);
100 } 73 }
101 74
102 template <typename T2> 75 template <typename T2>
103 struct rebind { 76 struct rebind {
104 using other = AlignmentAllocator<T2, Align>; 77 using other = AlignmentAllocator<T2, Align>;
105 }; 78 };
106
107 bool operator!=(const AlignmentAllocator<T, Align>& other) const noexcept {
108 return !(*this == other);
109 }
110
111 // Returns true if and only if storage allocated from *this
112 // can be deallocated from other, and vice versa.
113 // Always returns true for stateless allocators.
114 bool operator==(const AlignmentAllocator<T, Align>& other) const noexcept {
115 return true;
116 }
117}; 79};
118 80
119} // namespace Common 81} // namespace Common
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index a63e60461..b5feb3f24 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -53,12 +53,12 @@ void CoreTiming::ThreadEntry(CoreTiming& instance) {
53 instance.ThreadLoop(); 53 instance.ThreadLoop();
54} 54}
55 55
56void CoreTiming::Initialize(std::function<void(void)>&& on_thread_init_) { 56void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
57 on_thread_init = std::move(on_thread_init_); 57 on_thread_init = std::move(on_thread_init_);
58 event_fifo_id = 0; 58 event_fifo_id = 0;
59 shutting_down = false; 59 shutting_down = false;
60 ticks = 0; 60 ticks = 0;
61 const auto empty_timed_callback = [](u64, s64) {}; 61 const auto empty_timed_callback = [](u64, std::chrono::nanoseconds) {};
62 ev_lost = CreateEvent("_lost_event", empty_timed_callback); 62 ev_lost = CreateEvent("_lost_event", empty_timed_callback);
63 if (is_multicore) { 63 if (is_multicore) {
64 timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this)); 64 timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
@@ -106,11 +106,11 @@ bool CoreTiming::HasPendingEvents() const {
106 return !(wait_set && event_queue.empty()); 106 return !(wait_set && event_queue.empty());
107} 107}
108 108
109void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type, 109void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
110 u64 userdata) { 110 const std::shared_ptr<EventType>& event_type, u64 userdata) {
111 { 111 {
112 std::scoped_lock scope{basic_lock}; 112 std::scoped_lock scope{basic_lock};
113 const u64 timeout = static_cast<u64>(GetGlobalTimeNs().count() + ns_into_future); 113 const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
114 114
115 event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type}); 115 event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
116 116
@@ -195,8 +195,9 @@ std::optional<s64> CoreTiming::Advance() {
195 event_queue.pop_back(); 195 event_queue.pop_back();
196 basic_lock.unlock(); 196 basic_lock.unlock();
197 197
198 if (auto event_type{evt.type.lock()}) { 198 if (const auto event_type{evt.type.lock()}) {
199 event_type->callback(evt.userdata, global_timer - evt.time); 199 event_type->callback(
200 evt.userdata, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)});
200 } 201 }
201 202
202 basic_lock.lock(); 203 basic_lock.lock();
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 72faaab64..120c74e46 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -17,14 +17,12 @@
17#include "common/common_types.h" 17#include "common/common_types.h"
18#include "common/spin_lock.h" 18#include "common/spin_lock.h"
19#include "common/thread.h" 19#include "common/thread.h"
20#include "common/threadsafe_queue.h"
21#include "common/wall_clock.h" 20#include "common/wall_clock.h"
22#include "core/hardware_properties.h"
23 21
24namespace Core::Timing { 22namespace Core::Timing {
25 23
26/// A callback that may be scheduled for a particular core timing event. 24/// A callback that may be scheduled for a particular core timing event.
27using TimedCallback = std::function<void(u64 userdata, s64 cycles_late)>; 25using TimedCallback = std::function<void(u64 userdata, std::chrono::nanoseconds ns_late)>;
28 26
29/// Contains the characteristics of a particular event. 27/// Contains the characteristics of a particular event.
30struct EventType { 28struct EventType {
@@ -42,12 +40,12 @@ struct EventType {
42 * in main CPU clock cycles. 40 * in main CPU clock cycles.
43 * 41 *
44 * To schedule an event, you first have to register its type. This is where you pass in the 42 * To schedule an event, you first have to register its type. This is where you pass in the
45 * callback. You then schedule events using the type id you get back. 43 * callback. You then schedule events using the type ID you get back.
46 * 44 *
47 * The int cyclesLate that the callbacks get is how many cycles late it was. 45 * The s64 ns_late that the callbacks get is how many ns late it was.
48 * So to schedule a new event on a regular basis: 46 * So to schedule a new event on a regular basis:
49 * inside callback: 47 * inside callback:
50 * ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever") 48 * ScheduleEvent(period_in_ns - ns_late, callback, "whatever")
51 */ 49 */
52class CoreTiming { 50class CoreTiming {
53public: 51public:
@@ -62,7 +60,7 @@ public:
62 60
63 /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is 61 /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
64 /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. 62 /// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
65 void Initialize(std::function<void(void)>&& on_thread_init_); 63 void Initialize(std::function<void()>&& on_thread_init_);
66 64
67 /// Tears down all timing related functionality. 65 /// Tears down all timing related functionality.
68 void Shutdown(); 66 void Shutdown();
@@ -95,8 +93,8 @@ public:
95 bool HasPendingEvents() const; 93 bool HasPendingEvents() const;
96 94
97 /// Schedules an event in core timing 95 /// Schedules an event in core timing
98 void ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type, 96 void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
99 u64 userdata = 0); 97 const std::shared_ptr<EventType>& event_type, u64 userdata = 0);
100 98
101 void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata); 99 void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
102 100
@@ -141,8 +139,6 @@ private:
141 139
142 u64 global_timer = 0; 140 u64 global_timer = 0;
143 141
144 std::chrono::nanoseconds start_point;
145
146 // The queue is a min-heap using std::make_heap/push_heap/pop_heap. 142 // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
147 // We don't use std::priority_queue because we need to be able to serialize, unserialize and 143 // We don't use std::priority_queue because we need to be able to serialize, unserialize and
148 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't 144 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
@@ -161,7 +157,7 @@ private:
161 std::atomic<bool> wait_set{}; 157 std::atomic<bool> wait_set{};
162 std::atomic<bool> shutting_down{}; 158 std::atomic<bool> shutting_down{};
163 std::atomic<bool> has_started{}; 159 std::atomic<bool> has_started{};
164 std::function<void(void)> on_thread_init{}; 160 std::function<void()> on_thread_init{};
165 161
166 bool is_multicore{}; 162 bool is_multicore{};
167 163
diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp
index c629d9fa1..efc1030c1 100644
--- a/src/core/hardware_interrupt_manager.cpp
+++ b/src/core/hardware_interrupt_manager.cpp
@@ -11,19 +11,20 @@
11namespace Core::Hardware { 11namespace Core::Hardware {
12 12
13InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) { 13InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
14 gpu_interrupt_event = Core::Timing::CreateEvent("GPUInterrupt", [this](u64 message, s64) { 14 gpu_interrupt_event =
15 auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv"); 15 Core::Timing::CreateEvent("GPUInterrupt", [this](u64 message, std::chrono::nanoseconds) {
16 const u32 syncpt = static_cast<u32>(message >> 32); 16 auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
17 const u32 value = static_cast<u32>(message); 17 const u32 syncpt = static_cast<u32>(message >> 32);
18 nvdrv->SignalGPUInterruptSyncpt(syncpt, value); 18 const u32 value = static_cast<u32>(message);
19 }); 19 nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
20 });
20} 21}
21 22
22InterruptManager::~InterruptManager() = default; 23InterruptManager::~InterruptManager() = default;
23 24
24void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { 25void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
25 const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value; 26 const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
26 system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg); 27 system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
27} 28}
28 29
29} // namespace Core::Hardware 30} // namespace Core::Hardware
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index e1c7a0f3b..8dd4a2637 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -145,16 +145,18 @@ struct KernelCore::Impl {
145 145
146 void InitializePreemption(KernelCore& kernel) { 146 void InitializePreemption(KernelCore& kernel) {
147 preemption_event = Core::Timing::CreateEvent( 147 preemption_event = Core::Timing::CreateEvent(
148 "PreemptionCallback", [this, &kernel](u64 userdata, s64 cycles_late) { 148 "PreemptionCallback", [this, &kernel](u64, std::chrono::nanoseconds) {
149 { 149 {
150 SchedulerLock lock(kernel); 150 SchedulerLock lock(kernel);
151 global_scheduler.PreemptThreads(); 151 global_scheduler.PreemptThreads();
152 } 152 }
153 s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); 153 const auto time_interval = std::chrono::nanoseconds{
154 Core::Timing::msToCycles(std::chrono::milliseconds(10))};
154 system.CoreTiming().ScheduleEvent(time_interval, preemption_event); 155 system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
155 }); 156 });
156 157
157 s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); 158 const auto time_interval =
159 std::chrono::nanoseconds{Core::Timing::msToCycles(std::chrono::milliseconds(10))};
158 system.CoreTiming().ScheduleEvent(time_interval, preemption_event); 160 system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
159 } 161 }
160 162
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 7b23a6889..af22f4c33 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -34,7 +34,7 @@ ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kern
34 std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)}; 34 std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
35 35
36 session->request_event = Core::Timing::CreateEvent( 36 session->request_event = Core::Timing::CreateEvent(
37 name, [session](u64 userdata, s64 cycles_late) { session->CompleteSyncRequest(); }); 37 name, [session](u64, std::chrono::nanoseconds) { session->CompleteSyncRequest(); });
38 session->name = std::move(name); 38 session->name = std::move(name);
39 session->parent = std::move(parent); 39 session->parent = std::move(parent);
40 40
@@ -184,8 +184,8 @@ ResultCode ServerSession::CompleteSyncRequest() {
184 184
185ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, 185ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
186 Core::Memory::Memory& memory) { 186 Core::Memory::Memory& memory) {
187 ResultCode result = QueueSyncRequest(std::move(thread), memory); 187 const ResultCode result = QueueSyncRequest(std::move(thread), memory);
188 const u64 delay = kernel.IsMulticore() ? 0U : 20000U; 188 const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
189 Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {}); 189 Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {});
190 return result; 190 return result;
191} 191}
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index 941305e8e..88b01b751 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -16,7 +16,7 @@ namespace Kernel {
16 16
17TimeManager::TimeManager(Core::System& system_) : system{system_} { 17TimeManager::TimeManager(Core::System& system_) : system{system_} {
18 time_manager_event_type = Core::Timing::CreateEvent( 18 time_manager_event_type = Core::Timing::CreateEvent(
19 "Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) { 19 "Kernel::TimeManagerCallback", [this](u64 thread_handle, std::chrono::nanoseconds) {
20 SchedulerLock lock(system.Kernel()); 20 SchedulerLock lock(system.Kernel());
21 Handle proper_handle = static_cast<Handle>(thread_handle); 21 Handle proper_handle = static_cast<Handle>(thread_handle);
22 if (cancelled_events[proper_handle]) { 22 if (cancelled_events[proper_handle]) {
@@ -34,7 +34,8 @@ void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64
34 ASSERT(timetask); 34 ASSERT(timetask);
35 ASSERT(timetask->GetStatus() != ThreadStatus::Ready); 35 ASSERT(timetask->GetStatus() != ThreadStatus::Ready);
36 ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex); 36 ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex);
37 system.CoreTiming().ScheduleEvent(nanoseconds, time_manager_event_type, event_handle); 37 system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds},
38 time_manager_event_type, event_handle);
38 } else { 39 } else {
39 event_handle = InvalidHandle; 40 event_handle = InvalidHandle;
40 } 41 }
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index cadc03805..c66124998 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -55,6 +55,10 @@ std::string VfsDirectoryServiceWrapper::GetName() const {
55ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const { 55ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
56 std::string path(FileUtil::SanitizePath(path_)); 56 std::string path(FileUtil::SanitizePath(path_));
57 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 57 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
58 // dir can be nullptr if path contains subdirectories, create those prior to creating the file.
59 if (dir == nullptr) {
60 dir = backing->CreateSubdirectory(FileUtil::GetParentPath(path));
61 }
58 auto file = dir->CreateFile(FileUtil::GetFilename(path)); 62 auto file = dir->CreateFile(FileUtil::GetFilename(path));
59 if (file == nullptr) { 63 if (file == nullptr) {
60 // TODO(DarkLordZach): Find a better error code for this 64 // TODO(DarkLordZach): Find a better error code for this
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index e9020e0dc..680290cbd 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -39,9 +39,10 @@ namespace Service::HID {
39 39
40// Updating period for each HID device. 40// Updating period for each HID device.
41// TODO(ogniK): Find actual polling rate of hid 41// TODO(ogniK): Find actual polling rate of hid
42constexpr s64 pad_update_ticks = static_cast<s64>(1000000000 / 66); 42constexpr auto pad_update_ns = std::chrono::nanoseconds{1000000000 / 66};
43[[maybe_unused]] constexpr s64 accelerometer_update_ticks = static_cast<s64>(1000000000 / 100); 43[[maybe_unused]] constexpr auto accelerometer_update_ns =
44[[maybe_unused]] constexpr s64 gyroscope_update_ticks = static_cast<s64>(1000000000 / 100); 44 std::chrono::nanoseconds{1000000000 / 100};
45[[maybe_unused]] constexpr auto gyroscope_update_ticks = std::chrono::nanoseconds{1000000000 / 100};
45constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; 46constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
46 47
47IAppletResource::IAppletResource(Core::System& system) 48IAppletResource::IAppletResource(Core::System& system)
@@ -75,14 +76,14 @@ IAppletResource::IAppletResource(Core::System& system)
75 GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); 76 GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
76 77
77 // Register update callbacks 78 // Register update callbacks
78 pad_update_event = 79 pad_update_event = Core::Timing::CreateEvent(
79 Core::Timing::CreateEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 ns_late) { 80 "HID::UpdatePadCallback", [this](u64 userdata, std::chrono::nanoseconds ns_late) {
80 UpdateControllers(userdata, ns_late); 81 UpdateControllers(userdata, ns_late);
81 }); 82 });
82 83
83 // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) 84 // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
84 85
85 system.CoreTiming().ScheduleEvent(pad_update_ticks, pad_update_event); 86 system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
86 87
87 ReloadInputDevices(); 88 ReloadInputDevices();
88} 89}
@@ -107,7 +108,7 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
107 rb.PushCopyObjects(shared_mem); 108 rb.PushCopyObjects(shared_mem);
108} 109}
109 110
110void IAppletResource::UpdateControllers(u64 userdata, s64 ns_late) { 111void IAppletResource::UpdateControllers(u64 userdata, std::chrono::nanoseconds ns_late) {
111 auto& core_timing = system.CoreTiming(); 112 auto& core_timing = system.CoreTiming();
112 113
113 const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); 114 const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
@@ -118,7 +119,7 @@ void IAppletResource::UpdateControllers(u64 userdata, s64 ns_late) {
118 controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); 119 controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
119 } 120 }
120 121
121 core_timing.ScheduleEvent(pad_update_ticks - ns_late, pad_update_event); 122 core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
122} 123}
123 124
124class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { 125class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 6fb048360..c6f0a2584 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,10 +4,9 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "core/hle/service/hid/controllers/controller_base.h" 7#include <chrono>
8#include "core/hle/service/service.h"
9 8
10#include "controllers/controller_base.h" 9#include "core/hle/service/hid/controllers/controller_base.h"
11#include "core/hle/service/service.h" 10#include "core/hle/service/service.h"
12 11
13namespace Core::Timing { 12namespace Core::Timing {
@@ -65,7 +64,7 @@ private:
65 } 64 }
66 65
67 void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); 66 void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
68 void UpdateControllers(u64 userdata, s64 cycles_late); 67 void UpdateControllers(u64 userdata, std::chrono::nanoseconds ns_late);
69 68
70 std::shared_ptr<Kernel::SharedMemory> shared_mem; 69 std::shared_ptr<Kernel::SharedMemory> shared_mem;
71 70
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 2f44d3779..789856118 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -28,8 +28,7 @@
28 28
29namespace Service::NVFlinger { 29namespace Service::NVFlinger {
30 30
31constexpr s64 frame_ticks = static_cast<s64>(1000000000 / 60); 31constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
32constexpr s64 frame_ticks_30fps = static_cast<s64>(1000000000 / 30);
33 32
34void NVFlinger::VSyncThread(NVFlinger& nv_flinger) { 33void NVFlinger::VSyncThread(NVFlinger& nv_flinger) {
35 nv_flinger.SplitVSync(); 34 nv_flinger.SplitVSync();
@@ -67,20 +66,24 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
67 guard = std::make_shared<std::mutex>(); 66 guard = std::make_shared<std::mutex>();
68 67
69 // Schedule the screen composition events 68 // Schedule the screen composition events
70 composition_event = 69 composition_event = Core::Timing::CreateEvent(
71 Core::Timing::CreateEvent("ScreenComposition", [this](u64 userdata, s64 ns_late) { 70 "ScreenComposition", [this](u64, std::chrono::nanoseconds ns_late) {
72 Lock(); 71 Lock();
73 Compose(); 72 Compose();
74 const auto ticks = GetNextTicks(); 73
75 this->system.CoreTiming().ScheduleEvent(std::max<s64>(0LL, ticks - ns_late), 74 const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
76 composition_event); 75 const auto ticks_delta = ticks - ns_late;
76 const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
77
78 this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
77 }); 79 });
80
78 if (system.IsMulticore()) { 81 if (system.IsMulticore()) {
79 is_running = true; 82 is_running = true;
80 wait_event = std::make_unique<Common::Event>(); 83 wait_event = std::make_unique<Common::Event>();
81 vsync_thread = std::make_unique<std::thread>(VSyncThread, std::ref(*this)); 84 vsync_thread = std::make_unique<std::thread>(VSyncThread, std::ref(*this));
82 } else { 85 } else {
83 system.CoreTiming().ScheduleEvent(frame_ticks, composition_event); 86 system.CoreTiming().ScheduleEvent(frame_ns, composition_event);
84 } 87 }
85} 88}
86 89
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 53d27859b..ced41b1fe 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -20,7 +20,7 @@
20 20
21namespace Core::Memory { 21namespace Core::Memory {
22 22
23constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(1000000000 / 12); 23constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
24constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; 24constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
25 25
26StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) 26StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
@@ -188,10 +188,12 @@ CheatEngine::~CheatEngine() {
188} 188}
189 189
190void CheatEngine::Initialize() { 190void CheatEngine::Initialize() {
191 event = Core::Timing::CreateEvent( 191 event = Core::Timing::CreateEvent("CheatEngine::FrameCallback::" +
192 "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), 192 Common::HexToString(metadata.main_nso_build_id),
193 [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); 193 [this](u64 userdata, std::chrono::nanoseconds ns_late) {
194 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); 194 FrameCallback(userdata, ns_late);
195 });
196 core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
195 197
196 metadata.process_id = system.CurrentProcess()->GetProcessID(); 198 metadata.process_id = system.CurrentProcess()->GetProcessID();
197 metadata.title_id = system.CurrentProcess()->GetTitleID(); 199 metadata.title_id = system.CurrentProcess()->GetTitleID();
@@ -217,7 +219,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
217 219
218MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); 220MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
219 221
220void CheatEngine::FrameCallback(u64 userdata, s64 ns_late) { 222void CheatEngine::FrameCallback(u64, std::chrono::nanoseconds ns_late) {
221 if (is_pending_reload.exchange(false)) { 223 if (is_pending_reload.exchange(false)) {
222 vm.LoadProgram(cheats); 224 vm.LoadProgram(cheats);
223 } 225 }
@@ -230,7 +232,7 @@ void CheatEngine::FrameCallback(u64 userdata, s64 ns_late) {
230 232
231 vm.Execute(metadata); 233 vm.Execute(metadata);
232 234
233 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - ns_late, event); 235 core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
234} 236}
235 237
236} // namespace Core::Memory 238} // namespace Core::Memory
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index 2649423f8..d4068cf84 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <atomic> 7#include <atomic>
8#include <chrono>
8#include <memory> 9#include <memory>
9#include <vector> 10#include <vector>
10#include "common/common_types.h" 11#include "common/common_types.h"
@@ -71,7 +72,7 @@ public:
71 void Reload(std::vector<CheatEntry> cheats); 72 void Reload(std::vector<CheatEntry> cheats);
72 73
73private: 74private:
74 void FrameCallback(u64 userdata, s64 cycles_late); 75 void FrameCallback(u64 userdata, std::chrono::nanoseconds ns_late);
75 76
76 DmntCheatVm vm; 77 DmntCheatVm vm;
77 CheatProcessMetadata metadata; 78 CheatProcessMetadata metadata;
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 8b0c50d11..27b894b51 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -14,7 +14,7 @@
14namespace Tools { 14namespace Tools {
15namespace { 15namespace {
16 16
17constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(1000000000 / 60); 17constexpr auto memory_freezer_ns = std::chrono::nanoseconds{1000000000 / 60};
18 18
19u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { 19u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) {
20 switch (width) { 20 switch (width) {
@@ -55,10 +55,11 @@ void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 v
55 55
56Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_) 56Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_)
57 : core_timing{core_timing_}, memory{memory_} { 57 : core_timing{core_timing_}, memory{memory_} {
58 event = Core::Timing::CreateEvent( 58 event = Core::Timing::CreateEvent("MemoryFreezer::FrameCallback",
59 "MemoryFreezer::FrameCallback", 59 [this](u64 userdata, std::chrono::nanoseconds ns_late) {
60 [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); 60 FrameCallback(userdata, ns_late);
61 core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); 61 });
62 core_timing.ScheduleEvent(memory_freezer_ns, event);
62} 63}
63 64
64Freezer::~Freezer() { 65Freezer::~Freezer() {
@@ -68,7 +69,7 @@ Freezer::~Freezer() {
68void Freezer::SetActive(bool active) { 69void Freezer::SetActive(bool active) {
69 if (!this->active.exchange(active)) { 70 if (!this->active.exchange(active)) {
70 FillEntryReads(); 71 FillEntryReads();
71 core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); 72 core_timing.ScheduleEvent(memory_freezer_ns, event);
72 LOG_DEBUG(Common_Memory, "Memory freezer activated!"); 73 LOG_DEBUG(Common_Memory, "Memory freezer activated!");
73 } else { 74 } else {
74 LOG_DEBUG(Common_Memory, "Memory freezer deactivated!"); 75 LOG_DEBUG(Common_Memory, "Memory freezer deactivated!");
@@ -158,7 +159,7 @@ std::vector<Freezer::Entry> Freezer::GetEntries() const {
158 return entries; 159 return entries;
159} 160}
160 161
161void Freezer::FrameCallback(u64 userdata, s64 ns_late) { 162void Freezer::FrameCallback(u64, std::chrono::nanoseconds ns_late) {
162 if (!IsActive()) { 163 if (!IsActive()) {
163 LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); 164 LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
164 return; 165 return;
@@ -173,7 +174,7 @@ void Freezer::FrameCallback(u64 userdata, s64 ns_late) {
173 MemoryWriteWidth(memory, entry.width, entry.address, entry.value); 174 MemoryWriteWidth(memory, entry.width, entry.address, entry.value);
174 } 175 }
175 176
176 core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - ns_late, event); 177 core_timing.ScheduleEvent(memory_freezer_ns - ns_late, event);
177} 178}
178 179
179void Freezer::FillEntryReads() { 180void Freezer::FillEntryReads() {
diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h
index 62fc6aa6c..8438783d5 100644
--- a/src/core/tools/freezer.h
+++ b/src/core/tools/freezer.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <atomic> 7#include <atomic>
8#include <chrono>
8#include <memory> 9#include <memory>
9#include <mutex> 10#include <mutex>
10#include <optional> 11#include <optional>
@@ -72,7 +73,7 @@ public:
72 std::vector<Entry> GetEntries() const; 73 std::vector<Entry> GetEntries() const;
73 74
74private: 75private:
75 void FrameCallback(u64 userdata, s64 cycles_late); 76 void FrameCallback(u64 userdata, std::chrono::nanoseconds ns_late);
76 void FillEntryReads(); 77 void FillEntryReads();
77 78
78 std::atomic_bool active{false}; 79 std::atomic_bool active{false};
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index e66db1940..244463a47 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <bitset> 8#include <bitset>
9#include <chrono>
9#include <cstdlib> 10#include <cstdlib>
10#include <memory> 11#include <memory>
11#include <string> 12#include <string>
@@ -17,7 +18,6 @@
17namespace { 18namespace {
18// Numbers are chosen randomly to make sure the correct one is given. 19// Numbers are chosen randomly to make sure the correct one is given.
19constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; 20constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
20constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
21constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}}; 21constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
22std::array<s64, 5> delays{}; 22std::array<s64, 5> delays{};
23 23
@@ -25,12 +25,12 @@ std::bitset<CB_IDS.size()> callbacks_ran_flags;
25u64 expected_callback = 0; 25u64 expected_callback = 0;
26 26
27template <unsigned int IDX> 27template <unsigned int IDX>
28void HostCallbackTemplate(u64 userdata, s64 nanoseconds_late) { 28void HostCallbackTemplate(u64 userdata, std::chrono::nanoseconds ns_late) {
29 static_assert(IDX < CB_IDS.size(), "IDX out of range"); 29 static_assert(IDX < CB_IDS.size(), "IDX out of range");
30 callbacks_ran_flags.set(IDX); 30 callbacks_ran_flags.set(IDX);
31 REQUIRE(CB_IDS[IDX] == userdata); 31 REQUIRE(CB_IDS[IDX] == userdata);
32 REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]); 32 REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
33 delays[IDX] = nanoseconds_late; 33 delays[IDX] = ns_late.count();
34 ++expected_callback; 34 ++expected_callback;
35} 35}
36 36
@@ -77,10 +77,12 @@ TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
77 77
78 core_timing.SyncPause(true); 78 core_timing.SyncPause(true);
79 79
80 u64 one_micro = 1000U; 80 const u64 one_micro = 1000U;
81 for (std::size_t i = 0; i < events.size(); i++) { 81 for (std::size_t i = 0; i < events.size(); i++) {
82 u64 order = calls_order[i]; 82 const u64 order = calls_order[i];
83 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); 83 const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
84
85 core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
84 } 86 }
85 /// test pause 87 /// test pause
86 REQUIRE(callbacks_ran_flags.none()); 88 REQUIRE(callbacks_ran_flags.none());
@@ -116,13 +118,16 @@ TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
116 118
117 expected_callback = 0; 119 expected_callback = 0;
118 120
119 u64 start = core_timing.GetGlobalTimeNs().count(); 121 const u64 start = core_timing.GetGlobalTimeNs().count();
120 u64 one_micro = 1000U; 122 const u64 one_micro = 1000U;
123
121 for (std::size_t i = 0; i < events.size(); i++) { 124 for (std::size_t i = 0; i < events.size(); i++) {
122 u64 order = calls_order[i]; 125 const u64 order = calls_order[i];
123 core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); 126 const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
127 core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
124 } 128 }
125 u64 end = core_timing.GetGlobalTimeNs().count(); 129
130 const u64 end = core_timing.GetGlobalTimeNs().count();
126 const double scheduling_time = static_cast<double>(end - start); 131 const double scheduling_time = static_cast<double>(end - start);
127 const double timer_time = static_cast<double>(TestTimerSpeed(core_timing)); 132 const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));
128 133
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index a862b2610..656096c9f 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -133,11 +133,44 @@ file(GLOB COMPAT_LIST
133file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) 133file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
134file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) 134file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
135 135
136if (ENABLE_QT_TRANSLATION)
137 set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
138 option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
139
140 # Update source TS file if enabled
141 if (GENERATE_QT_TRANSLATION)
142 get_target_property(SRCS yuzu SOURCES)
143 qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts)
144 add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts)
145 endif()
146
147 # Find all TS files except en.ts
148 file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts)
149 list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
150
151 # Compile TS files to QM files
152 qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
153
154 # Build a QRC file from the QM file list
155 set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
156 file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
157 foreach (QM ${LANGUAGES_QM})
158 get_filename_component(QM_FILE ${QM} NAME)
159 file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
160 endforeach (QM)
161 file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
162
163 # Add the QRC file to package in all QM files
164 qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
165else()
166 set(LANGUAGES)
167endif()
136 168
137target_sources(yuzu 169target_sources(yuzu
138 PRIVATE 170 PRIVATE
139 ${COMPAT_LIST} 171 ${COMPAT_LIST}
140 ${ICONS} 172 ${ICONS}
173 ${LANGUAGES}
141 ${THEMES} 174 ${THEMES}
142) 175)
143 176
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 805bb954b..59a193edd 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -611,6 +611,7 @@ void Config::ReadPathValues() {
611 } 611 }
612 } 612 }
613 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); 613 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
614 UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
614 615
615 qt_config->endGroup(); 616 qt_config->endGroup();
616} 617}
@@ -1095,6 +1096,7 @@ void Config::SavePathValues() {
1095 } 1096 }
1096 qt_config->endArray(); 1097 qt_config->endArray();
1097 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); 1098 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
1099 WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
1098 1100
1099 qt_config->endGroup(); 1101 qt_config->endGroup();
1100} 1102}
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index a5afb354f..4e30dc51e 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -23,6 +23,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
23 SetConfiguration(); 23 SetConfiguration();
24 PopulateSelectionList(); 24 PopulateSelectionList();
25 25
26 connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
26 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, 27 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
27 &ConfigureDialog::UpdateVisibleTabs); 28 &ConfigureDialog::UpdateVisibleTabs);
28 29
@@ -98,6 +99,14 @@ void ConfigureDialog::PopulateSelectionList() {
98 } 99 }
99} 100}
100 101
102void ConfigureDialog::OnLanguageChanged(const QString& locale) {
103 emit LanguageChanged(locale);
104 // first apply the configuration, and then restore the display
105 ApplyConfiguration();
106 RetranslateUI();
107 SetConfiguration();
108}
109
101void ConfigureDialog::UpdateVisibleTabs() { 110void ConfigureDialog::UpdateVisibleTabs() {
102 const auto items = ui->selectorList->selectedItems(); 111 const auto items = ui->selectorList->selectedItems();
103 if (items.isEmpty()) { 112 if (items.isEmpty()) {
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 2d3bfc2da..4289bc225 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -22,6 +22,12 @@ public:
22 22
23 void ApplyConfiguration(); 23 void ApplyConfiguration();
24 24
25private slots:
26 void OnLanguageChanged(const QString& locale);
27
28signals:
29 void LanguageChanged(const QString& locale);
30
25private: 31private:
26 void changeEvent(QEvent* event) override; 32 void changeEvent(QEvent* event) override;
27 33
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 94424ee44..24b6c5b72 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -5,6 +5,7 @@
5#include <array> 5#include <array>
6#include <utility> 6#include <utility>
7 7
8#include <QDirIterator>
8#include "common/common_types.h" 9#include "common/common_types.h"
9#include "core/settings.h" 10#include "core/settings.h"
10#include "ui_configure_ui.h" 11#include "ui_configure_ui.h"
@@ -29,6 +30,8 @@ constexpr std::array row_text_names{
29ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { 30ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
30 ui->setupUi(this); 31 ui->setupUi(this);
31 32
33 InitializeLanguageComboBox();
34
32 for (const auto& theme : UISettings::themes) { 35 for (const auto& theme : UISettings::themes) {
33 ui->theme_combobox->addItem(QString::fromUtf8(theme.first), 36 ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
34 QString::fromUtf8(theme.second)); 37 QString::fromUtf8(theme.second));
@@ -72,6 +75,8 @@ void ConfigureUi::RequestGameListUpdate() {
72 75
73void ConfigureUi::SetConfiguration() { 76void ConfigureUi::SetConfiguration() {
74 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 77 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
78 ui->language_combobox->setCurrentIndex(
79 ui->language_combobox->findData(UISettings::values.language));
75 ui->show_add_ons->setChecked(UISettings::values.show_add_ons); 80 ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
76 ui->icon_size_combobox->setCurrentIndex( 81 ui->icon_size_combobox->setCurrentIndex(
77 ui->icon_size_combobox->findData(UISettings::values.icon_size)); 82 ui->icon_size_combobox->findData(UISettings::values.icon_size));
@@ -100,6 +105,25 @@ void ConfigureUi::RetranslateUI() {
100 } 105 }
101} 106}
102 107
108void ConfigureUi::InitializeLanguageComboBox() {
109 ui->language_combobox->addItem(tr("<System>"), QString{});
110 ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
111 QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
112 while (it.hasNext()) {
113 QString locale = it.next();
114 locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
115 locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
116 const QString lang = QLocale::languageToString(QLocale(locale).language());
117 ui->language_combobox->addItem(lang, locale);
118 }
119
120 // Unlike other configuration changes, interface language changes need to be reflected on the
121 // interface immediately. This is done by passing a signal to the main window, and then
122 // retranslating when passing back.
123 connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
124 &ConfigureUi::OnLanguageChanged);
125}
126
103void ConfigureUi::InitializeIconSizeComboBox() { 127void ConfigureUi::InitializeIconSizeComboBox() {
104 for (const auto& size : default_icon_sizes) { 128 for (const auto& size : default_icon_sizes) {
105 ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first); 129 ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
@@ -147,3 +171,10 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) {
147 ui->row_2_text_combobox->removeItem( 171 ui->row_2_text_combobox->removeItem(
148 ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData())); 172 ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));
149} 173}
174
175void ConfigureUi::OnLanguageChanged(int index) {
176 if (index == -1)
177 return;
178
179 emit LanguageChanged(ui->language_combobox->itemData(index).toString());
180}
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index d471afe99..c30bcf6ff 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -20,6 +20,12 @@ public:
20 20
21 void ApplyConfiguration(); 21 void ApplyConfiguration();
22 22
23private slots:
24 void OnLanguageChanged(int index);
25
26signals:
27 void LanguageChanged(const QString& locale);
28
23private: 29private:
24 void RequestGameListUpdate(); 30 void RequestGameListUpdate();
25 31
@@ -28,6 +34,7 @@ private:
28 void changeEvent(QEvent*) override; 34 void changeEvent(QEvent*) override;
29 void RetranslateUI(); 35 void RetranslateUI();
30 36
37 void InitializeLanguageComboBox();
31 void InitializeIconSizeComboBox(); 38 void InitializeIconSizeComboBox();
32 void InitializeRowComboBoxes(); 39 void InitializeRowComboBoxes();
33 40
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index bd5c5d3c2..0b81747d7 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -13,112 +13,132 @@
13 <property name="windowTitle"> 13 <property name="windowTitle">
14 <string>Form</string> 14 <string>Form</string>
15 </property> 15 </property>
16 <layout class="QHBoxLayout" name="HorizontalLayout"> 16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item> 17 <item>
18 <layout class="QVBoxLayout" name="VerticalLayout"> 18 <widget class="QGroupBox" name="general_groupBox">
19 <item> 19 <property name="title">
20 <widget class="QGroupBox" name="GeneralGroupBox"> 20 <string>General</string>
21 <property name="title"> 21 </property>
22 <string>General</string> 22 <layout class="QHBoxLayout" name="horizontalLayout">
23 </property> 23 <item>
24 <layout class="QHBoxLayout" name="horizontalLayout"> 24 <layout class="QVBoxLayout" name="verticalLayout_2">
25 <item> 25 <item>
26 <layout class="QVBoxLayout" name="verticalLayout"> 26 <widget class="QLabel" name="label_change_language_info">
27 <property name="text">
28 <string>Note: Changing language will apply your configuration.</string>
29 </property>
30 <property name="wordWrap">
31 <bool>true</bool>
32 </property>
33 </widget>
34 </item>
35 <item>
36 <layout class="QHBoxLayout" name="horizontalLayout_2">
37 <item>
38 <widget class="QLabel" name="language_label">
39 <property name="text">
40 <string>Interface language:</string>
41 </property>
42 </widget>
43 </item>
44 <item>
45 <widget class="QComboBox" name="language_combobox"/>
46 </item>
47 </layout>
48 </item>
49 <item>
50 <layout class="QHBoxLayout" name="horizontalLayout_3">
51 <item>
52 <widget class="QLabel" name="theme_label">
53 <property name="text">
54 <string>Theme:</string>
55 </property>
56 </widget>
57 </item>
27 <item> 58 <item>
28 <layout class="QHBoxLayout" name="horizontalLayout_3"> 59 <widget class="QComboBox" name="theme_combobox"/>
29 <item>
30 <widget class="QLabel" name="theme_label">
31 <property name="text">
32 <string>Theme:</string>
33 </property>
34 </widget>
35 </item>
36 <item>
37 <widget class="QComboBox" name="theme_combobox"/>
38 </item>
39 </layout>
40 </item> 60 </item>
41 </layout> 61 </layout>
42 </item> 62 </item>
43 </layout> 63 </layout>
44 </widget> 64 </item>
45 </item> 65 </layout>
46 <item> 66 </widget>
47 <widget class="QGroupBox" name="GameListGroupBox"> 67 </item>
48 <property name="title"> 68 <item>
49 <string>Game List</string> 69 <widget class="QGroupBox" name="GameListGroupBox">
50 </property> 70 <property name="title">
51 <layout class="QHBoxLayout" name="GameListHorizontalLayout"> 71 <string>Game List</string>
72 </property>
73 <layout class="QHBoxLayout" name="GameListHorizontalLayout">
74 <item>
75 <layout class="QVBoxLayout" name="GeneralVerticalLayout">
52 <item> 76 <item>
53 <layout class="QVBoxLayout" name="GeneralVerticalLayout"> 77 <widget class="QCheckBox" name="show_add_ons">
78 <property name="text">
79 <string>Show Add-Ons Column</string>
80 </property>
81 </widget>
82 </item>
83 <item>
84 <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
54 <item> 85 <item>
55 <widget class="QCheckBox" name="show_add_ons"> 86 <widget class="QLabel" name="icon_size_label">
56 <property name="text"> 87 <property name="text">
57 <string>Show Add-Ons Column</string> 88 <string>Icon Size:</string>
58 </property> 89 </property>
59 </widget> 90 </widget>
60 </item> 91 </item>
61 <item> 92 <item>
62 <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> 93 <widget class="QComboBox" name="icon_size_combobox"/>
63 <item>
64 <widget class="QLabel" name="icon_size_label">
65 <property name="text">
66 <string>Icon Size:</string>
67 </property>
68 </widget>
69 </item>
70 <item>
71 <widget class="QComboBox" name="icon_size_combobox"/>
72 </item>
73 </layout>
74 </item> 94 </item>
95 </layout>
96 </item>
97 <item>
98 <layout class="QHBoxLayout" name="row_1_qhbox_layout">
75 <item> 99 <item>
76 <layout class="QHBoxLayout" name="row_1_qhbox_layout"> 100 <widget class="QLabel" name="row_1_label">
77 <item> 101 <property name="text">
78 <widget class="QLabel" name="row_1_label"> 102 <string>Row 1 Text:</string>
79 <property name="text"> 103 </property>
80 <string>Row 1 Text:</string> 104 </widget>
81 </property>
82 </widget>
83 </item>
84 <item>
85 <widget class="QComboBox" name="row_1_text_combobox"/>
86 </item>
87 </layout>
88 </item> 105 </item>
89 <item> 106 <item>
90 <layout class="QHBoxLayout" name="row_2_qhbox_layout"> 107 <widget class="QComboBox" name="row_1_text_combobox"/>
91 <item> 108 </item>
92 <widget class="QLabel" name="row_2_label"> 109 </layout>
93 <property name="text"> 110 </item>
94 <string>Row 2 Text:</string> 111 <item>
95 </property> 112 <layout class="QHBoxLayout" name="row_2_qhbox_layout">
96 </widget> 113 <item>
97 </item> 114 <widget class="QLabel" name="row_2_label">
98 <item> 115 <property name="text">
99 <widget class="QComboBox" name="row_2_text_combobox"/> 116 <string>Row 2 Text:</string>
100 </item> 117 </property>
101 </layout> 118 </widget>
119 </item>
120 <item>
121 <widget class="QComboBox" name="row_2_text_combobox"/>
102 </item> 122 </item>
103 </layout> 123 </layout>
104 </item> 124 </item>
105 </layout> 125 </layout>
106 </widget> 126 </item>
107 </item> 127 </layout>
108 <item> 128 </widget>
109 <spacer name="verticalSpacer"> 129 </item>
110 <property name="orientation"> 130 <item>
111 <enum>Qt::Vertical</enum> 131 <spacer name="verticalSpacer">
112 </property> 132 <property name="orientation">
113 <property name="sizeHint" stdset="0"> 133 <enum>Qt::Vertical</enum>
114 <size> 134 </property>
115 <width>20</width> 135 <property name="sizeHint" stdset="0">
116 <height>40</height> 136 <size>
117 </size> 137 <width>20</width>
118 </property> 138 <height>40</height>
119 </spacer> 139 </size>
120 </item> 140 </property>
121 </layout> 141 </spacer>
122 </item> 142 </item>
123 </layout> 143 </layout>
124 </widget> 144 </widget>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 9bb0a0109..f391a41a9 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -2,9 +2,11 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array>
5#include <fmt/format.h> 6#include <fmt/format.h>
6 7
7#include "yuzu/debugger/wait_tree.h" 8#include "yuzu/debugger/wait_tree.h"
9#include "yuzu/uisettings.h"
8#include "yuzu/util/util.h" 10#include "yuzu/util/util.h"
9 11
10#include "common/assert.h" 12#include "common/assert.h"
@@ -19,11 +21,37 @@
19#include "core/hle/kernel/thread.h" 21#include "core/hle/kernel/thread.h"
20#include "core/memory.h" 22#include "core/memory.h"
21 23
24namespace {
25
26constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
27 {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
28 {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
29 {Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan},
30 {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
31 {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
32 {Qt::GlobalColor::darkRed, Qt::GlobalColor::red},
33 {Qt::GlobalColor::darkYellow, Qt::GlobalColor::yellow},
34 {Qt::GlobalColor::red, Qt::GlobalColor::red},
35 {Qt::GlobalColor::darkCyan, Qt::GlobalColor::cyan},
36 {Qt::GlobalColor::gray, Qt::GlobalColor::gray},
37}};
38
39bool IsDarkTheme() {
40 const auto& theme = UISettings::values.theme;
41 return theme == QStringLiteral("qdarkstyle") || theme == QStringLiteral("colorful_dark");
42}
43
44} // namespace
45
22WaitTreeItem::WaitTreeItem() = default; 46WaitTreeItem::WaitTreeItem() = default;
23WaitTreeItem::~WaitTreeItem() = default; 47WaitTreeItem::~WaitTreeItem() = default;
24 48
25QColor WaitTreeItem::GetColor() const { 49QColor WaitTreeItem::GetColor() const {
26 return QColor(Qt::GlobalColor::black); 50 if (IsDarkTheme()) {
51 return QColor(Qt::GlobalColor::white);
52 } else {
53 return QColor(Qt::GlobalColor::black);
54 }
27} 55}
28 56
29std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const { 57std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const {
@@ -263,36 +291,38 @@ QString WaitTreeThread::GetText() const {
263} 291}
264 292
265QColor WaitTreeThread::GetColor() const { 293QColor WaitTreeThread::GetColor() const {
294 const std::size_t color_index = IsDarkTheme() ? 1 : 0;
295
266 const auto& thread = static_cast<const Kernel::Thread&>(object); 296 const auto& thread = static_cast<const Kernel::Thread&>(object);
267 switch (thread.GetStatus()) { 297 switch (thread.GetStatus()) {
268 case Kernel::ThreadStatus::Running: 298 case Kernel::ThreadStatus::Running:
269 return QColor(Qt::GlobalColor::darkGreen); 299 return QColor(WaitTreeColors[0][color_index]);
270 case Kernel::ThreadStatus::Ready: 300 case Kernel::ThreadStatus::Ready:
271 if (!thread.IsPaused()) { 301 if (!thread.IsPaused()) {
272 if (thread.WasRunning()) { 302 if (thread.WasRunning()) {
273 return QColor(Qt::GlobalColor::darkGreen); 303 return QColor(WaitTreeColors[1][color_index]);
274 } else { 304 } else {
275 return QColor(Qt::GlobalColor::darkBlue); 305 return QColor(WaitTreeColors[2][color_index]);
276 } 306 }
277 } else { 307 } else {
278 return QColor(Qt::GlobalColor::lightGray); 308 return QColor(WaitTreeColors[3][color_index]);
279 } 309 }
280 case Kernel::ThreadStatus::Paused: 310 case Kernel::ThreadStatus::Paused:
281 return QColor(Qt::GlobalColor::lightGray); 311 return QColor(WaitTreeColors[4][color_index]);
282 case Kernel::ThreadStatus::WaitHLEEvent: 312 case Kernel::ThreadStatus::WaitHLEEvent:
283 case Kernel::ThreadStatus::WaitIPC: 313 case Kernel::ThreadStatus::WaitIPC:
284 return QColor(Qt::GlobalColor::darkRed); 314 return QColor(WaitTreeColors[5][color_index]);
285 case Kernel::ThreadStatus::WaitSleep: 315 case Kernel::ThreadStatus::WaitSleep:
286 return QColor(Qt::GlobalColor::darkYellow); 316 return QColor(WaitTreeColors[6][color_index]);
287 case Kernel::ThreadStatus::WaitSynch: 317 case Kernel::ThreadStatus::WaitSynch:
288 case Kernel::ThreadStatus::WaitMutex: 318 case Kernel::ThreadStatus::WaitMutex:
289 case Kernel::ThreadStatus::WaitCondVar: 319 case Kernel::ThreadStatus::WaitCondVar:
290 case Kernel::ThreadStatus::WaitArb: 320 case Kernel::ThreadStatus::WaitArb:
291 return QColor(Qt::GlobalColor::red); 321 return QColor(WaitTreeColors[7][color_index]);
292 case Kernel::ThreadStatus::Dormant: 322 case Kernel::ThreadStatus::Dormant:
293 return QColor(Qt::GlobalColor::darkCyan); 323 return QColor(WaitTreeColors[8][color_index]);
294 case Kernel::ThreadStatus::Dead: 324 case Kernel::ThreadStatus::Dead:
295 return QColor(Qt::GlobalColor::gray); 325 return QColor(WaitTreeColors[9][color_index]);
296 default: 326 default:
297 return WaitTreeItem::GetColor(); 327 return WaitTreeItem::GetColor();
298 } 328 }
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 6909d65d0..31a635176 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -191,6 +191,8 @@ GMainWindow::GMainWindow()
191 provider(std::make_unique<FileSys::ManualContentProvider>()) { 191 provider(std::make_unique<FileSys::ManualContentProvider>()) {
192 InitializeLogging(); 192 InitializeLogging();
193 193
194 LoadTranslation();
195
194 setAcceptDrops(true); 196 setAcceptDrops(true);
195 ui.setupUi(this); 197 ui.setupUi(this);
196 statusBar()->hide(); 198 statusBar()->hide();
@@ -2048,6 +2050,9 @@ void GMainWindow::OnConfigure() {
2048 const bool old_discord_presence = UISettings::values.enable_discord_presence; 2050 const bool old_discord_presence = UISettings::values.enable_discord_presence;
2049 2051
2050 ConfigureDialog configure_dialog(this, hotkey_registry); 2052 ConfigureDialog configure_dialog(this, hotkey_registry);
2053 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
2054 &GMainWindow::OnLanguageChanged);
2055
2051 const auto result = configure_dialog.exec(); 2056 const auto result = configure_dialog.exec();
2052 if (result != QDialog::Accepted) { 2057 if (result != QDialog::Accepted) {
2053 return; 2058 return;
@@ -2620,6 +2625,43 @@ void GMainWindow::UpdateUITheme() {
2620 QIcon::setThemeSearchPaths(theme_paths); 2625 QIcon::setThemeSearchPaths(theme_paths);
2621} 2626}
2622 2627
2628void GMainWindow::LoadTranslation() {
2629 // If the selected language is English, no need to install any translation
2630 if (UISettings::values.language == QStringLiteral("en")) {
2631 return;
2632 }
2633
2634 bool loaded;
2635
2636 if (UISettings::values.language.isEmpty()) {
2637 // If the selected language is empty, use system locale
2638 loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
2639 } else {
2640 // Otherwise load from the specified file
2641 loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
2642 }
2643
2644 if (loaded) {
2645 qApp->installTranslator(&translator);
2646 } else {
2647 UISettings::values.language = QStringLiteral("en");
2648 }
2649}
2650
2651void GMainWindow::OnLanguageChanged(const QString& locale) {
2652 if (UISettings::values.language != QStringLiteral("en")) {
2653 qApp->removeTranslator(&translator);
2654 }
2655
2656 UISettings::values.language = locale;
2657 LoadTranslation();
2658 ui.retranslateUi(this);
2659 UpdateWindowTitle();
2660
2661 if (emulation_running)
2662 ui.action_Start->setText(tr("Continue"));
2663}
2664
2623void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { 2665void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
2624#ifdef USE_DISCORD_PRESENCE 2666#ifdef USE_DISCORD_PRESENCE
2625 if (state) { 2667 if (state) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 59d9073ae..db573d606 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -10,6 +10,7 @@
10 10
11#include <QMainWindow> 11#include <QMainWindow>
12#include <QTimer> 12#include <QTimer>
13#include <QTranslator>
13 14
14#include "common/common_types.h" 15#include "common/common_types.h"
15#include "core/core.h" 16#include "core/core.h"
@@ -225,6 +226,7 @@ private slots:
225 void OnCaptureScreenshot(); 226 void OnCaptureScreenshot();
226 void OnCoreError(Core::System::ResultStatus, std::string); 227 void OnCoreError(Core::System::ResultStatus, std::string);
227 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 228 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
229 void OnLanguageChanged(const QString& locale);
228 230
229private: 231private:
230 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 232 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
@@ -237,6 +239,7 @@ private:
237 void HideMouseCursor(); 239 void HideMouseCursor();
238 void ShowMouseCursor(); 240 void ShowMouseCursor();
239 void OpenURL(const QUrl& url); 241 void OpenURL(const QUrl& url);
242 void LoadTranslation();
240 243
241 Ui::MainWindow ui; 244 Ui::MainWindow ui;
242 245
@@ -285,6 +288,8 @@ private:
285 288
286 HotkeyRegistry hotkey_registry; 289 HotkeyRegistry hotkey_registry;
287 290
291 QTranslator translator;
292
288 // Install progress dialog 293 // Install progress dialog
289 QProgressDialog* install_progress; 294 QProgressDialog* install_progress;
290 295
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 830932d45..6cc65736d 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -75,6 +75,7 @@ struct Values {
75 bool game_dir_deprecated_deepscan; 75 bool game_dir_deprecated_deepscan;
76 QVector<UISettings::GameDir> game_dirs; 76 QVector<UISettings::GameDir> game_dirs;
77 QStringList recent_files; 77 QStringList recent_files;
78 QString language;
78 79
79 QString theme; 80 QString theme;
80 81