Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
534daaff3b | ||
|
|
89d79a59ff | ||
|
|
3ef3acf948 | ||
|
|
a687732975 | ||
|
|
4c2e25abe6 | ||
|
|
15d39acf30 | ||
|
|
5415607ff4 | ||
|
|
38ae6e22c4 |
No files matched your search
@@ -51,6 +51,9 @@ mkdir -p AppDir/usr/optional/libgcc_s
|
||||
# Deploy yuzu's needed dependencies
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt
|
||||
|
||||
# Workaround for libQt5MultimediaGstTools indirectly requiring libwayland-client and breaking Vulkan usage on end-user systems
|
||||
find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print
|
||||
|
||||
# Workaround for building yuzu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al.
|
||||
# See https://github.com/darealshinji/AppImageKit-checkrt
|
||||
cp exec-x86_64.so AppDir/usr/optional/exec.so
|
||||
|
||||
@@ -6,7 +6,7 @@ parameters:
|
||||
steps:
|
||||
- script: choco install vulkan-sdk
|
||||
displayName: 'Install vulkan-sdk'
|
||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON .. && cd ..
|
||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
|
||||
@@ -47,7 +47,7 @@ stages:
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'msvc'
|
||||
pool:
|
||||
vmImage: windows-2022
|
||||
vmImage: windows-2019
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
|
||||
@@ -12,7 +12,7 @@ stages:
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'windows-msvc'
|
||||
pool:
|
||||
vmImage: windows-2022
|
||||
vmImage: windows-2019
|
||||
steps:
|
||||
- template: ./templates/sync-source.yml
|
||||
parameters:
|
||||
|
||||
@@ -30,10 +30,6 @@ namespace Common {
|
||||
#else
|
||||
return _udiv128(r[1], r[0], d, &remainder);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __SIZEOF_INT128__
|
||||
const auto product = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
|
||||
return static_cast<u64>(product / d);
|
||||
#else
|
||||
const u64 diva = a / d;
|
||||
const u64 moda = a % d;
|
||||
@@ -41,7 +37,6 @@ namespace Common {
|
||||
const u64 modb = b % d;
|
||||
return diva * b + moda * divb + moda * modb / d;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
|
||||
@@ -75,8 +75,8 @@ NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequen
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
TimePoint current_time_point{};
|
||||
TimePoint new_time_point{};
|
||||
TimePoint current_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
|
||||
@@ -125,7 +125,9 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
|
||||
if (parent.uses_wall_clock) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
@@ -142,7 +144,12 @@ public:
|
||||
}
|
||||
|
||||
u64 GetTicksRemaining() override {
|
||||
ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
|
||||
if (parent.uses_wall_clock) {
|
||||
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
|
||||
return minimum_run_cycles;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
||||
}
|
||||
@@ -172,7 +179,7 @@ public:
|
||||
Core::Memory::Memory& memory;
|
||||
std::size_t num_interpreted_instructions{};
|
||||
bool debugger_enabled{};
|
||||
static constexpr u64 minimum_run_cycles = 1000U;
|
||||
static constexpr u64 minimum_run_cycles = 10000U;
|
||||
};
|
||||
|
||||
std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* page_table) const {
|
||||
@@ -200,7 +207,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
|
||||
// Timing
|
||||
config.wall_clock_cntpct = uses_wall_clock;
|
||||
config.enable_cycle_counting = !uses_wall_clock;
|
||||
config.enable_cycle_counting = true;
|
||||
|
||||
// Code cache size
|
||||
config.code_cache_size = 512_MiB;
|
||||
|
||||
@@ -166,7 +166,9 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
|
||||
if (parent.uses_wall_clock) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
@@ -181,7 +183,12 @@ public:
|
||||
}
|
||||
|
||||
u64 GetTicksRemaining() override {
|
||||
ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled");
|
||||
if (parent.uses_wall_clock) {
|
||||
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
|
||||
return minimum_run_cycles;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
||||
}
|
||||
@@ -216,7 +223,7 @@ public:
|
||||
u64 tpidrro_el0 = 0;
|
||||
u64 tpidr_el0 = 0;
|
||||
bool debugger_enabled{};
|
||||
static constexpr u64 minimum_run_cycles = 1000U;
|
||||
static constexpr u64 minimum_run_cycles = 10000U;
|
||||
};
|
||||
|
||||
std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* page_table,
|
||||
@@ -260,7 +267,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
|
||||
// Timing
|
||||
config.wall_clock_cntpct = uses_wall_clock;
|
||||
config.enable_cycle_counting = !uses_wall_clock;
|
||||
config.enable_cycle_counting = true;
|
||||
|
||||
// Code cache size
|
||||
config.code_cache_size = 512_MiB;
|
||||
|
||||
+56
-95
@@ -6,9 +6,7 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hardware_properties.h"
|
||||
@@ -44,10 +42,10 @@ CoreTiming::CoreTiming()
|
||||
|
||||
CoreTiming::~CoreTiming() = default;
|
||||
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance, size_t id) {
|
||||
const std::string name = "yuzu:HostTiming_" + std::to_string(id);
|
||||
MicroProfileOnThreadCreate(name.c_str());
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance) {
|
||||
constexpr char name[] = "yuzu:HostTiming";
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
instance.on_thread_init();
|
||||
instance.ThreadLoop();
|
||||
@@ -63,127 +61,100 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0);
|
||||
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::Shutdown() {
|
||||
is_paused = true;
|
||||
paused = true;
|
||||
shutting_down = true;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
|
||||
event_cv.notify_all();
|
||||
wait_pause_cv.notify_all();
|
||||
for (auto& thread : worker_threads) {
|
||||
thread.join();
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
if (timer_thread) {
|
||||
timer_thread->join();
|
||||
}
|
||||
worker_threads.clear();
|
||||
pause_callbacks.clear();
|
||||
ClearPendingEvents();
|
||||
timer_thread.reset();
|
||||
has_started = false;
|
||||
}
|
||||
|
||||
void CoreTiming::Pause(bool is_paused_) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
if (is_paused_ == paused_state.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
if (is_multicore) {
|
||||
is_paused = is_paused_;
|
||||
event_cv.notify_all();
|
||||
if (!is_paused_) {
|
||||
wait_pause_cv.notify_all();
|
||||
}
|
||||
}
|
||||
paused_state.store(is_paused_, std::memory_order_relaxed);
|
||||
void CoreTiming::Pause(bool is_paused) {
|
||||
paused = is_paused;
|
||||
pause_event.Set();
|
||||
|
||||
if (!is_paused_) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::SyncPause(bool is_paused_) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
if (is_paused_ == paused_state.load(std::memory_order_relaxed)) {
|
||||
void CoreTiming::SyncPause(bool is_paused) {
|
||||
if (is_paused == paused && paused_set == paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_multicore) {
|
||||
is_paused = is_paused_;
|
||||
event_cv.notify_all();
|
||||
if (!is_paused_) {
|
||||
wait_pause_cv.notify_all();
|
||||
}
|
||||
}
|
||||
paused_state.store(is_paused_, std::memory_order_relaxed);
|
||||
if (is_multicore) {
|
||||
if (is_paused_) {
|
||||
wait_signal_cv.wait(main_lock, [this] { return pause_count == worker_threads.size(); });
|
||||
} else {
|
||||
wait_signal_cv.wait(main_lock, [this] { return pause_count == 0; });
|
||||
Pause(is_paused);
|
||||
if (timer_thread) {
|
||||
if (!is_paused) {
|
||||
pause_event.Set();
|
||||
}
|
||||
event.Set();
|
||||
while (paused_set != is_paused)
|
||||
;
|
||||
}
|
||||
|
||||
if (!is_paused_) {
|
||||
if (!is_paused) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
cb(is_paused);
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreTiming::IsRunning() const {
|
||||
return !paused_state.load(std::memory_order_acquire);
|
||||
return !paused_set;
|
||||
}
|
||||
|
||||
bool CoreTiming::HasPendingEvents() const {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
return !event_queue.empty() || pending_events.load(std::memory_order_relaxed) != 0;
|
||||
return !(wait_set && event_queue.empty());
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
{
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
||||
if (is_multicore) {
|
||||
event_cv.notify_one();
|
||||
event_queue.emplace_back(
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
|
||||
event.Set();
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
std::chrono::nanoseconds resched_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
||||
|
||||
event_queue.emplace_back(
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
||||
if (is_multicore) {
|
||||
event_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
|
||||
});
|
||||
@@ -192,7 +163,6 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
pending_events.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,12 +202,11 @@ u64 CoreTiming::GetClockTicks() const {
|
||||
}
|
||||
|
||||
void CoreTiming::ClearPendingEvents() {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_queue.clear();
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock lock{basic_lock};
|
||||
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get();
|
||||
@@ -251,28 +220,27 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
}
|
||||
|
||||
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
std::scoped_lock lock{basic_lock};
|
||||
pause_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
|
||||
if (const auto event_type{evt.type.lock()}) {
|
||||
event_mutex.unlock();
|
||||
basic_lock.unlock();
|
||||
|
||||
const auto new_schedule_time{event_type->callback(
|
||||
evt.user_data, evt.time,
|
||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
|
||||
|
||||
event_mutex.lock();
|
||||
pending_events.fetch_sub(1, std::memory_order_relaxed);
|
||||
basic_lock.lock();
|
||||
|
||||
if (evt.reschedule_time != 0) {
|
||||
// If this event was scheduled into a pause, its time now is going to be way behind.
|
||||
@@ -285,9 +253,9 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
const auto next_schedule_time{new_schedule_time.has_value()
|
||||
? new_schedule_time.value().count()
|
||||
: evt.reschedule_time};
|
||||
|
||||
event_queue.emplace_back(
|
||||
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
@@ -304,34 +272,27 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
}
|
||||
|
||||
void CoreTiming::ThreadLoop() {
|
||||
const auto predicate = [this] { return !event_queue.empty() || is_paused; };
|
||||
has_started = true;
|
||||
while (!shutting_down) {
|
||||
while (!is_paused && !shutting_down) {
|
||||
while (!paused) {
|
||||
paused_set = false;
|
||||
const auto next_time = Advance();
|
||||
if (next_time) {
|
||||
if (*next_time > 0) {
|
||||
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_cv.wait_for(main_lock, next_time_ns, predicate);
|
||||
event.WaitFor(next_time_ns);
|
||||
}
|
||||
} else {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
event_cv.wait(main_lock, predicate);
|
||||
wait_set = true;
|
||||
event.Wait();
|
||||
}
|
||||
wait_set = false;
|
||||
}
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
pause_count++;
|
||||
if (pause_count == worker_threads.size()) {
|
||||
clock->Pause(true);
|
||||
wait_signal_cv.notify_all();
|
||||
}
|
||||
wait_pause_cv.wait(main_lock, [this] { return !is_paused || shutting_down; });
|
||||
pause_count--;
|
||||
if (pause_count == 0) {
|
||||
clock->Pause(false);
|
||||
wait_signal_cv.notify_all();
|
||||
}
|
||||
|
||||
paused_set = true;
|
||||
clock->Pause(true);
|
||||
pause_event.Wait();
|
||||
clock->Pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-14
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -15,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
@@ -143,7 +143,7 @@ private:
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
|
||||
static void ThreadEntry(CoreTiming& instance, size_t id);
|
||||
static void ThreadEntry(CoreTiming& instance);
|
||||
void ThreadLoop();
|
||||
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
@@ -156,24 +156,21 @@ private:
|
||||
// accomodated by the standard adaptor class.
|
||||
std::vector<Event> event_queue;
|
||||
u64 event_fifo_id = 0;
|
||||
std::atomic<size_t> pending_events{};
|
||||
|
||||
std::shared_ptr<EventType> ev_lost;
|
||||
Common::Event event{};
|
||||
Common::Event pause_event{};
|
||||
std::mutex basic_lock;
|
||||
std::mutex advance_lock;
|
||||
std::unique_ptr<std::thread> timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
std::atomic<bool> shutting_down{};
|
||||
std::atomic<bool> has_started{};
|
||||
std::function<void()> on_thread_init{};
|
||||
|
||||
std::vector<std::thread> worker_threads;
|
||||
|
||||
std::condition_variable event_cv;
|
||||
std::condition_variable wait_pause_cv;
|
||||
std::condition_variable wait_signal_cv;
|
||||
mutable std::mutex event_mutex;
|
||||
|
||||
std::atomic<bool> paused_state{};
|
||||
bool is_paused{};
|
||||
bool shutting_down{};
|
||||
bool is_multicore{};
|
||||
size_t pause_count{};
|
||||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@@ -23,15 +22,14 @@ std::array<s64, 5> delays{};
|
||||
|
||||
std::bitset<CB_IDS.size()> callbacks_ran_flags;
|
||||
u64 expected_callback = 0;
|
||||
std::mutex control_mutex;
|
||||
|
||||
template <unsigned int IDX>
|
||||
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
std::unique_lock<std::mutex> lk(control_mutex);
|
||||
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
||||
callbacks_ran_flags.set(IDX);
|
||||
REQUIRE(CB_IDS[IDX] == user_data);
|
||||
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
|
||||
delays[IDX] = ns_late.count();
|
||||
++expected_callback;
|
||||
return std::nullopt;
|
||||
|
||||
@@ -31,8 +31,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
|
||||
VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
CommandDataContainer next;
|
||||
state.queue.Pop(next, stop_token);
|
||||
CommandDataContainer next = state.queue.PopWait(stop_token);
|
||||
if (stop_token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "video_core/framebuffer_config.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -96,7 +96,7 @@ struct CommandDataContainer {
|
||||
|
||||
/// Struct used to synchronize the GPU thread
|
||||
struct SynchState final {
|
||||
using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
|
||||
using CommandQueue = Common::MPSCQueue<CommandDataContainer, true>;
|
||||
std::mutex write_lock;
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/scope_exit.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/macro/macro.h"
|
||||
#include "video_core/macro/macro_hle.h"
|
||||
@@ -58,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
|
||||
maxwell3d.regs.index_array.first = parameters[3];
|
||||
maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
|
||||
maxwell3d.regs.index_array.count = parameters[1];
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
maxwell3d.regs.vb_element_base = element_base;
|
||||
maxwell3d.regs.vb_base_instance = base_instance;
|
||||
maxwell3d.mme_draw.instance_count = instance_count;
|
||||
@@ -80,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
|
||||
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
|
||||
// Multidraw Indirect
|
||||
void HLE_3f5e74b9c9a50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
|
||||
SCOPE_EXIT({
|
||||
// Clean everything.
|
||||
maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
|
||||
maxwell3d.regs.index_array.count = 0;
|
||||
maxwell3d.regs.vb_element_base = 0x0;
|
||||
maxwell3d.regs.vb_base_instance = 0x0;
|
||||
maxwell3d.mme_draw.instance_count = 0;
|
||||
maxwell3d.CallMethodFromMME(0x8e3, 0x640);
|
||||
maxwell3d.CallMethodFromMME(0x8e4, 0x0);
|
||||
maxwell3d.CallMethodFromMME(0x8e5, 0x0);
|
||||
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
});
|
||||
const u32 start_indirect = parameters[0];
|
||||
const u32 end_indirect = parameters[1];
|
||||
if (start_indirect >= end_indirect) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
const auto topology =
|
||||
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
|
||||
maxwell3d.regs.draw.topology.Assign(topology);
|
||||
const u32 padding = parameters[3];
|
||||
const std::size_t max_draws = parameters[4];
|
||||
|
||||
const u32 indirect_words = 5 + padding;
|
||||
const std::size_t first_draw = start_indirect;
|
||||
const std::size_t effective_draws = end_indirect - start_indirect;
|
||||
const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
|
||||
|
||||
for (std::size_t index = first_draw; index < last_draw; index++) {
|
||||
const std::size_t base = index * indirect_words + 5;
|
||||
const u32 num_vertices = parameters[base];
|
||||
const u32 instance_count = parameters[base + 1];
|
||||
const u32 first_index = parameters[base + 2];
|
||||
const u32 base_vertex = parameters[base + 3];
|
||||
const u32 base_instance = parameters[base + 4];
|
||||
maxwell3d.regs.index_array.first = first_index;
|
||||
maxwell3d.regs.reg_array[0x446] = base_vertex;
|
||||
maxwell3d.regs.index_array.count = num_vertices;
|
||||
maxwell3d.regs.vb_element_base = base_vertex;
|
||||
maxwell3d.regs.vb_base_instance = base_instance;
|
||||
maxwell3d.mme_draw.instance_count = instance_count;
|
||||
maxwell3d.CallMethodFromMME(0x8e3, 0x640);
|
||||
maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
|
||||
maxwell3d.CallMethodFromMME(0x8e5, base_instance);
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
if (maxwell3d.ShouldExecute()) {
|
||||
maxwell3d.Rasterizer().Draw(true, true);
|
||||
}
|
||||
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
|
||||
{0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
|
||||
{0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
|
||||
{0x0217920100488FF7, &HLE_0217920100488FF7},
|
||||
{0x3f5e74b9c9a50164, &HLE_3f5e74b9c9a50164},
|
||||
}};
|
||||
|
||||
class HLEMacroImpl final : public CachedMacro {
|
||||
@@ -99,6 +159,7 @@ private:
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
HLEFunction func;
|
||||
};
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
#include <csignal>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
|
||||
#include "applets/qt_controller.h"
|
||||
@@ -260,6 +264,10 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
config{std::make_unique<Config>(*system)},
|
||||
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
||||
provider{std::make_unique<FileSys::ManualContentProvider>()} {
|
||||
#ifdef __linux__
|
||||
SetupSigInterrupts();
|
||||
#endif
|
||||
|
||||
Common::Log::Initialize();
|
||||
LoadTranslation();
|
||||
|
||||
@@ -379,6 +387,8 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
|
||||
SDL_EnableScreenSaver();
|
||||
#endif
|
||||
|
||||
SetupPrepareForSleep();
|
||||
|
||||
Common::Log::Start();
|
||||
|
||||
QStringList args = QApplication::arguments();
|
||||
@@ -463,7 +473,13 @@ GMainWindow::~GMainWindow() {
|
||||
if (render_window->parent() == nullptr) {
|
||||
delete render_window;
|
||||
}
|
||||
|
||||
system->GetRoomNetwork().Shutdown();
|
||||
|
||||
#ifdef __linux__
|
||||
::close(sig_interrupt_fds[0]);
|
||||
::close(sig_interrupt_fds[1]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GMainWindow::RegisterMetaTypes() {
|
||||
@@ -1314,6 +1330,43 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::SetupPrepareForSleep() {
|
||||
#ifdef __linux__
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
if (bus.isConnected()) {
|
||||
const bool success = bus.connect(
|
||||
QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"),
|
||||
QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"),
|
||||
QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool)));
|
||||
|
||||
if (!success) {
|
||||
LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
|
||||
}
|
||||
#endif // __linux__
|
||||
}
|
||||
|
||||
void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
|
||||
if (emu_thread == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prepare_sleep) {
|
||||
if (emu_thread->IsRunning()) {
|
||||
auto_paused = true;
|
||||
OnPauseGame();
|
||||
}
|
||||
} else {
|
||||
if (!emu_thread->IsRunning() && auto_paused) {
|
||||
auto_paused = false;
|
||||
RequestGameResume();
|
||||
OnStartGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
|
||||
if (!QDBusConnection::sessionBus().isConnected()) {
|
||||
@@ -1353,6 +1406,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
|
||||
QString::fromLatin1("org.freedesktop.portal.Request"));
|
||||
unlocker.call(QString::fromLatin1("Close"));
|
||||
}
|
||||
|
||||
std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
|
||||
|
||||
void GMainWindow::SetupSigInterrupts() {
|
||||
if (sig_interrupt_fds[2] == 1) {
|
||||
return;
|
||||
}
|
||||
socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data());
|
||||
sig_interrupt_fds[2] = 1;
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = &GMainWindow::HandleSigInterrupt;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESETHAND;
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
|
||||
sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this);
|
||||
connect(sig_interrupt_notifier, &QSocketNotifier::activated, this,
|
||||
&GMainWindow::OnSigInterruptNotifierActivated);
|
||||
connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close);
|
||||
}
|
||||
|
||||
void GMainWindow::HandleSigInterrupt(int sig) {
|
||||
if (sig == SIGINT) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Calling into Qt directly from a signal handler is not safe,
|
||||
// so wake up a QSocketNotifier with this hacky write call instead.
|
||||
char a = 1;
|
||||
int ret = write(sig_interrupt_fds[0], &a, sizeof(a));
|
||||
(void)ret;
|
||||
}
|
||||
|
||||
void GMainWindow::OnSigInterruptNotifierActivated() {
|
||||
sig_interrupt_notifier->setEnabled(false);
|
||||
|
||||
char a;
|
||||
int ret = read(sig_interrupt_fds[1], &a, sizeof(a));
|
||||
(void)ret;
|
||||
|
||||
sig_interrupt_notifier->setEnabled(true);
|
||||
|
||||
emit SigInterrupt();
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
void GMainWindow::PreventOSSleep() {
|
||||
|
||||
+15
-1
@@ -164,6 +164,8 @@ signals:
|
||||
void WebBrowserExtractOfflineRomFS();
|
||||
void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
|
||||
|
||||
void SigInterrupt();
|
||||
|
||||
public slots:
|
||||
void OnLoadComplete();
|
||||
void OnExecuteProgram(std::size_t program_index);
|
||||
@@ -203,7 +205,7 @@ private:
|
||||
void ConnectMenuEvents();
|
||||
void UpdateMenuState();
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
void SetupPrepareForSleep();
|
||||
|
||||
void PreventOSSleep();
|
||||
void AllowOSSleep();
|
||||
@@ -252,12 +254,19 @@ private:
|
||||
void RequestGameResume();
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
#ifdef __linux__
|
||||
void SetupSigInterrupts();
|
||||
static void HandleSigInterrupt(int);
|
||||
void OnSigInterruptNotifierActivated();
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void OnStartGame();
|
||||
void OnRestartGame();
|
||||
void OnPauseGame();
|
||||
void OnPauseContinueGame();
|
||||
void OnStopGame();
|
||||
void OnPrepareForSleep(bool prepare_sleep);
|
||||
void OnMenuReportCompatibility();
|
||||
void OnOpenModsPage();
|
||||
void OnOpenQuickstartGuide();
|
||||
@@ -348,6 +357,8 @@ private:
|
||||
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
GRenderWindow* render_window;
|
||||
GameList* game_list;
|
||||
LoadingScreen* loading_screen;
|
||||
@@ -420,6 +431,9 @@ private:
|
||||
bool is_tas_recording_dialog_active{};
|
||||
|
||||
#ifdef __linux__
|
||||
QSocketNotifier* sig_interrupt_notifier;
|
||||
static std::array<int, 3> sig_interrupt_fds;
|
||||
|
||||
QDBusObjectPath wake_lock{};
|
||||
#endif
|
||||
|
||||
|
||||
Reference in new issue
Block a user