diff options
| author | 2017-11-25 14:56:57 +0100 | |
|---|---|---|
| committer | 2018-01-08 19:10:25 -0500 | |
| commit | 82151d407d8021fa8865cf8dd51c4d5cf0a4b702 (patch) | |
| tree | 739df280fddbecb50e1a2fa690abe8749486ea2d /src | |
| parent | IPC: Make DuplicateSession return the Domain instead of the Session if the re... (diff) | |
| download | yuzu-82151d407d8021fa8865cf8dd51c4d5cf0a4b702.tar.gz yuzu-82151d407d8021fa8865cf8dd51c4d5cf0a4b702.tar.xz yuzu-82151d407d8021fa8865cf8dd51c4d5cf0a4b702.zip | |
CoreTiming: Reworked CoreTiming (cherry-picked from Citra #3119)
* CoreTiming: New CoreTiming; Add Test for CoreTiming
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio_core/audio_core.cpp | 2 | ||||
| -rw-r--r-- | src/common/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/common/threadsafe_queue.h | 122 | ||||
| -rw-r--r-- | src/core/core.cpp | 1 | ||||
| -rw-r--r-- | src/core/core_timing.cpp | 610 | ||||
| -rw-r--r-- | src/core/core_timing.h | 201 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 5 | ||||
| -rw-r--r-- | src/core/hle/kernel/timer.cpp | 9 | ||||
| -rw-r--r-- | src/core/hle/shared_page.cpp | 4 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 2 | ||||
| -rw-r--r-- | src/tests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/tests/core/core_timing.cpp | 237 |
12 files changed, 638 insertions, 557 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 9c2e6ed88..ae2b68f9c 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp | |||
| @@ -18,7 +18,7 @@ | |||
| 18 | namespace AudioCore { | 18 | namespace AudioCore { |
| 19 | 19 | ||
| 20 | // Audio Ticks occur about every 5 miliseconds. | 20 | // Audio Ticks occur about every 5 miliseconds. |
| 21 | static int tick_event; ///< CoreTiming event | 21 | static CoreTiming::EventType* tick_event; ///< CoreTiming event |
| 22 | static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles | 22 | static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles |
| 23 | 23 | ||
| 24 | static void AudioTickCallback(u64 /*userdata*/, int cycles_late) { | 24 | static void AudioTickCallback(u64 /*userdata*/, int cycles_late) { |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7e83e64b0..447d7198c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -76,6 +76,7 @@ set(HEADERS | |||
| 76 | telemetry.h | 76 | telemetry.h |
| 77 | thread.h | 77 | thread.h |
| 78 | thread_queue_list.h | 78 | thread_queue_list.h |
| 79 | threadsafe_queue.h | ||
| 79 | timer.h | 80 | timer.h |
| 80 | vector_math.h | 81 | vector_math.h |
| 81 | ) | 82 | ) |
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h new file mode 100644 index 000000000..a0c731e8c --- /dev/null +++ b/src/common/threadsafe_queue.h | |||
| @@ -0,0 +1,122 @@ | |||
| 1 | // Copyright 2010 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | // a simple lockless thread-safe, | ||
| 8 | // single reader, single writer queue | ||
| 9 | |||
| 10 | #include <algorithm> | ||
| 11 | #include <atomic> | ||
| 12 | #include <cstddef> | ||
| 13 | #include <mutex> | ||
| 14 | #include "common/common_types.h" | ||
| 15 | |||
| 16 | namespace Common { | ||
| 17 | template <typename T, bool NeedSize = true> | ||
| 18 | class SPSCQueue { | ||
| 19 | public: | ||
| 20 | SPSCQueue() : size(0) { | ||
| 21 | write_ptr = read_ptr = new ElementPtr(); | ||
| 22 | } | ||
| 23 | ~SPSCQueue() { | ||
| 24 | // this will empty out the whole queue | ||
| 25 | delete read_ptr; | ||
| 26 | } | ||
| 27 | |||
| 28 | u32 Size() const { | ||
| 29 | static_assert(NeedSize, "using Size() on FifoQueue without NeedSize"); | ||
| 30 | return size.load(); | ||
| 31 | } | ||
| 32 | |||
| 33 | bool Empty() const { | ||
| 34 | return !read_ptr->next.load(); | ||
| 35 | } | ||
| 36 | T& Front() const { | ||
| 37 | return read_ptr->current; | ||
| 38 | } | ||
| 39 | template <typename Arg> | ||
| 40 | void Push(Arg&& t) { | ||
| 41 | // create the element, add it to the queue | ||
| 42 | write_ptr->current = std::forward<Arg>(t); | ||
| 43 | // set the next pointer to a new element ptr | ||
| 44 | // then advance the write pointer | ||
| 45 | ElementPtr* new_ptr = new ElementPtr(); | ||
| 46 | write_ptr->next.store(new_ptr, std::memory_order_release); | ||
| 47 | write_ptr = new_ptr; | ||
| 48 | if (NeedSize) | ||
| 49 | size++; | ||
| 50 | } | ||
| 51 | |||
| 52 | void Pop() { | ||
| 53 | if (NeedSize) | ||
| 54 | size--; | ||
| 55 | ElementPtr* tmpptr = read_ptr; | ||
| 56 | // advance the read pointer | ||
| 57 | read_ptr = tmpptr->next.load(); | ||
| 58 | // set the next element to nullptr to stop the recursive deletion | ||
| 59 | tmpptr->next.store(nullptr); | ||
| 60 | delete tmpptr; // this also deletes the element | ||
| 61 | } | ||
| 62 | |||
| 63 | bool Pop(T& t) { | ||
| 64 | if (Empty()) | ||
| 65 | return false; | ||
| 66 | |||
| 67 | if (NeedSize) | ||
| 68 | size--; | ||
| 69 | |||
| 70 | ElementPtr* tmpptr = read_ptr; | ||
| 71 | read_ptr = tmpptr->next.load(std::memory_order_acquire); | ||
| 72 | t = std::move(tmpptr->current); | ||
| 73 | tmpptr->next.store(nullptr); | ||
| 74 | delete tmpptr; | ||
| 75 | return true; | ||
| 76 | } | ||
| 77 | |||
| 78 | // not thread-safe | ||
| 79 | void Clear() { | ||
| 80 | size.store(0); | ||
| 81 | delete read_ptr; | ||
| 82 | write_ptr = read_ptr = new ElementPtr(); | ||
| 83 | } | ||
| 84 | |||
| 85 | private: | ||
| 86 | // stores a pointer to element | ||
| 87 | // and a pointer to the next ElementPtr | ||
| 88 | class ElementPtr { | ||
| 89 | public: | ||
| 90 | ElementPtr() : next(nullptr) {} | ||
| 91 | ~ElementPtr() { | ||
| 92 | ElementPtr* next_ptr = next.load(); | ||
| 93 | |||
| 94 | if (next_ptr) | ||
| 95 | delete next_ptr; | ||
| 96 | } | ||
| 97 | |||
| 98 | T current; | ||
| 99 | std::atomic<ElementPtr*> next; | ||
| 100 | }; | ||
| 101 | |||
| 102 | ElementPtr* write_ptr; | ||
| 103 | ElementPtr* read_ptr; | ||
| 104 | std::atomic<u32> size; | ||
| 105 | }; | ||
| 106 | |||
| 107 | // a simple thread-safe, | ||
| 108 | // single reader, multiple writer queue | ||
| 109 | |||
| 110 | template <typename T, bool NeedSize = true> | ||
| 111 | class MPSCQueue : public SPSCQueue<T, NeedSize> { | ||
| 112 | public: | ||
| 113 | template <typename Arg> | ||
| 114 | void Push(Arg&& t) { | ||
| 115 | std::lock_guard<std::mutex> lock(write_lock); | ||
| 116 | SPSCQueue<T, NeedSize>::Push(t); | ||
| 117 | } | ||
| 118 | |||
| 119 | private: | ||
| 120 | std::mutex write_lock; | ||
| 121 | }; | ||
| 122 | } // namespace Common | ||
diff --git a/src/core/core.cpp b/src/core/core.cpp index d7e2450ff..40ef58f59 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -54,6 +54,7 @@ System::ResultStatus System::RunLoop(int tight_loop) { | |||
| 54 | CoreTiming::Advance(); | 54 | CoreTiming::Advance(); |
| 55 | PrepareReschedule(); | 55 | PrepareReschedule(); |
| 56 | } else { | 56 | } else { |
| 57 | CoreTiming::Advance(); | ||
| 57 | cpu_core->Run(tight_loop); | 58 | cpu_core->Run(tight_loop); |
| 58 | } | 59 | } |
| 59 | 60 | ||
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index c90e62385..a0656f0a8 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp | |||
| @@ -1,562 +1,238 @@ | |||
| 1 | // Copyright (c) 2012- PPSSPP Project / Dolphin Project. | 1 | // Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project |
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2+ |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <atomic> | 5 | #include "core/core_timing.h" |
| 6 | |||
| 7 | #include <algorithm> | ||
| 6 | #include <cinttypes> | 8 | #include <cinttypes> |
| 7 | #include <mutex> | 9 | #include <mutex> |
| 10 | #include <string> | ||
| 11 | #include <tuple> | ||
| 12 | #include <unordered_map> | ||
| 8 | #include <vector> | 13 | #include <vector> |
| 9 | #include "common/chunk_file.h" | 14 | #include "common/assert.h" |
| 10 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 11 | #include "common/string_util.h" | 16 | #include "common/thread.h" |
| 12 | #include "core/arm/arm_interface.h" | 17 | #include "common/threadsafe_queue.h" |
| 13 | #include "core/core.h" | ||
| 14 | #include "core/core_timing.h" | ||
| 15 | |||
| 16 | int g_clock_rate_arm11 = BASE_CLOCK_RATE; | ||
| 17 | |||
| 18 | // is this really necessary? | ||
| 19 | #define INITIAL_SLICE_LENGTH 20000 | ||
| 20 | #define MAX_SLICE_LENGTH 100000000 | ||
| 21 | 18 | ||
| 22 | namespace CoreTiming { | 19 | namespace CoreTiming { |
| 23 | struct EventType { | ||
| 24 | EventType() {} | ||
| 25 | 20 | ||
| 26 | EventType(TimedCallback cb, const char* n) : callback(cb), name(n) {} | 21 | static s64 global_timer; |
| 22 | static int slice_length; | ||
| 23 | static int downcount; | ||
| 27 | 24 | ||
| 25 | struct EventType { | ||
| 28 | TimedCallback callback; | 26 | TimedCallback callback; |
| 29 | const char* name; | 27 | const std::string* name; |
| 30 | }; | 28 | }; |
| 31 | 29 | ||
| 32 | static std::vector<EventType> event_types; | 30 | struct Event { |
| 33 | |||
| 34 | struct BaseEvent { | ||
| 35 | s64 time; | 31 | s64 time; |
| 32 | u64 fifo_order; | ||
| 36 | u64 userdata; | 33 | u64 userdata; |
| 37 | int type; | 34 | const EventType* type; |
| 38 | }; | 35 | }; |
| 39 | 36 | ||
| 40 | typedef LinkedListItem<BaseEvent> Event; | 37 | // Sort by time, unless the times are the same, in which case sort by the order added to the queue |
| 41 | 38 | static bool operator>(const Event& left, const Event& right) { | |
| 42 | static Event* first; | 39 | return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order); |
| 43 | static Event* ts_first; | ||
| 44 | static Event* ts_last; | ||
| 45 | |||
| 46 | // event pools | ||
| 47 | static Event* event_pool = nullptr; | ||
| 48 | static Event* event_ts_pool = nullptr; | ||
| 49 | static int allocated_ts_events = 0; | ||
| 50 | // Optimization to skip MoveEvents when possible. | ||
| 51 | static std::atomic<bool> has_ts_events(false); | ||
| 52 | |||
| 53 | int g_slice_length; | ||
| 54 | |||
| 55 | static s64 global_timer; | ||
| 56 | static s64 idled_cycles; | ||
| 57 | static s64 last_global_time_ticks; | ||
| 58 | static s64 last_global_time_us; | ||
| 59 | |||
| 60 | static s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event, | ||
| 61 | /// decreased by the cpu run loop | ||
| 62 | |||
| 63 | static std::recursive_mutex external_event_section; | ||
| 64 | |||
| 65 | // Warning: not included in save state. | ||
| 66 | using AdvanceCallback = void(int cycles_executed); | ||
| 67 | static AdvanceCallback* advance_callback = nullptr; | ||
| 68 | static std::vector<MHzChangeCallback> mhz_change_callbacks; | ||
| 69 | |||
| 70 | static void FireMhzChange() { | ||
| 71 | for (auto callback : mhz_change_callbacks) | ||
| 72 | callback(); | ||
| 73 | } | ||
| 74 | |||
| 75 | void SetClockFrequencyMHz(int cpu_mhz) { | ||
| 76 | // When the mhz changes, we keep track of what "time" it was before hand. | ||
| 77 | // This way, time always moves forward, even if mhz is changed. | ||
| 78 | last_global_time_us = GetGlobalTimeUs(); | ||
| 79 | last_global_time_ticks = GetTicks(); | ||
| 80 | |||
| 81 | g_clock_rate_arm11 = cpu_mhz * 1000000; | ||
| 82 | // TODO: Rescale times of scheduled events? | ||
| 83 | |||
| 84 | FireMhzChange(); | ||
| 85 | } | ||
| 86 | |||
| 87 | int GetClockFrequencyMHz() { | ||
| 88 | return g_clock_rate_arm11 / 1000000; | ||
| 89 | } | 40 | } |
| 90 | 41 | ||
| 91 | u64 GetGlobalTimeUs() { | 42 | static bool operator<(const Event& left, const Event& right) { |
| 92 | s64 ticks_since_last = GetTicks() - last_global_time_ticks; | 43 | return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order); |
| 93 | int freq = GetClockFrequencyMHz(); | ||
| 94 | s64 us_since_last = ticks_since_last / freq; | ||
| 95 | return last_global_time_us + us_since_last; | ||
| 96 | } | 44 | } |
| 97 | 45 | ||
| 98 | static Event* GetNewEvent() { | 46 | // unordered_map stores each element separately as a linked list node so pointers to elements |
| 99 | if (!event_pool) | 47 | // remain stable regardless of rehashes/resizing. |
| 100 | return new Event; | 48 | static std::unordered_map<std::string, EventType> event_types; |
| 101 | |||
| 102 | Event* event = event_pool; | ||
| 103 | event_pool = event->next; | ||
| 104 | return event; | ||
| 105 | } | ||
| 106 | 49 | ||
| 107 | static Event* GetNewTsEvent() { | 50 | // The queue is a min-heap using std::make_heap/push_heap/pop_heap. |
| 108 | allocated_ts_events++; | 51 | // We don't use std::priority_queue because we need to be able to serialize, unserialize and |
| 52 | // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated | ||
| 53 | // by the standard adaptor class. | ||
| 54 | static std::vector<Event> event_queue; | ||
| 55 | static u64 event_fifo_id; | ||
| 56 | // the queue for storing the events from other threads threadsafe until they will be added | ||
| 57 | // to the event_queue by the emu thread | ||
| 58 | static Common::MPSCQueue<Event, false> ts_queue; | ||
| 109 | 59 | ||
| 110 | if (!event_ts_pool) | 60 | static constexpr int MAX_SLICE_LENGTH = 20000; |
| 111 | return new Event; | ||
| 112 | 61 | ||
| 113 | Event* event = event_ts_pool; | 62 | static s64 idled_cycles; |
| 114 | event_ts_pool = event->next; | ||
| 115 | return event; | ||
| 116 | } | ||
| 117 | |||
| 118 | static void FreeEvent(Event* event) { | ||
| 119 | event->next = event_pool; | ||
| 120 | event_pool = event; | ||
| 121 | } | ||
| 122 | 63 | ||
| 123 | static void FreeTsEvent(Event* event) { | 64 | // Are we in a function that has been called from Advance() |
| 124 | event->next = event_ts_pool; | 65 | // If events are sheduled from a function that gets called from Advance(), |
| 125 | event_ts_pool = event; | 66 | // don't change slice_length and downcount. |
| 126 | allocated_ts_events--; | 67 | static bool is_global_timer_sane; |
| 127 | } | ||
| 128 | 68 | ||
| 129 | int RegisterEvent(const char* name, TimedCallback callback) { | 69 | static EventType* ev_lost = nullptr; |
| 130 | event_types.emplace_back(callback, name); | ||
| 131 | return (int)event_types.size() - 1; | ||
| 132 | } | ||
| 133 | 70 | ||
| 134 | static void AntiCrashCallback(u64 userdata, int cycles_late) { | 71 | static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {} |
| 135 | LOG_CRITICAL(Core_Timing, "Savestate broken: an unregistered event was called."); | ||
| 136 | } | ||
| 137 | 72 | ||
| 138 | void RestoreRegisterEvent(int event_type, const char* name, TimedCallback callback) { | 73 | EventType* RegisterEvent(const std::string& name, TimedCallback callback) { |
| 139 | if (event_type >= (int)event_types.size()) | 74 | // check for existing type with same name. |
| 140 | event_types.resize(event_type + 1, EventType(AntiCrashCallback, "INVALID EVENT")); | 75 | // we want event type names to remain unique so that we can use them for serialization. |
| 76 | ASSERT_MSG(event_types.find(name) == event_types.end(), | ||
| 77 | "CoreTiming Event \"%s\" is already registered. Events should only be registered " | ||
| 78 | "during Init to avoid breaking save states.", | ||
| 79 | name.c_str()); | ||
| 141 | 80 | ||
| 142 | event_types[event_type] = EventType(callback, name); | 81 | auto info = event_types.emplace(name, EventType{callback, nullptr}); |
| 82 | EventType* event_type = &info.first->second; | ||
| 83 | event_type->name = &info.first->first; | ||
| 84 | return event_type; | ||
| 143 | } | 85 | } |
| 144 | 86 | ||
| 145 | void UnregisterAllEvents() { | 87 | void UnregisterAllEvents() { |
| 146 | if (first) | 88 | ASSERT_MSG(event_queue.empty(), "Cannot unregister events with events pending"); |
| 147 | LOG_ERROR(Core_Timing, "Cannot unregister events with events pending"); | ||
| 148 | event_types.clear(); | 89 | event_types.clear(); |
| 149 | } | 90 | } |
| 150 | 91 | ||
| 151 | void Init() { | 92 | void Init() { |
| 152 | down_count = INITIAL_SLICE_LENGTH; | 93 | downcount = MAX_SLICE_LENGTH; |
| 153 | g_slice_length = INITIAL_SLICE_LENGTH; | 94 | slice_length = MAX_SLICE_LENGTH; |
| 154 | global_timer = 0; | 95 | global_timer = 0; |
| 155 | idled_cycles = 0; | 96 | idled_cycles = 0; |
| 156 | last_global_time_ticks = 0; | ||
| 157 | last_global_time_us = 0; | ||
| 158 | has_ts_events = 0; | ||
| 159 | mhz_change_callbacks.clear(); | ||
| 160 | |||
| 161 | first = nullptr; | ||
| 162 | ts_first = nullptr; | ||
| 163 | ts_last = nullptr; | ||
| 164 | 97 | ||
| 165 | event_pool = nullptr; | 98 | // The time between CoreTiming being intialized and the first call to Advance() is considered |
| 166 | event_ts_pool = nullptr; | 99 | // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before |
| 167 | allocated_ts_events = 0; | 100 | // executing the first cycle of each slice to prepare the slice length and downcount for |
| 101 | // that slice. | ||
| 102 | is_global_timer_sane = true; | ||
| 168 | 103 | ||
| 169 | advance_callback = nullptr; | 104 | event_fifo_id = 0; |
| 105 | ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback); | ||
| 170 | } | 106 | } |
| 171 | 107 | ||
| 172 | void Shutdown() { | 108 | void Shutdown() { |
| 173 | MoveEvents(); | 109 | MoveEvents(); |
| 174 | ClearPendingEvents(); | 110 | ClearPendingEvents(); |
| 175 | UnregisterAllEvents(); | 111 | UnregisterAllEvents(); |
| 176 | |||
| 177 | while (event_pool) { | ||
| 178 | Event* event = event_pool; | ||
| 179 | event_pool = event->next; | ||
| 180 | delete event; | ||
| 181 | } | ||
| 182 | |||
| 183 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | ||
| 184 | while (event_ts_pool) { | ||
| 185 | Event* event = event_ts_pool; | ||
| 186 | event_ts_pool = event->next; | ||
| 187 | delete event; | ||
| 188 | } | ||
| 189 | } | 112 | } |
| 190 | 113 | ||
| 191 | void AddTicks(u64 ticks) { | 114 | // This should only be called from the CPU thread. If you are calling |
| 192 | down_count -= ticks; | 115 | // it from any other thread, you are doing something evil |
| 193 | if (down_count < 0) { | 116 | u64 GetTicks() { |
| 194 | Advance(); | 117 | u64 ticks = static_cast<u64>(global_timer); |
| 118 | if (!is_global_timer_sane) { | ||
| 119 | ticks += slice_length - downcount; | ||
| 195 | } | 120 | } |
| 121 | return ticks; | ||
| 196 | } | 122 | } |
| 197 | 123 | ||
| 198 | u64 GetTicks() { | 124 | void AddTicks(u64 ticks) { |
| 199 | return (u64)global_timer + g_slice_length - down_count; | 125 | downcount -= ticks; |
| 200 | } | 126 | } |
| 201 | 127 | ||
| 202 | u64 GetIdleTicks() { | 128 | u64 GetIdleTicks() { |
| 203 | return (u64)idled_cycles; | 129 | return static_cast<u64>(idled_cycles); |
| 204 | } | ||
| 205 | |||
| 206 | // This is to be called when outside threads, such as the graphics thread, wants to | ||
| 207 | // schedule things to be executed on the main thread. | ||
| 208 | void ScheduleEvent_Threadsafe(s64 cycles_into_future, int event_type, u64 userdata) { | ||
| 209 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | ||
| 210 | Event* new_event = GetNewTsEvent(); | ||
| 211 | new_event->time = GetTicks() + cycles_into_future; | ||
| 212 | new_event->type = event_type; | ||
| 213 | new_event->next = nullptr; | ||
| 214 | new_event->userdata = userdata; | ||
| 215 | if (!ts_first) | ||
| 216 | ts_first = new_event; | ||
| 217 | if (ts_last) | ||
| 218 | ts_last->next = new_event; | ||
| 219 | ts_last = new_event; | ||
| 220 | |||
| 221 | has_ts_events = true; | ||
| 222 | } | ||
| 223 | |||
| 224 | // Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread | ||
| 225 | // in which case the event will get handled immediately, before returning. | ||
| 226 | void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata) { | ||
| 227 | if (false) // Core::IsCPUThread()) | ||
| 228 | { | ||
| 229 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | ||
| 230 | event_types[event_type].callback(userdata, 0); | ||
| 231 | } else | ||
| 232 | ScheduleEvent_Threadsafe(0, event_type, userdata); | ||
| 233 | } | 130 | } |
| 234 | 131 | ||
| 235 | void ClearPendingEvents() { | 132 | void ClearPendingEvents() { |
| 236 | while (first) { | 133 | event_queue.clear(); |
| 237 | Event* event = first->next; | ||
| 238 | FreeEvent(first); | ||
| 239 | first = event; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | static void AddEventToQueue(Event* new_event) { | ||
| 244 | Event* prev_event = nullptr; | ||
| 245 | Event** next_event = &first; | ||
| 246 | for (;;) { | ||
| 247 | Event*& next = *next_event; | ||
| 248 | if (!next || new_event->time < next->time) { | ||
| 249 | new_event->next = next; | ||
| 250 | next = new_event; | ||
| 251 | break; | ||
| 252 | } | ||
| 253 | prev_event = next; | ||
| 254 | next_event = &prev_event->next; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata) { | ||
| 259 | Event* new_event = GetNewEvent(); | ||
| 260 | new_event->userdata = userdata; | ||
| 261 | new_event->type = event_type; | ||
| 262 | new_event->time = GetTicks() + cycles_into_future; | ||
| 263 | AddEventToQueue(new_event); | ||
| 264 | } | ||
| 265 | |||
| 266 | s64 UnscheduleEvent(int event_type, u64 userdata) { | ||
| 267 | s64 result = 0; | ||
| 268 | if (!first) | ||
| 269 | return result; | ||
| 270 | while (first) { | ||
| 271 | if (first->type == event_type && first->userdata == userdata) { | ||
| 272 | result = first->time - GetTicks(); | ||
| 273 | |||
| 274 | Event* next = first->next; | ||
| 275 | FreeEvent(first); | ||
| 276 | first = next; | ||
| 277 | } else { | ||
| 278 | break; | ||
| 279 | } | ||
| 280 | } | ||
| 281 | if (!first) | ||
| 282 | return result; | ||
| 283 | |||
| 284 | Event* prev_event = first; | ||
| 285 | Event* ptr = prev_event->next; | ||
| 286 | |||
| 287 | while (ptr) { | ||
| 288 | if (ptr->type == event_type && ptr->userdata == userdata) { | ||
| 289 | result = ptr->time - GetTicks(); | ||
| 290 | |||
| 291 | prev_event->next = ptr->next; | ||
| 292 | FreeEvent(ptr); | ||
| 293 | ptr = prev_event->next; | ||
| 294 | } else { | ||
| 295 | prev_event = ptr; | ||
| 296 | ptr = ptr->next; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | return result; | ||
| 301 | } | 134 | } |
| 302 | 135 | ||
| 303 | s64 UnscheduleThreadsafeEvent(int event_type, u64 userdata) { | 136 | void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) { |
| 304 | s64 result = 0; | 137 | ASSERT(event_type != nullptr); |
| 305 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | 138 | s64 timeout = GetTicks() + cycles_into_future; |
| 306 | if (!ts_first) | ||
| 307 | return result; | ||
| 308 | |||
| 309 | while (ts_first) { | ||
| 310 | if (ts_first->type == event_type && ts_first->userdata == userdata) { | ||
| 311 | result = ts_first->time - GetTicks(); | ||
| 312 | |||
| 313 | Event* next = ts_first->next; | ||
| 314 | FreeTsEvent(ts_first); | ||
| 315 | ts_first = next; | ||
| 316 | } else { | ||
| 317 | break; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | 139 | ||
| 321 | if (!ts_first) { | 140 | // If this event needs to be scheduled before the next advance(), force one early |
| 322 | ts_last = nullptr; | 141 | if (!is_global_timer_sane) |
| 323 | return result; | 142 | ForceExceptionCheck(cycles_into_future); |
| 324 | } | ||
| 325 | 143 | ||
| 326 | Event* prev_event = ts_first; | 144 | event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type}); |
| 327 | Event* next = prev_event->next; | 145 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); |
| 328 | while (next) { | ||
| 329 | if (next->type == event_type && next->userdata == userdata) { | ||
| 330 | result = next->time - GetTicks(); | ||
| 331 | |||
| 332 | prev_event->next = next->next; | ||
| 333 | if (next == ts_last) | ||
| 334 | ts_last = prev_event; | ||
| 335 | FreeTsEvent(next); | ||
| 336 | next = prev_event->next; | ||
| 337 | } else { | ||
| 338 | prev_event = next; | ||
| 339 | next = next->next; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | return result; | ||
| 344 | } | 146 | } |
| 345 | 147 | ||
| 346 | // Warning: not included in save state. | 148 | void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata) { |
| 347 | void RegisterAdvanceCallback(AdvanceCallback* callback) { | 149 | ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type}); |
| 348 | advance_callback = callback; | ||
| 349 | } | 150 | } |
| 350 | 151 | ||
| 351 | void RegisterMHzChangeCallback(MHzChangeCallback callback) { | 152 | void UnscheduleEvent(const EventType* event_type, u64 userdata) { |
| 352 | mhz_change_callbacks.push_back(callback); | 153 | auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { |
| 353 | } | 154 | return e.type == event_type && e.userdata == userdata; |
| 354 | 155 | }); | |
| 355 | bool IsScheduled(int event_type) { | ||
| 356 | if (!first) | ||
| 357 | return false; | ||
| 358 | Event* event = first; | ||
| 359 | while (event) { | ||
| 360 | if (event->type == event_type) | ||
| 361 | return true; | ||
| 362 | event = event->next; | ||
| 363 | } | ||
| 364 | return false; | ||
| 365 | } | ||
| 366 | 156 | ||
| 367 | void RemoveEvent(int event_type) { | 157 | // Removing random items breaks the invariant so we have to re-establish it. |
| 368 | if (!first) | 158 | if (itr != event_queue.end()) { |
| 369 | return; | 159 | event_queue.erase(itr, event_queue.end()); |
| 370 | while (first) { | 160 | std::make_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); |
| 371 | if (first->type == event_type) { | ||
| 372 | Event* next = first->next; | ||
| 373 | FreeEvent(first); | ||
| 374 | first = next; | ||
| 375 | } else { | ||
| 376 | break; | ||
| 377 | } | ||
| 378 | } | ||
| 379 | if (!first) | ||
| 380 | return; | ||
| 381 | Event* prev = first; | ||
| 382 | Event* next = prev->next; | ||
| 383 | while (next) { | ||
| 384 | if (next->type == event_type) { | ||
| 385 | prev->next = next->next; | ||
| 386 | FreeEvent(next); | ||
| 387 | next = prev->next; | ||
| 388 | } else { | ||
| 389 | prev = next; | ||
| 390 | next = next->next; | ||
| 391 | } | ||
| 392 | } | 161 | } |
| 393 | } | 162 | } |
| 394 | 163 | ||
| 395 | void RemoveThreadsafeEvent(int event_type) { | 164 | void RemoveEvent(const EventType* event_type) { |
| 396 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | 165 | auto itr = std::remove_if(event_queue.begin(), event_queue.end(), |
| 397 | if (!ts_first) | 166 | [&](const Event& e) { return e.type == event_type; }); |
| 398 | return; | ||
| 399 | |||
| 400 | while (ts_first) { | ||
| 401 | if (ts_first->type == event_type) { | ||
| 402 | Event* next = ts_first->next; | ||
| 403 | FreeTsEvent(ts_first); | ||
| 404 | ts_first = next; | ||
| 405 | } else { | ||
| 406 | break; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | if (!ts_first) { | ||
| 411 | ts_last = nullptr; | ||
| 412 | return; | ||
| 413 | } | ||
| 414 | 167 | ||
| 415 | Event* prev = ts_first; | 168 | // Removing random items breaks the invariant so we have to re-establish it. |
| 416 | Event* next = prev->next; | 169 | if (itr != event_queue.end()) { |
| 417 | while (next) { | 170 | event_queue.erase(itr, event_queue.end()); |
| 418 | if (next->type == event_type) { | 171 | std::make_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); |
| 419 | prev->next = next->next; | ||
| 420 | if (next == ts_last) | ||
| 421 | ts_last = prev; | ||
| 422 | FreeTsEvent(next); | ||
| 423 | next = prev->next; | ||
| 424 | } else { | ||
| 425 | prev = next; | ||
| 426 | next = next->next; | ||
| 427 | } | ||
| 428 | } | 172 | } |
| 429 | } | 173 | } |
| 430 | 174 | ||
| 431 | void RemoveAllEvents(int event_type) { | 175 | void RemoveNormalAndThreadsafeEvent(const EventType* event_type) { |
| 432 | RemoveThreadsafeEvent(event_type); | 176 | MoveEvents(); |
| 433 | RemoveEvent(event_type); | 177 | RemoveEvent(event_type); |
| 434 | } | 178 | } |
| 435 | 179 | ||
| 436 | // This raise only the events required while the fifo is processing data | 180 | void ForceExceptionCheck(s64 cycles) { |
| 437 | void ProcessFifoWaitEvents() { | 181 | cycles = std::max<s64>(0, cycles); |
| 438 | while (first) { | 182 | if (downcount > cycles) { |
| 439 | if (first->time <= (s64)GetTicks()) { | 183 | // downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int |
| 440 | Event* evt = first; | 184 | // here. Account for cycles already executed by adjusting the g.slice_length |
| 441 | first = first->next; | 185 | slice_length -= downcount - static_cast<int>(cycles); |
| 442 | event_types[evt->type].callback(evt->userdata, (int)(GetTicks() - evt->time)); | 186 | downcount = static_cast<int>(cycles); |
| 443 | FreeEvent(evt); | ||
| 444 | } else { | ||
| 445 | break; | ||
| 446 | } | ||
| 447 | } | 187 | } |
| 448 | } | 188 | } |
| 449 | 189 | ||
| 450 | void MoveEvents() { | 190 | void MoveEvents() { |
| 451 | has_ts_events = false; | 191 | for (Event ev; ts_queue.Pop(ev);) { |
| 452 | 192 | ev.fifo_order = event_fifo_id++; | |
| 453 | std::lock_guard<std::recursive_mutex> lock(external_event_section); | 193 | event_queue.emplace_back(std::move(ev)); |
| 454 | // Move events from async queue into main queue | 194 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); |
| 455 | while (ts_first) { | ||
| 456 | Event* next = ts_first->next; | ||
| 457 | AddEventToQueue(ts_first); | ||
| 458 | ts_first = next; | ||
| 459 | } | ||
| 460 | ts_last = nullptr; | ||
| 461 | |||
| 462 | // Move free events to threadsafe pool | ||
| 463 | while (allocated_ts_events > 0 && event_pool) { | ||
| 464 | Event* event = event_pool; | ||
| 465 | event_pool = event->next; | ||
| 466 | event->next = event_ts_pool; | ||
| 467 | event_ts_pool = event; | ||
| 468 | allocated_ts_events--; | ||
| 469 | } | 195 | } |
| 470 | } | 196 | } |
| 471 | 197 | ||
| 472 | void ForceCheck() { | ||
| 473 | s64 cycles_executed = g_slice_length - down_count; | ||
| 474 | global_timer += cycles_executed; | ||
| 475 | // This will cause us to check for new events immediately. | ||
| 476 | down_count = 0; | ||
| 477 | // But let's not eat a bunch more time in Advance() because of this. | ||
| 478 | g_slice_length = 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | void Advance() { | 198 | void Advance() { |
| 482 | s64 cycles_executed = g_slice_length - down_count; | 199 | MoveEvents(); |
| 200 | |||
| 201 | int cycles_executed = slice_length - downcount; | ||
| 483 | global_timer += cycles_executed; | 202 | global_timer += cycles_executed; |
| 484 | down_count = g_slice_length; | 203 | slice_length = MAX_SLICE_LENGTH; |
| 485 | |||
| 486 | if (has_ts_events) | ||
| 487 | MoveEvents(); | ||
| 488 | ProcessFifoWaitEvents(); | ||
| 489 | |||
| 490 | if (!first) { | ||
| 491 | if (g_slice_length < 10000) { | ||
| 492 | g_slice_length += 10000; | ||
| 493 | down_count += g_slice_length; | ||
| 494 | } | ||
| 495 | } else { | ||
| 496 | // Note that events can eat cycles as well. | ||
| 497 | int target = (int)(first->time - global_timer); | ||
| 498 | if (target > MAX_SLICE_LENGTH) | ||
| 499 | target = MAX_SLICE_LENGTH; | ||
| 500 | |||
| 501 | const int diff = target - g_slice_length; | ||
| 502 | g_slice_length += diff; | ||
| 503 | down_count += diff; | ||
| 504 | } | ||
| 505 | if (advance_callback) | ||
| 506 | advance_callback(static_cast<int>(cycles_executed)); | ||
| 507 | } | ||
| 508 | 204 | ||
| 509 | void LogPendingEvents() { | 205 | is_global_timer_sane = true; |
| 510 | Event* event = first; | 206 | |
| 511 | while (event) { | 207 | while (!event_queue.empty() && event_queue.front().time <= global_timer) { |
| 512 | // LOG_TRACE(Core_Timing, "PENDING: Now: %lld Pending: %lld Type: %d", globalTimer, | 208 | Event evt = std::move(event_queue.front()); |
| 513 | // next->time, next->type); | 209 | std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); |
| 514 | event = event->next; | 210 | event_queue.pop_back(); |
| 211 | evt.type->callback(evt.userdata, global_timer - evt.time); | ||
| 515 | } | 212 | } |
| 516 | } | ||
| 517 | 213 | ||
| 518 | void Idle(int max_idle) { | 214 | is_global_timer_sane = false; |
| 519 | s64 cycles_down = down_count; | 215 | |
| 520 | if (max_idle != 0 && cycles_down > max_idle) | 216 | // Still events left (scheduled in the future) |
| 521 | cycles_down = max_idle; | 217 | if (!event_queue.empty()) { |
| 522 | 218 | slice_length = static_cast<int>( | |
| 523 | if (first && cycles_down > 0) { | 219 | std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH)); |
| 524 | s64 cycles_executed = g_slice_length - down_count; | ||
| 525 | s64 cycles_next_event = first->time - global_timer; | ||
| 526 | |||
| 527 | if (cycles_next_event < cycles_executed + cycles_down) { | ||
| 528 | cycles_down = cycles_next_event - cycles_executed; | ||
| 529 | // Now, now... no time machines, please. | ||
| 530 | if (cycles_down < 0) | ||
| 531 | cycles_down = 0; | ||
| 532 | } | ||
| 533 | } | 220 | } |
| 534 | 221 | ||
| 535 | LOG_TRACE(Core_Timing, "Idle for %" PRId64 " cycles! (%f ms)", cycles_down, | 222 | downcount = slice_length; |
| 536 | cycles_down / (float)(g_clock_rate_arm11 * 0.001f)); | 223 | } |
| 537 | 224 | ||
| 538 | idled_cycles += cycles_down; | 225 | void Idle() { |
| 539 | down_count -= cycles_down; | 226 | idled_cycles += downcount; |
| 540 | if (down_count == 0) | 227 | downcount = 0; |
| 541 | down_count = -1; | ||
| 542 | } | 228 | } |
| 543 | 229 | ||
| 544 | std::string GetScheduledEventsSummary() { | 230 | u64 GetGlobalTimeUs() { |
| 545 | Event* event = first; | 231 | return GetTicks() * 1000000 / BASE_CLOCK_RATE; |
| 546 | std::string text = "Scheduled events\n"; | 232 | } |
| 547 | text.reserve(1000); | 233 | |
| 548 | while (event) { | 234 | int GetDowncount() { |
| 549 | unsigned int t = event->type; | 235 | return downcount; |
| 550 | if (t >= event_types.size()) | ||
| 551 | LOG_ERROR(Core_Timing, "Invalid event type"); // %i", t); | ||
| 552 | const char* name = event_types[event->type].name; | ||
| 553 | if (!name) | ||
| 554 | name = "[unknown]"; | ||
| 555 | text += Common::StringFromFormat("%s : %i %08x%08x\n", name, (int)event->time, | ||
| 556 | (u32)(event->userdata >> 32), (u32)(event->userdata)); | ||
| 557 | event = event->next; | ||
| 558 | } | ||
| 559 | return text; | ||
| 560 | } | 236 | } |
| 561 | 237 | ||
| 562 | } // namespace | 238 | } // namespace CoreTiming |
diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 92c811af6..46ddcd18b 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h | |||
| @@ -1,144 +1,191 @@ | |||
| 1 | // Copyright (c) 2012- PPSSPP Project / Dolphin Project. | 1 | // Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project |
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2+ |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | /** | ||
| 8 | * This is a system to schedule events into the emulated machine's future. Time is measured | ||
| 9 | * in main CPU clock cycles. | ||
| 10 | * | ||
| 11 | * To schedule an event, you first have to register its type. This is where you pass in the | ||
| 12 | * callback. You then schedule events using the type id you get back. | ||
| 13 | * | ||
| 14 | * The int cyclesLate that the callbacks get is how many cycles late it was. | ||
| 15 | * So to schedule a new event on a regular basis: | ||
| 16 | * inside callback: | ||
| 17 | * ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever") | ||
| 18 | */ | ||
| 19 | |||
| 7 | #include <functional> | 20 | #include <functional> |
| 21 | #include <limits> | ||
| 8 | #include <string> | 22 | #include <string> |
| 9 | #include "common/common_types.h" | 23 | #include "common/common_types.h" |
| 24 | #include "common/logging/log.h" | ||
| 10 | 25 | ||
| 11 | // This is a system to schedule events into the emulated machine's future. Time is measured | 26 | // The timing we get from the assembly is 268,111,855.956 Hz |
| 12 | // in main CPU clock cycles. | 27 | // It is possible that this number isn't just an integer because the compiler could have |
| 13 | 28 | // optimized the multiplication by a multiply-by-constant division. | |
| 14 | // To schedule an event, you first have to register its type. This is where you pass in the | 29 | // Rounding to the nearest integer should be fine |
| 15 | // callback. You then schedule events using the type id you get back. | 30 | constexpr u64 BASE_CLOCK_RATE = 383778816; // Switch clock speed is 384MHz docked |
| 16 | 31 | constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE; | |
| 17 | // See HW/SystemTimers.cpp for the main part of Dolphin's usage of this scheduler. | ||
| 18 | |||
| 19 | // The int cycles_late that the callbacks get is how many cycles late it was. | ||
| 20 | // So to schedule a new event on a regular basis: | ||
| 21 | // inside callback: | ||
| 22 | // ScheduleEvent(periodInCycles - cycles_late, callback, "whatever") | ||
| 23 | |||
| 24 | constexpr int BASE_CLOCK_RATE = 383778816; // Switch clock speed is 384MHz docked | ||
| 25 | extern int g_clock_rate_arm11; | ||
| 26 | 32 | ||
| 27 | inline s64 msToCycles(int ms) { | 33 | inline s64 msToCycles(int ms) { |
| 28 | return (s64)g_clock_rate_arm11 / 1000 * ms; | 34 | // since ms is int there is no way to overflow |
| 35 | return BASE_CLOCK_RATE * static_cast<s64>(ms) / 1000; | ||
| 29 | } | 36 | } |
| 30 | 37 | ||
| 31 | inline s64 msToCycles(float ms) { | 38 | inline s64 msToCycles(float ms) { |
| 32 | return (s64)(g_clock_rate_arm11 * ms * (0.001f)); | 39 | return static_cast<s64>(BASE_CLOCK_RATE * (0.001f) * ms); |
| 33 | } | 40 | } |
| 34 | 41 | ||
| 35 | inline s64 msToCycles(double ms) { | 42 | inline s64 msToCycles(double ms) { |
| 36 | return (s64)(g_clock_rate_arm11 * ms * (0.001)); | 43 | return static_cast<s64>(BASE_CLOCK_RATE * (0.001) * ms); |
| 37 | } | 44 | } |
| 38 | 45 | ||
| 39 | inline s64 usToCycles(float us) { | 46 | inline s64 usToCycles(float us) { |
| 40 | return (s64)(g_clock_rate_arm11 * us * (0.000001f)); | 47 | return static_cast<s64>(BASE_CLOCK_RATE * (0.000001f) * us); |
| 41 | } | 48 | } |
| 42 | 49 | ||
| 43 | inline s64 usToCycles(int us) { | 50 | inline s64 usToCycles(int us) { |
| 44 | return (g_clock_rate_arm11 / 1000000 * (s64)us); | 51 | return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000); |
| 45 | } | 52 | } |
| 46 | 53 | ||
| 47 | inline s64 usToCycles(s64 us) { | 54 | inline s64 usToCycles(s64 us) { |
| 48 | return (g_clock_rate_arm11 / 1000000 * us); | 55 | if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { |
| 56 | LOG_ERROR(Core_Timing, "Integer overflow, use max value"); | ||
| 57 | return std::numeric_limits<s64>::max(); | ||
| 58 | } | ||
| 59 | if (us > MAX_VALUE_TO_MULTIPLY) { | ||
| 60 | LOG_DEBUG(Core_Timing, "Time very big, do rounding"); | ||
| 61 | return BASE_CLOCK_RATE * (us / 1000000); | ||
| 62 | } | ||
| 63 | return (BASE_CLOCK_RATE * us) / 1000000; | ||
| 49 | } | 64 | } |
| 50 | 65 | ||
| 51 | inline s64 usToCycles(u64 us) { | 66 | inline s64 usToCycles(u64 us) { |
| 52 | return (s64)(g_clock_rate_arm11 / 1000000 * us); | 67 | if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) { |
| 68 | LOG_ERROR(Core_Timing, "Integer overflow, use max value"); | ||
| 69 | return std::numeric_limits<s64>::max(); | ||
| 70 | } | ||
| 71 | if (us > MAX_VALUE_TO_MULTIPLY) { | ||
| 72 | LOG_DEBUG(Core_Timing, "Time very big, do rounding"); | ||
| 73 | return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000); | ||
| 74 | } | ||
| 75 | return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000; | ||
| 76 | } | ||
| 77 | |||
| 78 | inline s64 nsToCycles(float ns) { | ||
| 79 | return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns); | ||
| 80 | } | ||
| 81 | |||
| 82 | inline s64 nsToCycles(int ns) { | ||
| 83 | return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000; | ||
| 84 | } | ||
| 85 | |||
| 86 | inline s64 nsToCycles(s64 ns) { | ||
| 87 | if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { | ||
| 88 | LOG_ERROR(Core_Timing, "Integer overflow, use max value"); | ||
| 89 | return std::numeric_limits<s64>::max(); | ||
| 90 | } | ||
| 91 | if (ns > MAX_VALUE_TO_MULTIPLY) { | ||
| 92 | LOG_DEBUG(Core_Timing, "Time very big, do rounding"); | ||
| 93 | return BASE_CLOCK_RATE * (ns / 1000000000); | ||
| 94 | } | ||
| 95 | return (BASE_CLOCK_RATE * ns) / 1000000000; | ||
| 96 | } | ||
| 97 | |||
| 98 | inline s64 nsToCycles(u64 ns) { | ||
| 99 | if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { | ||
| 100 | LOG_ERROR(Core_Timing, "Integer overflow, use max value"); | ||
| 101 | return std::numeric_limits<s64>::max(); | ||
| 102 | } | ||
| 103 | if (ns > MAX_VALUE_TO_MULTIPLY) { | ||
| 104 | LOG_DEBUG(Core_Timing, "Time very big, do rounding"); | ||
| 105 | return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000); | ||
| 106 | } | ||
| 107 | return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000; | ||
| 108 | } | ||
| 109 | |||
| 110 | inline u64 cyclesToNs(s64 cycles) { | ||
| 111 | return cycles * 1000000000 / BASE_CLOCK_RATE; | ||
| 53 | } | 112 | } |
| 54 | 113 | ||
| 55 | inline s64 cyclesToUs(s64 cycles) { | 114 | inline s64 cyclesToUs(s64 cycles) { |
| 56 | return cycles / (g_clock_rate_arm11 / 1000000); | 115 | return cycles * 1000000 / BASE_CLOCK_RATE; |
| 57 | } | 116 | } |
| 58 | 117 | ||
| 59 | inline u64 cyclesToMs(s64 cycles) { | 118 | inline u64 cyclesToMs(s64 cycles) { |
| 60 | return cycles / (g_clock_rate_arm11 / 1000); | 119 | return cycles * 1000 / BASE_CLOCK_RATE; |
| 61 | } | 120 | } |
| 62 | 121 | ||
| 63 | namespace CoreTiming { | 122 | namespace CoreTiming { |
| 123 | |||
| 124 | /** | ||
| 125 | * CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is | ||
| 126 | * required to end slice -1 and start slice 0 before the first cycle of code is executed. | ||
| 127 | */ | ||
| 64 | void Init(); | 128 | void Init(); |
| 65 | void Shutdown(); | 129 | void Shutdown(); |
| 66 | 130 | ||
| 67 | typedef void (*MHzChangeCallback)(); | ||
| 68 | typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback; | 131 | typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback; |
| 69 | 132 | ||
| 70 | /** | 133 | /** |
| 71 | * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time) | 134 | * This should only be called from the emu thread, if you are calling it any other thread, you are |
| 72 | * @param ticks Number of ticks to advance the CPU core | 135 | * doing something evil |
| 73 | */ | 136 | */ |
| 74 | void AddTicks(u64 ticks); | ||
| 75 | |||
| 76 | u64 GetTicks(); | 137 | u64 GetTicks(); |
| 77 | u64 GetIdleTicks(); | 138 | u64 GetIdleTicks(); |
| 78 | u64 GetGlobalTimeUs(); | 139 | void AddTicks(u64 ticks); |
| 140 | |||
| 141 | struct EventType; | ||
| 79 | 142 | ||
| 80 | /** | 143 | /** |
| 81 | * Registers an event type with the specified name and callback | 144 | * Returns the event_type identifier. if name is not unique, it will assert. |
| 82 | * @param name Name of the event type | ||
| 83 | * @param callback Function that will execute when this event fires | ||
| 84 | * @returns An identifier for the event type that was registered | ||
| 85 | */ | 145 | */ |
| 86 | int RegisterEvent(const char* name, TimedCallback callback); | 146 | EventType* RegisterEvent(const std::string& name, TimedCallback callback); |
| 87 | /// For save states. | ||
| 88 | void RestoreRegisterEvent(int event_type, const char* name, TimedCallback callback); | ||
| 89 | void UnregisterAllEvents(); | 147 | void UnregisterAllEvents(); |
| 90 | 148 | ||
| 91 | /// userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from disk, | ||
| 92 | /// when we implement state saves. | ||
| 93 | /** | 149 | /** |
| 94 | * Schedules an event to run after the specified number of cycles, | 150 | * After the first Advance, the slice lengths and the downcount will be reduced whenever an event |
| 95 | * with an optional parameter to be passed to the callback handler. | 151 | * is scheduled earlier than the current values. |
| 96 | * This must be run ONLY from within the cpu thread. | 152 | * Scheduling from a callback will not update the downcount until the Advance() completes. |
| 97 | * @param cycles_into_future The number of cycles after which this event will be fired | ||
| 98 | * @param event_type The event type to fire, as returned from RegisterEvent | ||
| 99 | * @param userdata Optional parameter to pass to the callback when fired | ||
| 100 | */ | 153 | */ |
| 101 | void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata = 0); | 154 | void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata = 0); |
| 102 | |||
| 103 | void ScheduleEvent_Threadsafe(s64 cycles_into_future, int event_type, u64 userdata = 0); | ||
| 104 | void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata = 0); | ||
| 105 | 155 | ||
| 106 | /** | 156 | /** |
| 107 | * Unschedules an event with the specified type and userdata | 157 | * This is to be called when outside of hle threads, such as the graphics thread, wants to |
| 108 | * @param event_type The type of event to unschedule, as returned from RegisterEvent | 158 | * schedule things to be executed on the main thread. |
| 109 | * @param userdata The userdata that identifies this event, as passed to ScheduleEvent | 159 | * Not that this doesn't change slice_length and thus events scheduled by this might be called |
| 110 | * @returns The remaining ticks until the next invocation of the event callback | 160 | * with a delay of up to MAX_SLICE_LENGTH |
| 111 | */ | 161 | */ |
| 112 | s64 UnscheduleEvent(int event_type, u64 userdata); | 162 | void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata); |
| 163 | |||
| 164 | void UnscheduleEvent(const EventType* event_type, u64 userdata); | ||
| 113 | 165 | ||
| 114 | s64 UnscheduleThreadsafeEvent(int event_type, u64 userdata); | 166 | /// We only permit one event of each type in the queue at a time. |
| 167 | void RemoveEvent(const EventType* event_type); | ||
| 168 | void RemoveNormalAndThreadsafeEvent(const EventType* event_type); | ||
| 115 | 169 | ||
| 116 | void RemoveEvent(int event_type); | 170 | /** Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends |
| 117 | void RemoveThreadsafeEvent(int event_type); | 171 | * the previous timing slice and begins the next one, you must Advance from the previous |
| 118 | void RemoveAllEvents(int event_type); | 172 | * slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an |
| 119 | bool IsScheduled(int event_type); | 173 | * Advance() is required to initialize the slice length before the first cycle of emulated |
| 120 | /// Runs any pending events and updates downcount for the next slice of cycles | 174 | * instructions is executed. |
| 175 | */ | ||
| 121 | void Advance(); | 176 | void Advance(); |
| 122 | void MoveEvents(); | 177 | void MoveEvents(); |
| 123 | void ProcessFifoWaitEvents(); | ||
| 124 | void ForceCheck(); | ||
| 125 | 178 | ||
| 126 | /// Pretend that the main CPU has executed enough cycles to reach the next event. | 179 | /// Pretend that the main CPU has executed enough cycles to reach the next event. |
| 127 | void Idle(int maxIdle = 0); | 180 | void Idle(); |
| 128 | 181 | ||
| 129 | /// Clear all pending events. This should ONLY be done on exit or state load. | 182 | /// Clear all pending events. This should ONLY be done on exit. |
| 130 | void ClearPendingEvents(); | 183 | void ClearPendingEvents(); |
| 131 | 184 | ||
| 132 | void LogPendingEvents(); | 185 | void ForceExceptionCheck(s64 cycles); |
| 133 | |||
| 134 | /// Warning: not included in save states. | ||
| 135 | void RegisterAdvanceCallback(void (*callback)(int cycles_executed)); | ||
| 136 | void RegisterMHzChangeCallback(MHzChangeCallback callback); | ||
| 137 | 186 | ||
| 138 | std::string GetScheduledEventsSummary(); | 187 | u64 GetGlobalTimeUs(); |
| 139 | 188 | ||
| 140 | void SetClockFrequencyMHz(int cpu_mhz); | 189 | int GetDowncount(); |
| 141 | int GetClockFrequencyMHz(); | ||
| 142 | extern int g_slice_length; | ||
| 143 | 190 | ||
| 144 | } // namespace | 191 | } // namespace CoreTiming |
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 9132d1d77..f9d821a80 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -26,7 +26,7 @@ | |||
| 26 | namespace Kernel { | 26 | namespace Kernel { |
| 27 | 27 | ||
| 28 | /// Event type for the thread wake up event | 28 | /// Event type for the thread wake up event |
| 29 | static int ThreadWakeupEventType; | 29 | static CoreTiming::EventType* ThreadWakeupEventType = nullptr; |
| 30 | 30 | ||
| 31 | bool Thread::ShouldWait(Thread* thread) const { | 31 | bool Thread::ShouldWait(Thread* thread) const { |
| 32 | return status != THREADSTATUS_DEAD; | 32 | return status != THREADSTATUS_DEAD; |
| @@ -265,8 +265,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { | |||
| 265 | if (nanoseconds == -1) | 265 | if (nanoseconds == -1) |
| 266 | return; | 266 | return; |
| 267 | 267 | ||
| 268 | u64 microseconds = nanoseconds / 1000; | 268 | CoreTiming::ScheduleEvent(nsToCycles(nanoseconds), ThreadWakeupEventType, callback_handle); |
| 269 | CoreTiming::ScheduleEvent(usToCycles(microseconds), ThreadWakeupEventType, callback_handle); | ||
| 270 | } | 269 | } |
| 271 | 270 | ||
| 272 | void Thread::ResumeFromWait() { | 271 | void Thread::ResumeFromWait() { |
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index d7ec93672..a93a6c87a 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp | |||
| @@ -14,7 +14,7 @@ | |||
| 14 | namespace Kernel { | 14 | namespace Kernel { |
| 15 | 15 | ||
| 16 | /// The event type of the generic timer callback event | 16 | /// The event type of the generic timer callback event |
| 17 | static int timer_callback_event_type; | 17 | static CoreTiming::EventType* timer_callback_event_type = nullptr; |
| 18 | // TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future, allowing | 18 | // TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future, allowing |
| 19 | // us to simply use a pool index or similar. | 19 | // us to simply use a pool index or similar. |
| 20 | static Kernel::HandleTable timer_callback_handle_table; | 20 | static Kernel::HandleTable timer_callback_handle_table; |
| @@ -57,9 +57,7 @@ void Timer::Set(s64 initial, s64 interval) { | |||
| 57 | // Immediately invoke the callback | 57 | // Immediately invoke the callback |
| 58 | Signal(0); | 58 | Signal(0); |
| 59 | } else { | 59 | } else { |
| 60 | u64 initial_microseconds = initial / 1000; | 60 | CoreTiming::ScheduleEvent(nsToCycles(initial), timer_callback_event_type, callback_handle); |
| 61 | CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, | ||
| 62 | callback_handle); | ||
| 63 | } | 61 | } |
| 64 | } | 62 | } |
| 65 | 63 | ||
| @@ -88,8 +86,7 @@ void Timer::Signal(int cycles_late) { | |||
| 88 | 86 | ||
| 89 | if (interval_delay != 0) { | 87 | if (interval_delay != 0) { |
| 90 | // Reschedule the timer with the interval delay | 88 | // Reschedule the timer with the interval delay |
| 91 | u64 interval_microseconds = interval_delay / 1000; | 89 | CoreTiming::ScheduleEvent(nsToCycles(interval_delay) - cycles_late, |
| 92 | CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, | ||
| 93 | timer_callback_event_type, callback_handle); | 90 | timer_callback_event_type, callback_handle); |
| 94 | } | 91 | } |
| 95 | } | 92 | } |
diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 833dc5ec9..9ce8af961 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp | |||
| @@ -14,7 +14,7 @@ namespace SharedPage { | |||
| 14 | 14 | ||
| 15 | SharedPageDef shared_page; | 15 | SharedPageDef shared_page; |
| 16 | 16 | ||
| 17 | static int update_time_event; | 17 | static CoreTiming::EventType* update_time_event; |
| 18 | 18 | ||
| 19 | /// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond. | 19 | /// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond. |
| 20 | static u64 GetSystemTime() { | 20 | static u64 GetSystemTime() { |
| @@ -56,7 +56,7 @@ static void UpdateTimeCallback(u64 userdata, int cycles_late) { | |||
| 56 | 56 | ||
| 57 | date_time.date_time = GetSystemTime(); | 57 | date_time.date_time = GetSystemTime(); |
| 58 | date_time.update_tick = CoreTiming::GetTicks(); | 58 | date_time.update_tick = CoreTiming::GetTicks(); |
| 59 | date_time.tick_to_second_coefficient = g_clock_rate_arm11; | 59 | date_time.tick_to_second_coefficient = BASE_CLOCK_RATE; |
| 60 | date_time.tick_offset = 0; | 60 | date_time.tick_offset = 0; |
| 61 | 61 | ||
| 62 | ++shared_page.date_time_counter; | 62 | ++shared_page.date_time_counter; |
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 4826d9c79..47ab14ae9 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp | |||
| @@ -31,7 +31,7 @@ Regs g_regs; | |||
| 31 | /// 268MHz CPU clocks / 60Hz frames per second | 31 | /// 268MHz CPU clocks / 60Hz frames per second |
| 32 | const u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); | 32 | const u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); |
| 33 | /// Event id for CoreTiming | 33 | /// Event id for CoreTiming |
| 34 | static int vblank_event; | 34 | static CoreTiming::EventType* vblank_event; |
| 35 | 35 | ||
| 36 | template <typename T> | 36 | template <typename T> |
| 37 | inline void Read(T& var, const u32 raw_addr) { | 37 | inline void Read(T& var, const u32 raw_addr) { |
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index b5a8d9c66..1b8fb2a9f 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | set(SRCS | 1 | set(SRCS |
| 2 | common/param_package.cpp | 2 | common/param_package.cpp |
| 3 | core/arm/arm_test_common.cpp | 3 | core/arm/arm_test_common.cpp |
| 4 | core/core_timing.cpp | ||
| 4 | core/file_sys/path_parser.cpp | 5 | core/file_sys/path_parser.cpp |
| 5 | core/memory/memory.cpp | 6 | core/memory/memory.cpp |
| 6 | glad.cpp | 7 | glad.cpp |
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp new file mode 100644 index 000000000..fcaa30990 --- /dev/null +++ b/src/tests/core/core_timing.cpp | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | // Copyright 2016 Dolphin Emulator Project / 2017 Dolphin Emulator Project | ||
| 2 | // Licensed under GPLv2+ | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <catch.hpp> | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <bitset> | ||
| 9 | #include <string> | ||
| 10 | #include "common/file_util.h" | ||
| 11 | #include "core/core.h" | ||
| 12 | #include "core/core_timing.h" | ||
| 13 | |||
| 14 | // Numbers are chosen randomly to make sure the correct one is given. | ||
| 15 | static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; | ||
| 16 | static constexpr int MAX_SLICE_LENGTH = 20000; // Copied from CoreTiming internals | ||
| 17 | |||
| 18 | static std::bitset<CB_IDS.size()> callbacks_ran_flags; | ||
| 19 | static u64 expected_callback = 0; | ||
| 20 | static s64 lateness = 0; | ||
| 21 | |||
| 22 | template <unsigned int IDX> | ||
| 23 | void CallbackTemplate(u64 userdata, s64 cycles_late) { | ||
| 24 | static_assert(IDX < CB_IDS.size(), "IDX out of range"); | ||
| 25 | callbacks_ran_flags.set(IDX); | ||
| 26 | REQUIRE(CB_IDS[IDX] == userdata); | ||
| 27 | REQUIRE(CB_IDS[IDX] == expected_callback); | ||
| 28 | REQUIRE(lateness == cycles_late); | ||
| 29 | } | ||
| 30 | |||
| 31 | class ScopeInit final { | ||
| 32 | public: | ||
| 33 | ScopeInit() { | ||
| 34 | CoreTiming::Init(); | ||
| 35 | } | ||
| 36 | ~ScopeInit() { | ||
| 37 | CoreTiming::Shutdown(); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | |||
| 41 | static void AdvanceAndCheck(u32 idx, int downcount, int expected_lateness = 0, | ||
| 42 | int cpu_downcount = 0) { | ||
| 43 | callbacks_ran_flags = 0; | ||
| 44 | expected_callback = CB_IDS[idx]; | ||
| 45 | lateness = expected_lateness; | ||
| 46 | |||
| 47 | CoreTiming::AddTicks(CoreTiming::GetDowncount() - | ||
| 48 | cpu_downcount); // Pretend we executed X cycles of instructions. | ||
| 49 | CoreTiming::Advance(); | ||
| 50 | |||
| 51 | REQUIRE(decltype(callbacks_ran_flags)().set(idx) == callbacks_ran_flags); | ||
| 52 | REQUIRE(downcount == CoreTiming::GetDowncount()); | ||
| 53 | } | ||
| 54 | |||
| 55 | TEST_CASE("CoreTiming[BasicOrder]", "[core]") { | ||
| 56 | ScopeInit guard; | ||
| 57 | |||
| 58 | CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>); | ||
| 59 | CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>); | ||
| 60 | CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>); | ||
| 61 | CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>); | ||
| 62 | CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>); | ||
| 63 | |||
| 64 | // Enter slice 0 | ||
| 65 | CoreTiming::Advance(); | ||
| 66 | |||
| 67 | // D -> B -> C -> A -> E | ||
| 68 | CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]); | ||
| 69 | REQUIRE(1000 == CoreTiming::GetDowncount()); | ||
| 70 | CoreTiming::ScheduleEvent(500, cb_b, CB_IDS[1]); | ||
| 71 | REQUIRE(500 == CoreTiming::GetDowncount()); | ||
| 72 | CoreTiming::ScheduleEvent(800, cb_c, CB_IDS[2]); | ||
| 73 | REQUIRE(500 == CoreTiming::GetDowncount()); | ||
| 74 | CoreTiming::ScheduleEvent(100, cb_d, CB_IDS[3]); | ||
| 75 | REQUIRE(100 == CoreTiming::GetDowncount()); | ||
| 76 | CoreTiming::ScheduleEvent(1200, cb_e, CB_IDS[4]); | ||
| 77 | REQUIRE(100 == CoreTiming::GetDowncount()); | ||
| 78 | |||
| 79 | AdvanceAndCheck(3, 400); | ||
| 80 | AdvanceAndCheck(1, 300); | ||
| 81 | AdvanceAndCheck(2, 200); | ||
| 82 | AdvanceAndCheck(0, 200); | ||
| 83 | AdvanceAndCheck(4, MAX_SLICE_LENGTH); | ||
| 84 | } | ||
| 85 | |||
| 86 | TEST_CASE("CoreTiming[Threadsave]", "[core]") { | ||
| 87 | ScopeInit guard; | ||
| 88 | |||
| 89 | CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>); | ||
| 90 | CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>); | ||
| 91 | CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>); | ||
| 92 | CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>); | ||
| 93 | CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>); | ||
| 94 | |||
| 95 | // Enter slice 0 | ||
| 96 | CoreTiming::Advance(); | ||
| 97 | |||
| 98 | // D -> B -> C -> A -> E | ||
| 99 | CoreTiming::ScheduleEventThreadsafe(1000, cb_a, CB_IDS[0]); | ||
| 100 | // Manually force since ScheduleEventThreadsafe doesn't call it | ||
| 101 | CoreTiming::ForceExceptionCheck(1000); | ||
| 102 | REQUIRE(1000 == CoreTiming::GetDowncount()); | ||
| 103 | CoreTiming::ScheduleEventThreadsafe(500, cb_b, CB_IDS[1]); | ||
| 104 | // Manually force since ScheduleEventThreadsafe doesn't call it | ||
| 105 | CoreTiming::ForceExceptionCheck(500); | ||
| 106 | REQUIRE(500 == CoreTiming::GetDowncount()); | ||
| 107 | CoreTiming::ScheduleEventThreadsafe(800, cb_c, CB_IDS[2]); | ||
| 108 | // Manually force since ScheduleEventThreadsafe doesn't call it | ||
| 109 | CoreTiming::ForceExceptionCheck(800); | ||
| 110 | REQUIRE(500 == CoreTiming::GetDowncount()); | ||
| 111 | CoreTiming::ScheduleEventThreadsafe(100, cb_d, CB_IDS[3]); | ||
| 112 | // Manually force since ScheduleEventThreadsafe doesn't call it | ||
| 113 | CoreTiming::ForceExceptionCheck(100); | ||
| 114 | REQUIRE(100 == CoreTiming::GetDowncount()); | ||
| 115 | CoreTiming::ScheduleEventThreadsafe(1200, cb_e, CB_IDS[4]); | ||
| 116 | // Manually force since ScheduleEventThreadsafe doesn't call it | ||
| 117 | CoreTiming::ForceExceptionCheck(1200); | ||
| 118 | REQUIRE(100 == CoreTiming::GetDowncount()); | ||
| 119 | |||
| 120 | AdvanceAndCheck(3, 400); | ||
| 121 | AdvanceAndCheck(1, 300); | ||
| 122 | AdvanceAndCheck(2, 200); | ||
| 123 | AdvanceAndCheck(0, 200); | ||
| 124 | AdvanceAndCheck(4, MAX_SLICE_LENGTH); | ||
| 125 | } | ||
| 126 | |||
| 127 | namespace SharedSlotTest { | ||
| 128 | static unsigned int counter = 0; | ||
| 129 | |||
| 130 | template <unsigned int ID> | ||
| 131 | void FifoCallback(u64 userdata, s64 cycles_late) { | ||
| 132 | static_assert(ID < CB_IDS.size(), "ID out of range"); | ||
| 133 | callbacks_ran_flags.set(ID); | ||
| 134 | REQUIRE(CB_IDS[ID] == userdata); | ||
| 135 | REQUIRE(ID == counter); | ||
| 136 | REQUIRE(lateness == cycles_late); | ||
| 137 | ++counter; | ||
| 138 | } | ||
| 139 | } // namespace SharedSlotTest | ||
| 140 | |||
| 141 | TEST_CASE("CoreTiming[SharedSlot]", "[core]") { | ||
| 142 | using namespace SharedSlotTest; | ||
| 143 | |||
| 144 | ScopeInit guard; | ||
| 145 | |||
| 146 | CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", FifoCallback<0>); | ||
| 147 | CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", FifoCallback<1>); | ||
| 148 | CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", FifoCallback<2>); | ||
| 149 | CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", FifoCallback<3>); | ||
| 150 | CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", FifoCallback<4>); | ||
| 151 | |||
| 152 | CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]); | ||
| 153 | CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]); | ||
| 154 | CoreTiming::ScheduleEvent(1000, cb_c, CB_IDS[2]); | ||
| 155 | CoreTiming::ScheduleEvent(1000, cb_d, CB_IDS[3]); | ||
| 156 | CoreTiming::ScheduleEvent(1000, cb_e, CB_IDS[4]); | ||
| 157 | |||
| 158 | // Enter slice 0 | ||
| 159 | CoreTiming::Advance(); | ||
| 160 | REQUIRE(1000 == CoreTiming::GetDowncount()); | ||
| 161 | |||
| 162 | callbacks_ran_flags = 0; | ||
| 163 | counter = 0; | ||
| 164 | lateness = 0; | ||
| 165 | CoreTiming::AddTicks(CoreTiming::GetDowncount()); | ||
| 166 | CoreTiming::Advance(); | ||
| 167 | REQUIRE(MAX_SLICE_LENGTH == CoreTiming::GetDowncount()); | ||
| 168 | REQUIRE(0x1FULL == callbacks_ran_flags.to_ullong()); | ||
| 169 | } | ||
| 170 | |||
| 171 | TEST_CASE("CoreTiming[PredictableLateness]", "[core]") { | ||
| 172 | ScopeInit guard; | ||
| 173 | |||
| 174 | CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>); | ||
| 175 | CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>); | ||
| 176 | |||
| 177 | // Enter slice 0 | ||
| 178 | CoreTiming::Advance(); | ||
| 179 | |||
| 180 | CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]); | ||
| 181 | CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]); | ||
| 182 | |||
| 183 | AdvanceAndCheck(0, 90, 10, -10); // (100 - 10) | ||
| 184 | AdvanceAndCheck(1, MAX_SLICE_LENGTH, 50, -50); | ||
| 185 | } | ||
| 186 | |||
| 187 | namespace ChainSchedulingTest { | ||
| 188 | static int reschedules = 0; | ||
| 189 | |||
| 190 | static void RescheduleCallback(u64 userdata, s64 cycles_late) { | ||
| 191 | --reschedules; | ||
| 192 | REQUIRE(reschedules >= 0); | ||
| 193 | REQUIRE(lateness == cycles_late); | ||
| 194 | |||
| 195 | if (reschedules > 0) | ||
| 196 | CoreTiming::ScheduleEvent(1000, reinterpret_cast<CoreTiming::EventType*>(userdata), | ||
| 197 | userdata); | ||
| 198 | } | ||
| 199 | } // namespace ChainSchedulingTest | ||
| 200 | |||
| 201 | TEST_CASE("CoreTiming[ChainScheduling]", "[core]") { | ||
| 202 | using namespace ChainSchedulingTest; | ||
| 203 | |||
| 204 | ScopeInit guard; | ||
| 205 | |||
| 206 | CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>); | ||
| 207 | CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>); | ||
| 208 | CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>); | ||
| 209 | CoreTiming::EventType* cb_rs = | ||
| 210 | CoreTiming::RegisterEvent("callbackReschedule", RescheduleCallback); | ||
| 211 | |||
| 212 | // Enter slice 0 | ||
| 213 | CoreTiming::Advance(); | ||
| 214 | |||
| 215 | CoreTiming::ScheduleEvent(800, cb_a, CB_IDS[0]); | ||
| 216 | CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]); | ||
| 217 | CoreTiming::ScheduleEvent(2200, cb_c, CB_IDS[2]); | ||
| 218 | CoreTiming::ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs)); | ||
| 219 | REQUIRE(800 == CoreTiming::GetDowncount()); | ||
| 220 | |||
| 221 | reschedules = 3; | ||
| 222 | AdvanceAndCheck(0, 200); // cb_a | ||
| 223 | AdvanceAndCheck(1, 1000); // cb_b, cb_rs | ||
| 224 | REQUIRE(2 == reschedules); | ||
| 225 | |||
| 226 | CoreTiming::AddTicks(CoreTiming::GetDowncount()); | ||
| 227 | CoreTiming::Advance(); // cb_rs | ||
| 228 | REQUIRE(1 == reschedules); | ||
| 229 | REQUIRE(200 == CoreTiming::GetDowncount()); | ||
| 230 | |||
| 231 | AdvanceAndCheck(2, 800); // cb_c | ||
| 232 | |||
| 233 | CoreTiming::AddTicks(CoreTiming::GetDowncount()); | ||
| 234 | CoreTiming::Advance(); // cb_rs | ||
| 235 | REQUIRE(0 == reschedules); | ||
| 236 | REQUIRE(MAX_SLICE_LENGTH == CoreTiming::GetDowncount()); | ||
| 237 | } | ||