Remove all sleeping and wall-clock pacing from the core

retro_run runs one video frame and the frontend paces it, so the core no
longer paces itself:

- The speed limiter is gone: CoreTiming::Throttle and its host-time
  targets, VI skip, the presenter's presentation-time wait,
  PrecisionTimer, PerformanceMetrics/PerformanceTracker, and the
  emulation speed, precision timing, rush/smooth presentation and VI
  skip settings and core options. The Dolphin IOS device reports 100%
  and ignores speed limits a guest asks for.
- Common/FrameClock.c (C89) counts retro_run calls. Background threads
  that acted every so many milliseconds wait for a number of frames
  instead: real-Wiimote pool upkeep, scanning, write tests and
  reconnect retries, USB scanning, the USB Gecko and GBA link
  listeners, the BBA read thread, memory card flushing and the WC24
  scheduler. Shutdown wakes every waiter.
- Waits that polled or timed out now block until signalled: dual-core
  boot, PauseAndLock in the GPU loop, CPU thread jobs, shader
  compilation progress, BlockingLoop and ParallelFor setup and
  teardown.
- Bluetooth passthrough submits transfers straight to libusb; the
  output worker and its timed sleep are deleted, HCI command timeouts
  count emulated time, and blocking firmware commands wait for the
  adapter's events.
- Real Wiimote reports go out when produced; delayed ones carry a
  frame number.
- Common::Flag sits on retro_atomic.
- Deleted: SleepCurrentThread, YieldCPU, Common::SleepUntil,
  Event::WaitFor, BlockingLoop::WaitYield, timer resolution changes,
  the play-time tracker, Host_YieldToUI, FrameTiming::ThrottleFrame
  leftovers and the never-used DO_WAIT_FOR_WIIMOTES mode.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-05 22:05:03 +01:00
1 parent 1d3415fa2c
commit 1e2dbf6616
70 files changed
+452 -1904

No files matched your search

+1 -3
View File
@@ -1110,6 +1110,7 @@ DOLPHIN_SOURCES_ASM :=
DOLPHIN_SOURCES_OBJCXX :=
DOLPHIN_SOURCES_C += \
$(CORE_DIR)/Source/Core/Common/CallGate.c \
$(CORE_DIR)/Source/Core/Common/FrameClock.c \
$(CORE_DIR)/Source/Core/Common/ParallelFor.c \
$(CORE_DIR)/Source/Core/Common/Quiesce.c
DOLPHIN_SOURCES_CXX += \
@@ -1259,8 +1260,6 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/VideoCommon/OnScreenUI.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/OpcodeDecoding.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PerfQueryBase.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PerformanceMetrics.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PerformanceTracker.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PipelineUtils.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PixelEngine.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/PixelShaderGen.cpp \
@@ -1665,7 +1664,6 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/Core/State.cpp \
$(CORE_DIR)/Source/Core/Core/SysConf.cpp \
$(CORE_DIR)/Source/Core/Core/System.cpp \
$(CORE_DIR)/Source/Core/Core/TimePlayed.cpp \
$(CORE_DIR)/Source/Core/Core/TitleDatabase.cpp \
$(CORE_DIR)/Source/Core/Core/USBUtils.cpp \
$(CORE_DIR)/Source/Core/Core/WC24PatchEngine.cpp \
+1 -6
View File
@@ -69,11 +69,7 @@ void Mixer::MixerFifo::Mix(s16* samples, std::size_t num_samples)
// We need at least a double because the index jump has 24 bits of fractional precision.
const double out_sample_rate = m_mixer->m_output_sample_rate;
double in_sample_rate = double(m_input_sample_rate_dividend) / m_input_sample_rate_divisor;
const double emulation_speed = m_mixer->m_config_emulation_speed;
if (!m_mixer->m_config_audio_preserve_pitch && 0 < emulation_speed && emulation_speed != 1.0)
in_sample_rate *= emulation_speed;
const double in_sample_rate = double(m_input_sample_rate_dividend) / m_input_sample_rate_divisor;
const double base = static_cast<double>(1 << GRANULE_FRAC_BITS);
const u32 index_jump = std::lround(base * in_sample_rate / out_sample_rate);
@@ -426,7 +422,6 @@ void Mixer::StopLogDSPAudio()
void Mixer::RefreshConfig()
{
m_config_emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
m_config_audio_preserve_pitch = Config::Get(Config::MAIN_AUDIO_PRESERVE_PITCH);
m_config_fill_audio_gaps = Config::Get(Config::MAIN_AUDIO_FILL_GAPS);
m_config_audio_buffer_ms = Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE);
-1
View File
@@ -193,7 +193,6 @@ private:
bool m_log_dtk_audio = false;
bool m_log_dsp_audio = false;
float m_config_emulation_speed;
bool m_config_audio_preserve_pitch;
bool m_config_fill_audio_gaps;
int m_config_audio_buffer_ms;
+25 -43
View File
@@ -3,7 +3,6 @@
#pragma once
#include <chrono>
#include <retro_atomic.h>
#include <rthreads/retro_eventcount.h>
@@ -37,6 +36,7 @@ public:
{
retro_atomic_int_init(&m_running_state, STATE_DONE);
retro_atomic_int_init(&m_prepare_state, PREPARE_STOPPED);
retro_atomic_int_init(&m_in_run, 0);
retro_eventcount_init(&m_done);
}
~BlockingLoop()
@@ -86,31 +86,6 @@ public:
m_may_sleep.Set();
}
// Wait for a complete payload run after the last Wakeup() call.
// This version will call a yield function every 100ms.
// If stopped, this returns immediately.
template <class Rep, class Period, typename Functor>
void WaitYield(const std::chrono::duration<Rep, Period>& rel_time, Functor yield_func)
{
const int64_t timeout_us =
std::chrono::duration_cast<std::chrono::microseconds>(rel_time).count();
while (!IsDone())
{
const int key = retro_eventcount_prepare_wait(&m_done);
if (IsDone())
{
retro_eventcount_cancel_wait(&m_done);
break;
}
if (!retro_eventcount_commit_wait_timeout(&m_done, key, timeout_us))
yield_func();
}
// As we wanted to wait for the other thread, there is likely no work remaining.
// So there is no need for a busy loop any more.
m_may_sleep.Set();
}
// Half start the worker.
// So this object is in a running state and Wait() will block until the worker calls Run().
// This may be called from any thread and is supposed to be called at least once before Wait()
@@ -122,9 +97,17 @@ public:
// sets it up; any other one waits for that, which is a few stores.
if (!retro_atomic_cas_int(&m_prepare_state, PREPARE_STOPPED, PREPARE_BUSY))
{
while (retro_atomic_load_acquire_int(&m_prepare_state) == PREPARE_BUSY)
sthread_yield();
return;
// The winner notifies m_done once it is set up.
for (;;)
{
const int key = retro_eventcount_prepare_wait(&m_done);
if (retro_atomic_load_acquire_int(&m_prepare_state) != PREPARE_BUSY)
{
retro_eventcount_cancel_wait(&m_done);
return;
}
retro_eventcount_commit_wait(&m_done, key);
}
}
// so the payload will only be executed once without any Wakeup call
@@ -132,19 +115,19 @@ public:
m_shutdown.Clear();
m_may_sleep.Set();
retro_atomic_store_release_int(&m_prepare_state, PREPARE_RUNNING);
retro_eventcount_notify(&m_done);
}
// Main loop of this object.
// The payload callback is called at least as often as it's needed to match the Wakeup()
// requirements.
// The optional timeout parameter is a timeout for how periodically the payload should be called.
// Use timeout = 0 to run without a timeout at all.
template <class F>
void Run(F payload, int64_t timeout = 0)
void Run(F payload)
{
// Asserts that Prepare is called at least once before we enter the loop.
// But a good implementation should call this before already.
Prepare();
retro_atomic_store_release_int(&m_in_run, 1);
while (!m_shutdown.IsSet())
{
@@ -191,19 +174,13 @@ public:
case STATE_SLEEPING:
// Just relax
if (timeout > 0)
{
m_new_work_event.WaitFor(std::chrono::milliseconds(timeout));
}
else
{
m_new_work_event.Wait();
}
m_new_work_event.Wait();
break;
}
}
// Shutdown down, so get a safe state
retro_atomic_store_release_int(&m_in_run, 0);
retro_atomic_exchange_int(&m_running_state, STATE_DONE);
retro_atomic_store_release_int(&m_prepare_state, PREPARE_STOPPED);
@@ -230,10 +207,14 @@ public:
Wait();
break;
case StopMode::BlockAndGiveUp:
WaitYield(std::chrono::milliseconds(100), [&] {
// If timed out, assume no one will come along to call Run, so force a break
// Prepared but never run: nobody will come along to call Run, so force the stop.
if (!retro_atomic_load_acquire_int(&m_in_run))
{
retro_atomic_store_release_int(&m_prepare_state, PREPARE_STOPPED);
});
retro_eventcount_notify(&m_done);
break;
}
Wait();
break;
}
}
@@ -273,6 +254,7 @@ private:
mutable retro_atomic_int_t m_running_state; // must be of type RUNNING_TYPE
mutable retro_atomic_int_t m_prepare_state; // must be of type PREPARE_TYPE
retro_atomic_int_t m_in_run; // set while Run() is looping
Flag m_shutdown; // If this is set, the loop shall end.
Event m_new_work_event;
retro_eventcount_t m_done;
-29
View File
@@ -11,7 +11,6 @@
#pragma once
#include <chrono>
#include <retro_atomic.h>
#include <rthreads/retro_eventcount.h>
@@ -53,34 +52,6 @@ public:
}
}
template <class Rep, class Period>
bool WaitFor(const std::chrono::duration<Rep, Period>& rel_time)
{
if (TakeSignal())
return true;
const auto deadline = std::chrono::steady_clock::now() + rel_time;
for (;;)
{
const auto left = std::chrono::duration_cast<std::chrono::microseconds>(
deadline - std::chrono::steady_clock::now());
const int key = retro_eventcount_prepare_wait(&m_wake);
if (TakeSignal())
{
retro_eventcount_cancel_wait(&m_wake);
return true;
}
if (left.count() <= 0)
{
retro_eventcount_cancel_wait(&m_wake);
return false;
}
retro_eventcount_commit_wait_timeout(&m_wake, key, left.count());
if (TakeSignal())
return true;
}
}
void Reset()
{
// Any lingering signal is dropped; a waiter re-checks the flag on every pass.
+9 -10
View File
@@ -16,7 +16,7 @@
#pragma once
#include <atomic>
#include <retro_atomic.h>
namespace Common
{
@@ -26,20 +26,19 @@ public:
// Declared as explicit since we do not want "= true" to work on a flag
// object - it should be made explicit that a flag is *not* a normal
// variable.
explicit Flag(bool initial_value = false) : m_val(initial_value) {}
void Set(bool val = true) { m_val.store(val); }
explicit Flag(bool initial_value = false) { retro_atomic_int_init(&m_val, initial_value); }
void Set(bool val = true) { retro_atomic_exchange_int(&m_val, val); }
void Clear() { Set(false); }
bool IsSet() const { return m_val.load(); }
bool TestAndSet(bool val = true)
{
bool expected = !val;
return m_val.compare_exchange_strong(expected, val);
}
bool IsSet() const { return retro_atomic_load_seq_cst_int(&m_val) != 0; }
bool TestAndSet(bool val = true) { return retro_atomic_cas_int(&m_val, !val, val); }
bool TestAndClear() { return TestAndSet(false); }
// For waits that take a running flag, such as frame_clock_wait_until.
const retro_atomic_int_t* Raw() const { return &m_val; }
private:
std::atomic_bool m_val;
mutable retro_atomic_int_t m_val;
};
} // namespace Common
+80
View File
@@ -0,0 +1,80 @@
/* Copyright 2026 Dolphin Emulator Project
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "Common/FrameClock.h"
#include <stdlib.h>
#include <rthreads/retro_eventcount.h>
static retro_atomic_64_t s_frame;
static retro_atomic_ptr_t s_tick; /* retro_eventcount_t*, made on first use */
static retro_eventcount_t *frame_clock_tick(void)
{
retro_eventcount_t *ec = (retro_eventcount_t*)retro_atomic_load_acquire_ptr(&s_tick);
if (!ec)
{
/* Racing threads build one apiece; the CAS keeps the first. */
retro_eventcount_t *fresh = (retro_eventcount_t*)calloc(1, sizeof(*fresh));
retro_eventcount_init(fresh);
if (retro_atomic_cas_ptr(&s_tick, NULL, fresh))
return fresh;
retro_eventcount_free(fresh);
free(fresh);
ec = (retro_eventcount_t*)retro_atomic_load_acquire_ptr(&s_tick);
}
return ec;
}
void frame_clock_advance(void)
{
retro_atomic_fetch_add_64(&s_frame, 1);
retro_eventcount_notify(frame_clock_tick());
}
uint64_t frame_clock_now(void)
{
return (uint64_t)retro_atomic_load_acquire_64(&s_frame);
}
bool frame_clock_wait_until(uint64_t target, const retro_atomic_int_t *running)
{
retro_eventcount_t *tick = frame_clock_tick();
for (;;)
{
int key;
if (running && !retro_atomic_load_acquire_int((retro_atomic_int_t*)running))
return false;
if (frame_clock_now() >= target)
return true;
key = retro_eventcount_prepare_wait(tick);
if ((running && !retro_atomic_load_acquire_int((retro_atomic_int_t*)running))
|| frame_clock_now() >= target)
{
retro_eventcount_cancel_wait(tick);
continue;
}
retro_eventcount_commit_wait(tick, key);
}
}
void frame_clock_wake_all(void)
{
retro_eventcount_notify(frame_clock_tick());
}
int frame_clock_prepare_wait(void)
{
return retro_eventcount_prepare_wait(frame_clock_tick());
}
void frame_clock_cancel_wait(void)
{
retro_eventcount_cancel_wait(frame_clock_tick());
}
void frame_clock_commit_wait(int key)
{
retro_eventcount_commit_wait(frame_clock_tick(), key);
}
+45
View File
@@ -0,0 +1,45 @@
/* Copyright 2026 Dolphin Emulator Project
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef DOLPHIN_COMMON_FRAMECLOCK_H
#define DOLPHIN_COMMON_FRAMECLOCK_H
#include <boolean.h>
#include <stdint.h>
#include <retro_atomic.h>
#ifdef __cplusplus
extern "C" {
#endif
/* The core's only timebase outside emulated time: the count of
* retro_run calls. Background threads that used to act every so many
* milliseconds wait for a number of frames instead, so they run at
* the frontend's pace, stop while it pauses, and never sleep or read a
* clock. */
/* Called once per retro_run, by the frontend's thread. */
void frame_clock_advance(void);
uint64_t frame_clock_now(void);
/* Blocks until frame target has begun or *running reads zero. running
* may be NULL. Returns false when no longer running. Whoever clears
* *running calls frame_clock_wake_all afterwards. */
bool frame_clock_wait_until(uint64_t target, const retro_atomic_int_t *running);
/* Wakes every waiter to look at its running flag again. */
void frame_clock_wake_all(void);
/* For a wait on frames and something else at once: the other thing's
* producer calls frame_clock_wake_all. Same protocol as
* retro_eventcount: prepare, check, then cancel or commit. */
int frame_clock_prepare_wait(void);
void frame_clock_cancel_wait(void);
void frame_clock_commit_wait(int key);
#ifdef __cplusplus
}
#endif
#endif
+20 -2
View File
@@ -184,6 +184,8 @@ void parallel_for(size_t count, parallel_for_fn fn, void *ctx)
}
retro_atomic_store_release_int(&s_pool.owner, 0);
/* parallel_for_deinit may be waiting for the pool to come free. */
retro_eventcount_notify(&s_pool.idle);
}
unsigned parallel_for_threads(void)
@@ -200,8 +202,24 @@ void parallel_for_deinit(void)
{
unsigned i;
while (!retro_atomic_cas_int(&s_pool.owner, 0, 1))
sthread_yield();
/* Waits for a batch in progress to finish; its owner notifies idle
* when it lets go. Without workers there is no idle eventcount and
* no batch to wait for: deinit runs once nothing calls in. */
while (s_pool.worker_count)
{
int key;
if (retro_atomic_cas_int(&s_pool.owner, 0, 1))
break;
key = retro_eventcount_prepare_wait(&s_pool.idle);
if (retro_atomic_cas_int(&s_pool.owner, 0, 1))
{
retro_eventcount_cancel_wait(&s_pool.idle);
break;
}
retro_eventcount_commit_wait(&s_pool.idle, key);
}
if (!s_pool.worker_count)
retro_atomic_cas_int(&s_pool.owner, 0, 1);
if (s_pool.worker_count)
{
-17
View File
@@ -21,7 +21,6 @@
#include <OS.h>
#endif
#include <retro_timers.h>
#include <rthreads/rthreads.h>
#include "Common/CommonTypes.h"
@@ -39,22 +38,6 @@ int CurrentThreadId()
#endif
}
void SleepCurrentThread(int ms)
{
retro_sleep(ms);
}
void SleepCurrentThreadUs(long long us)
{
if (us > 0)
retro_sleep_us((unsigned)us);
}
void YieldCPU()
{
sthread_yield();
}
void SetCurrentThreadName(const char* name)
{
sthread_setname(name);
-11
View File
@@ -23,17 +23,6 @@ namespace Common
{
int CurrentThreadId();
void SleepCurrentThread(int ms);
// Sleeps for us microseconds; returns at once when us is not positive. Callers that
// sleep until a time point pass the distance to it.
void SleepCurrentThreadUs(long long us);
// Use this function during a spin-wait to make the current thread
// relax while another thread is working. This may be more efficient
// than using events because event functions use kernel calls.
void YieldCPU();
void SetCurrentThreadName(const char* name);
// An sthread entry point that calls F on the object passed as the thread's userdata:
-108
View File
@@ -5,7 +5,6 @@
#include <chrono>
#include "Common/CommonFuncs.h"
#include "Common/Thread.h"
#ifdef _WIN32
#include <windows.h>
@@ -92,113 +91,6 @@ u64 Timer::GetLocalTimeSinceJan1970()
#endif
}
// This is requested by Timer::IncreaseResolution on Windows.
// On Linux/other we hope/assume we already have 1ms scheduling granularity.
static constexpr int TIMER_RESOLUTION_MS = 1;
void Timer::IncreaseResolution()
{
#ifdef _MSC_VER
// Disable execution speed and timer resolution throttling process-wide.
// This mainly will keep Dolphin marked as high performance if it's in the background. The OS
// should make it high performance if it's in the foreground anyway (or for some specific
// threads e.g. audio).
// This is best-effort (i.e. the call may fail on older versions of Windows, where such throttling
// doesn't exist, anyway), and we don't bother reverting once set.
// This adjusts behavior on CPUs with "performance" and "efficiency" cores
PROCESS_POWER_THROTTLING_STATE PowerThrottling{};
PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
PowerThrottling.ControlMask =
PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
PowerThrottling.StateMask = 0;
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling,
sizeof(PowerThrottling));
// Not actually sure how useful this is these days.. :')
timeBeginPeriod(TIMER_RESOLUTION_MS);
#endif
}
void Timer::RestoreResolution()
{
#ifdef _WIN32
timeEndPeriod(TIMER_RESOLUTION_MS);
#endif
}
PrecisionTimer::PrecisionTimer()
{
#if defined(_WIN32)
// "TIMER_HIGH_RESOLUTION" requires Windows 10, version 1803, and later.
m_timer_handle =
CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (m_timer_handle == NULL)
{
ERROR_LOG_FMT(COMMON, "CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: Error:{}", GetLastError());
// Create a normal timer if "HIGH_RESOLUTION" isn't available.
m_timer_handle = CreateWaitableTimerExW(NULL, NULL, 0, TIMER_ALL_ACCESS);
if (m_timer_handle == NULL)
ERROR_LOG_FMT(COMMON, "CreateWaitableTimerExW: Error:{}", GetLastError());
}
#endif
}
PrecisionTimer::~PrecisionTimer()
{
#if defined(_WIN32)
CloseHandle(m_timer_handle);
#endif
}
void SleepUntil(TimePoint target)
{
SleepCurrentThreadUs(
std::chrono::duration_cast<std::chrono::microseconds>(target - Clock::now()).count());
}
void PrecisionTimer::SleepUntil(Clock::time_point target)
{
constexpr auto SPIN_TIME =
std::chrono::milliseconds{TIMER_RESOLUTION_MS} + std::chrono::microseconds{20};
#if defined(_WIN32)
while (true)
{
// SetWaitableTimerEx takes time in "100 nanosecond intervals".
using TimerDuration = std::chrono::duration<LONGLONG, std::ratio<100, std::nano::den>::type>;
// Apparently waiting longer than the timer resolution gives terrible accuracy.
// We'll wait in steps of 95% of the time period.
constexpr auto MAX_TICKS =
duration_cast<TimerDuration>(std::chrono::milliseconds{TIMER_RESOLUTION_MS}) * 95 / 100;
const auto wait_time = target - Clock::now() - SPIN_TIME;
const auto ticks = std::min(duration_cast<TimerDuration>(wait_time), MAX_TICKS).count();
if (ticks <= 0)
break;
const LARGE_INTEGER due_time{.QuadPart = -ticks};
SetWaitableTimerEx(m_timer_handle, &due_time, 0, NULL, NULL, NULL, 0);
WaitForSingleObject(m_timer_handle, INFINITE);
}
#else
// Sleeping on Linux generally isn't as terrible as it is on Windows.
Common::SleepUntil(target - SPIN_TIME);
#endif
// Spin for the remaining time.
while (Clock::now() < target)
{
#if defined(_WIN32)
YieldProcessor();
#else
YieldCPU();
#endif
}
}
// Results are appropriately slewed on Linux, but not on Windows, macOS, or FreeBSD.
// Clocks with that functionality seem to not be available there.
auto SteadyAwakeClock::now() -> time_point
-24
View File
@@ -23,36 +23,12 @@ public:
// The rest of these functions probably belong somewhere else
static u64 GetLocalTimeSinceJan1970();
static void IncreaseResolution();
static void RestoreResolution();
private:
u64 m_start_ms{0};
u64 m_end_ms{0};
bool m_running{false};
};
// Sleeps until target, with the OS sleep's granularity; returns at once if it has passed.
void SleepUntil(TimePoint target);
class PrecisionTimer
{
public:
PrecisionTimer();
~PrecisionTimer();
PrecisionTimer(const PrecisionTimer&) = delete;
PrecisionTimer& operator=(const PrecisionTimer&) = delete;
void SleepUntil(Clock::time_point);
private:
#ifdef _WIN32
// Using void* to avoid including Windows.h in this header just for HANDLE.
void* m_timer_handle;
#endif
};
// Similar to std::chrono::steady_clock except this clock
// specifically does *not* count time while the system is suspended.
class SteadyAwakeClock
@@ -203,7 +203,6 @@ const Info<bool> GFX_HACK_COPY_EFB_SCALED{{System::GFX, "Hacks", "EFBScaledCopy"
const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES{
{System::GFX, "Hacks", "EFBEmulateFormatChanges"}, false};
const Info<bool> GFX_HACK_VERTEX_ROUNDING{{System::GFX, "Hacks", "VertexRounding"}, false};
const Info<bool> GFX_HACK_VI_SKIP{{System::GFX, "Hacks", "VISkip"}, false};
const Info<u32> GFX_HACK_MISSING_COLOR_VALUE{{System::GFX, "Hacks", "MissingColorValue"},
0xFFFFFFFF};
const Info<bool> GFX_HACK_FAST_TEXTURE_SAMPLING{{System::GFX, "Hacks", "FastTextureSampling"},
@@ -177,7 +177,6 @@ extern const Info<bool> GFX_HACK_EARLY_XFB_OUTPUT;
extern const Info<bool> GFX_HACK_COPY_EFB_SCALED;
extern const Info<bool> GFX_HACK_EFB_EMULATE_FORMAT_CHANGES;
extern const Info<bool> GFX_HACK_VERTEX_ROUNDING;
extern const Info<bool> GFX_HACK_VI_SKIP;
extern const Info<u32> GFX_HACK_MISSING_COLOR_VALUE;
extern const Info<bool> GFX_HACK_FAST_TEXTURE_SAMPLING;
#ifdef __APPLE__
-18
View File
@@ -49,13 +49,6 @@ const Info<bool> MAIN_FASTMEM_ARENA{{System::Main, "Core", "FastmemArena"}, true
const Info<bool> MAIN_LARGE_ENTRY_POINTS_MAP{{System::Main, "Core", "LargeEntryPointsMap"}, true};
const Info<bool> MAIN_ACCURATE_CPU_CACHE{{System::Main, "Core", "AccurateCPUCache"}, false};
const Info<bool> MAIN_DSP_HLE{{System::Main, "Core", "DSPHLE"}, true};
const Info<int> MAIN_MAX_FALLBACK{{System::Main, "Core", "MaxFallback"}, 100};
const Info<int> MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 40};
const Info<bool> MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false};
const Info<bool> MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"},
false};
const Info<bool> MAIN_SMOOTH_EARLY_PRESENTATION{{System::Main, "Core", "SmoothEarlyPresentation"},
false};
#if defined(ANDROID)
// Currently enabled by default on Android because the performance boost is really needed.
constexpr bool DEFAULT_CPU_THREAD = true;
@@ -229,16 +222,6 @@ const Info<bool> MAIN_FPRF{{System::Main, "Core", "FPRF"}, false};
const Info<bool> MAIN_ACCURATE_NANS{{System::Main, "Core", "AccurateNaNs"}, false};
const Info<bool> MAIN_ACCURATE_FMADDS{{System::Main, "Core", "AccurateFmadds"}, true};
const Info<bool> MAIN_DISABLE_ICACHE{{System::Main, "Core", "DisableICache"}, false};
const Info<float> MAIN_EMULATION_SPEED{{System::Main, "Core", "EmulationSpeed"}, 1.0f};
#if defined(ANDROID)
// Currently disabled by default on Android for concern of increased power usage while on battery.
// It is also not yet exposed in the UI on Android.
constexpr bool DEFAULT_PRECISION_FRAME_TIMING = false;
#else
constexpr bool DEFAULT_PRECISION_FRAME_TIMING = true;
#endif
const Info<bool> MAIN_PRECISION_FRAME_TIMING{{System::Main, "Core", "PrecisionFrameTiming"},
DEFAULT_PRECISION_FRAME_TIMING};
const Info<float> MAIN_OVERCLOCK{{System::Main, "Core", "Overclock"}, 1.0f};
const Info<bool> MAIN_OVERCLOCK_ENABLE{{System::Main, "Core", "OverclockEnable"}, false};
const Info<float> MAIN_VI_OVERCLOCK{{System::Main, "Core", "VIOverclock"}, 1.0f};
@@ -335,7 +318,6 @@ const Info<int> MAIN_GDB_PORT{{System::Main, "General", "GDBPort"}, -1};
const Info<int> MAIN_ISO_PATH_COUNT{{System::Main, "General", "ISOPaths"}, 0};
const Info<std::string> MAIN_SKYLANDERS_PATH{{System::Main, "General", "SkylandersCollectionPath"},
""};
const Info<bool> MAIN_TIME_TRACKING{{System::Main, "General", "EnablePlayTimeTracking"}, true};
static Info<std::string> MakeISOPathConfigInfo(size_t idx)
{
-8
View File
@@ -54,11 +54,6 @@ extern const Info<bool> MAIN_LARGE_ENTRY_POINTS_MAP;
extern const Info<bool> MAIN_ACCURATE_CPU_CACHE;
// Should really be in the DSP section, but we're kind of stuck with bad decisions made in the past.
extern const Info<bool> MAIN_DSP_HLE;
extern const Info<int> MAIN_MAX_FALLBACK;
extern const Info<int> MAIN_TIMING_VARIANCE;
extern const Info<bool> MAIN_CORRECT_TIME_DRIFT;
extern const Info<bool> MAIN_RUSH_FRAME_PRESENTATION;
extern const Info<bool> MAIN_SMOOTH_EARLY_PRESENTATION;
extern const Info<bool> MAIN_CPU_THREAD;
extern const Info<bool> MAIN_LOAD_GAME_INTO_MEMORY;
extern const Info<bool> MAIN_SYNC_ON_SKIP_IDLE;
@@ -122,8 +117,6 @@ extern const Info<bool> MAIN_FPRF;
extern const Info<bool> MAIN_ACCURATE_NANS;
extern const Info<bool> MAIN_ACCURATE_FMADDS;
extern const Info<bool> MAIN_DISABLE_ICACHE;
extern const Info<float> MAIN_EMULATION_SPEED;
extern const Info<bool> MAIN_PRECISION_FRAME_TIMING;
extern const Info<float> MAIN_OVERCLOCK;
extern const Info<bool> MAIN_OVERCLOCK_ENABLE;
extern const Info<float> MAIN_VI_OVERCLOCK;
@@ -182,7 +175,6 @@ extern const Info<int> MAIN_RENDER_WINDOW_HEIGHT;
extern const Info<bool> MAIN_RENDER_WINDOW_AUTOSIZE;
extern const Info<bool> MAIN_KEEP_WINDOW_ON_TOP;
extern const Info<bool> MAIN_DISABLE_SCREENSAVER;
extern const Info<bool> MAIN_TIME_TRACKING;
// Main.General
+1 -26
View File
@@ -84,7 +84,6 @@
#include "VideoCommon/Fifo.h"
#include "VideoCommon/FrameDumper.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoEvents.h"
@@ -98,7 +97,6 @@ std::unique_ptr<BootParameters> g_boot_params;
sthread_t* s_emu_thread = nullptr;
static Common::HookableEvent<Core::State> s_state_changed_event;
static bool s_is_throttler_temp_disabled = false;
bool s_frame_step = false;
std::atomic<bool> s_stop_frame_step;
@@ -128,16 +126,6 @@ static thread_local bool tls_is_gpu_thread = false;
void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot,
WindowSystemInfo wsi);
bool GetIsThrottlerTempDisabled()
{
return s_is_throttler_temp_disabled;
}
void SetIsThrottlerTempDisabled(bool disable)
{
s_is_throttler_temp_disabled = disable;
}
void OnFrameEnd(Core::System& system)
{
#ifdef USE_MEMORYWATCHER
@@ -310,7 +298,6 @@ static void CpuThread(Core::System& system, const std::optional<std::string> sav
Common::SetCurrentThreadName("CPU-GPU thread");
// Clear performance data collected from previous threads.
system.GetPerfMetrics().Reset();
// The JIT need to be able to intercept faults, both for fastmem and for the BLR optimization.
const bool exception_handler = EMM::IsExceptionHandlerSupported();
@@ -842,9 +829,7 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> funct
// If we're waiting for completion, block until the event fires.
if (wait_for_completion)
{
// Periodically yield to the UI thread, so we don't deadlock.
while (!cpu_thread_job_finished.WaitFor(std::chrono::milliseconds(10)))
Host_YieldToUI();
cpu_thread_job_finished.Wait();
}
}
@@ -853,16 +838,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> funct
// Called from Renderer::Swap (GPU thread) when a frame is presented to the host screen.
void Callback_FramePresented(const PresentInfo& present_info)
{
auto& perf_metrics = Core::System::GetInstance().GetPerfMetrics();
perf_metrics.CountFrame();
const auto presentation_offset =
present_info.actual_present_time - present_info.intended_present_time;
perf_metrics.SetLatestFramePresentationOffset(presentation_offset);
perf_metrics.SetLatestFrameBufferSize(present_info.frame_buffer_width,
present_info.frame_buffer_height);
if (present_info.reason == PresentInfo::PresentReason::VideoInterfaceDuplicate)
return;
-2
View File
@@ -26,8 +26,6 @@ namespace Core
{
class System;
bool GetIsThrottlerTempDisabled();
void SetIsThrottlerTempDisabled(bool disable);
void Callback_NewField(Core::System& system);
-201
View File
@@ -15,7 +15,6 @@
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Core/AchievementManager.h"
#include "Core/CPUThreadConfigCallback.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
@@ -25,7 +24,6 @@
#include "VideoCommon/Fifo.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/VideoEvents.h"
@@ -94,9 +92,6 @@ void CoreTimingManager::Init()
// that slice.
m_is_global_timer_sane = true;
// Reset data used by the throttling system
ResetThrottle(0);
m_event_fifo_id = 0;
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
@@ -106,19 +101,6 @@ void CoreTimingManager::Init()
m_last_oc_factor = m_config_oc_factor;
m_globals.last_OC_factor_inverted = m_config_oc_inv_factor;
m_core_state_changed_hook = Core::AddOnStateChangedCallback([this](Core::State state) {
if (state == Core::State::Running)
{
// We don't want Throttle to attempt catch-up for all the time lost while paused.
ResetThrottle(GetTicks());
}
});
m_throttled_after_presentation = false;
m_frame_hook = m_system.GetVideoEvents().after_present_event.Register([this](const PresentInfo&) {
m_throttled_after_presentation.store(false, std::memory_order_relaxed);
});
}
CoreTimingManager::~CoreTimingManager()
@@ -134,13 +116,10 @@ CoreTimingManager::~CoreTimingManager()
void CoreTimingManager::Shutdown()
{
m_core_state_changed_hook.reset();
MoveEvents();
ClearPendingEvents();
UnregisterAllEvents();
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id);
m_frame_hook.reset();
}
void CoreTimingManager::RefreshConfig()
@@ -151,31 +130,6 @@ void CoreTimingManager::RefreshConfig()
1.0f);
m_config_oc_inv_factor = 1.0f / m_config_oc_factor;
m_config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE);
m_config_rush_frame_presentation = Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION);
// We don't want to skip so much throttling that the audio buffer overfills.
m_max_throttle_skip_time =
std::chrono::milliseconds{Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE)} / 2;
// A maximum fallback is used to prevent the system from sleeping for
// too long or going full speed in an attempt to catch up to timings.
m_max_fallback = std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_MAX_FALLBACK)));
m_max_variance = std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE)));
m_correct_time_drift = Config::Get(Config::MAIN_CORRECT_TIME_DRIFT);
if (AchievementManager::GetInstance().IsHardcoreModeActive() &&
Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f &&
Config::Get(Config::MAIN_EMULATION_SPEED) > 0.0f)
{
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
OSD::AddMessage("Minimum speed is 100% in Hardcore Mode");
}
UpdateSpeedLimit(GetTicks(), Config::Get(Config::MAIN_EMULATION_SPEED));
m_use_precision_timer = Config::Get(Config::MAIN_PRECISION_FRAME_TIMING);
}
void CoreTimingManager::DoState(PointerWrap& p)
@@ -233,10 +187,6 @@ void CoreTimingManager::DoState(PointerWrap& p)
// The exact layout of the heap in memory is implementation defined, therefore it is platform
// and library version specific.
std::ranges::make_heap(m_event_queue, std::ranges::greater{});
// The stave state has changed the time, so our previous Throttle targets are invalid.
// Especially when global_time goes down; So we create a fake throttle update.
ResetThrottle(m_globals.global_timer);
}
}
@@ -403,153 +353,6 @@ void CoreTimingManager::Advance()
power_pc.CheckExternalExceptions();
}
TimePoint CoreTimingManager::CalculateTargetHostTimeInternal(s64 target_cycle)
{
const s64 elapsed_cycles = target_cycle - m_throttle_reference_cycle;
return m_throttle_reference_time +
Clock::duration{std::chrono::seconds{elapsed_cycles}} / m_throttle_adj_clock_per_sec;
}
bool CoreTimingManager::IsSpeedUnlimited() const
{
return m_throttle_adj_clock_per_sec == 0 || Core::GetIsThrottlerTempDisabled();
}
TimePoint CoreTimingManager::GetTargetHostTime(s64 target_cycle)
{
if (IsSpeedUnlimited())
return Clock::now();
return CalculateTargetHostTimeInternal(target_cycle);
}
void CoreTimingManager::SleepUntil(TimePoint time_point)
{
const bool use_precision_timer = m_use_precision_timer.load(std::memory_order_relaxed);
if (Core::IsCPUThread())
{
const TimePoint time = Clock::now();
if (use_precision_timer)
m_precision_cpu_timer.SleepUntil(time_point);
else
Common::SleepUntil(time_point);
// Count amount of time sleeping for performance metrics
const TimePoint time_after_sleep = Clock::now();
m_system.GetPerfMetrics().CountThrottleSleep(time_after_sleep - time);
}
else
{
if (use_precision_timer)
m_precision_gpu_timer.SleepUntil(time_point);
else
Common::SleepUntil(time_point);
}
}
void CoreTimingManager::Throttle(const s64 target_cycle)
{
const TimePoint time = Clock::now();
const bool already_throttled =
m_throttled_after_presentation.exchange(true, std::memory_order_relaxed);
// If RushFramePresentation is enabled, try to Throttle just once after each presentation.
// This lowers input latency by speeding through to presentation after grabbing input.
// Make sure we don't get too far ahead of proper timing though,
// otherwise the emulator unreasonably speeds through loading screens that don't have XFB copies,
// making audio sound terrible.
const bool skip_throttle = already_throttled && m_config_rush_frame_presentation &&
((GetTargetHostTime(target_cycle) - time) < m_max_throttle_skip_time);
if (skip_throttle)
return;
// Measure current performance after throttling.
Common::ScopeGuard perf_marker{[&] {
m_system.GetPerfMetrics().CountPerformanceMarker(
target_cycle, m_system.GetSystemTimers().GetTicksPerSecond());
}};
if (IsSpeedUnlimited())
{
ResetThrottle(target_cycle);
m_throttle_disable_vi_int = false;
return;
}
// Push throttle reference values forward by exact seconds.
// This avoids drifting from cumulative rounding errors.
{
const s64 sec_adj = (target_cycle - m_throttle_reference_cycle) / m_throttle_adj_clock_per_sec;
const s64 cycle_adj = sec_adj * m_throttle_adj_clock_per_sec;
m_throttle_reference_cycle += cycle_adj;
m_throttle_reference_time += std::chrono::seconds{sec_adj};
}
TimePoint target_time = CalculateTargetHostTimeInternal(target_cycle);
const TimePoint min_target = time - m_max_fallback;
// "Correct Time Drift" setting prevents timing relaxing.
if (!m_correct_time_drift && target_time < min_target)
{
// Core is running too slow.. i.e. CPU bottleneck.
const DT adjustment = min_target - target_time;
DEBUG_LOG_FMT(CORE, "Core can not keep up with timings! [relaxing timings by {} us]",
DT_us(adjustment).count());
m_throttle_reference_time += adjustment;
target_time += adjustment;
}
UpdateVISkip(time, target_time);
SleepUntil(target_time);
}
void CoreTimingManager::UpdateSpeedLimit(s64 cycle, double new_speed)
{
m_emulation_speed = new_speed;
const u32 new_clock_per_sec =
std::lround(m_system.GetSystemTimers().GetTicksPerSecond() * new_speed);
const bool was_limited = m_throttle_adj_clock_per_sec != 0;
if (was_limited)
{
// Adjust throttle reference for graceful clock speed transition.
const s64 ticks = cycle - m_throttle_reference_cycle;
const s64 new_ticks = ticks * new_clock_per_sec / m_throttle_adj_clock_per_sec;
m_throttle_reference_cycle = cycle - new_ticks;
}
m_throttle_adj_clock_per_sec = new_clock_per_sec;
}
void CoreTimingManager::ResetThrottle(s64 cycle)
{
m_throttle_reference_cycle = cycle;
m_throttle_reference_time = Clock::now();
}
void CoreTimingManager::UpdateVISkip(TimePoint current_time, TimePoint target_time)
{
const DT vi_fallback = std::min(m_max_variance, m_max_fallback);
// Skip the VI interrupt if the CPU is lagging by a certain amount.
// It doesn't matter what amount of lag we skip VI at, as long as it's constant.
const TimePoint vi_target = current_time - vi_fallback / 2;
m_throttle_disable_vi_int = target_time < vi_target;
}
bool CoreTimingManager::GetVISkip() const
{
return m_throttle_disable_vi_int && g_ActiveConfig.bVISkip && !Core::WantsDeterminism();
}
float CoreTimingManager::GetOverclock() const
{
return m_config_oc_factor;
@@ -576,10 +379,6 @@ void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clo
{
const s64 ticks = m_globals.global_timer;
UpdateSpeedLimit(ticks, m_emulation_speed);
m_system.GetPerfMetrics().AdjustClockSpeed(ticks, new_ppc_clock, old_ppc_clock);
for (Event& ev : m_event_queue)
{
const s64 ev_ticks = (ev.time - ticks) * new_ppc_clock / old_ppc_clock;
-38
View File
@@ -103,7 +103,6 @@ public:
// doing something evil
u64 GetTicks() const;
u64 GetIdleTicks() const;
TimePoint GetTargetHostTime(s64 target_cycle);
void RefreshConfig();
@@ -160,15 +159,6 @@ public:
// Directly accessed by the JIT.
Globals& GetGlobals() { return m_globals; }
// Throttle the CPU to the specified target cycle.
void Throttle(const s64 target_cycle);
// May be used from CPU or GPU thread.
void SleepUntil(TimePoint time_point);
// Used by VideoInterface
bool GetVISkip() const;
float GetOverclock() const;
bool UseSyncOnSkipIdle() const;
@@ -214,37 +204,9 @@ private:
float m_config_oc_factor = 1.0f;
float m_config_oc_inv_factor = 1.0f;
bool m_config_sync_on_skip_idle = false;
bool m_config_rush_frame_presentation = false;
s64 m_throttle_reference_cycle = 0;
TimePoint m_throttle_reference_time = Clock::now();
u32 m_throttle_adj_clock_per_sec = 0;
bool m_throttle_disable_vi_int = false;
DT m_max_fallback = {};
DT m_max_variance = {};
bool m_correct_time_drift = false;
double m_emulation_speed = 1.0;
bool IsSpeedUnlimited() const;
void UpdateSpeedLimit(s64 cycle, double new_speed);
void ResetThrottle(s64 cycle);
TimePoint CalculateTargetHostTimeInternal(s64 target_cycle);
void UpdateVISkip(TimePoint current_time, TimePoint target_time);
int DowncountToCycles(int downcount) const;
int CyclesToDowncount(int cycles) const;
std::atomic_bool m_use_precision_timer = false;
Common::PrecisionTimer m_precision_cpu_timer;
Common::PrecisionTimer m_precision_gpu_timer;
Common::EventHook m_core_state_changed_hook;
Common::EventHook m_frame_hook;
// Used to optionally minimize throttling for improving input latency.
std::atomic_bool m_throttled_after_presentation = false;
DT m_max_throttle_skip_time{};
};
} // namespace CoreTiming
-53
View File
@@ -16,7 +16,6 @@
#include "Core/PowerPC/GDBStub.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "Core/TimePlayed.h"
#include "VideoCommon/Fifo.h"
#ifdef __LIBRETRO__
#include "VideoCommon/AsyncRequests.h"
@@ -158,55 +157,10 @@ void CPUManager::ReleaseStepping()
retro_eventcount_notify(&m_stepping_released);
}
void CPUManager::StartTimePlayedTimer()
{
Common::SetCurrentThreadName("Play Time Tracker");
// Use a clock that will appropriately ignore suspended system time.
Common::SteadyAwakeClock timer;
auto prev_time = timer.now();
while (true)
{
TimePlayed time_played;
auto curr_time = timer.now();
// Check that emulation is not paused
// If the emulation is paused, wait for SetStepping() to reactivate
const State state = LoadState();
if (state == State::Running)
{
const std::string game_id = SConfig::GetInstance().GetGameID();
const auto diff_time =
std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - prev_time);
time_played.AddTime(game_id, diff_time);
}
else if (state == State::Stepping)
{
m_time_played_finish_sync.Wait();
curr_time = timer.now();
}
prev_time = curr_time;
if (LoadState() == State::PowerDown)
return;
m_time_played_finish_sync.WaitFor(std::chrono::seconds(30));
}
}
void CPUManager::Run()
{
auto& power_pc = m_system.GetPowerPC();
// Start a separate time tracker thread
sthread_t* timing = nullptr;
if (Config::Get(Config::MAIN_TIME_TRACKING))
{
timing = sthread_create(Common::ThreadEntry<&CPUManager::StartTimePlayedTimer>, this);
}
#ifdef __LIBRETRO__
retro_atomic_store_release_int(&m_cpu_run_state_reached, 1);
retro_eventcount_notify(&m_run_state_event);
@@ -323,12 +277,6 @@ void CPUManager::Run()
}
}
if (timing)
{
m_time_played_finish_sync.Set();
Common::JoinThread(timing);
}
Host_UpdateDisasmDialog();
}
@@ -447,7 +395,6 @@ void CPUManager::SetStepping(bool stepping)
{
if (!Core::IsCPUThread())
WakeCPU();
m_time_played_finish_sync.Set();
RunAdjacentSystems(true);
}
-2
View File
@@ -137,7 +137,6 @@ private:
void FlushStepSyncEvent();
void ExecutePendingJobs();
void StartTimePlayedTimer();
void RunAdjacentSystems(bool running);
bool SetStateChecked(State s);
State LoadState() const;
@@ -176,7 +175,6 @@ private:
retro_atomic_ptr_t m_step_request;
mpsc_stack_t m_pending_jobs;
Common::Event m_time_played_finish_sync;
Core::System& m_system;
#ifdef __LIBRETRO__
+11 -2
View File
@@ -16,6 +16,7 @@
#include <sys/socket.h>
#endif
#include "Common/FrameClock.h"
#include "Common/BitUtils.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
@@ -110,6 +111,7 @@ void CEXIETHERNET::BuiltInBBAInterface::Deactivate()
// Signal read thread to exit.
m_read_enabled.Clear();
m_read_thread_shutdown.Set();
frame_clock_wake_all();
m_active = false;
// Wait for read thread to exit; the network stack is this thread's again after.
@@ -611,6 +613,7 @@ void CEXIETHERNET::BuiltInBBAInterface::Post(Request::Kind kind, std::vector<u8>
{
auto* request = new Request{{}, kind, std::move(frame)};
mpsc_stack_push(&m_requests, &request->link);
frame_clock_wake_all();
}
void CEXIETHERNET::BuiltInBBAInterface::RunRequests()
@@ -758,8 +761,14 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(void* data)
if (datasize == 0)
{
// Make thread less CPU hungry
Common::SleepCurrentThread(1);
// Nothing came in: look again next frame, or sooner when the guest sends (see Post).
const u64 next = frame_clock_now() + 1;
const int key = frame_clock_prepare_wait();
if (self->m_read_thread_shutdown.IsSet() || !mpsc_stack_empty(&self->m_requests) ||
frame_clock_now() >= next)
frame_clock_cancel_wait();
else
frame_clock_commit_wait(key);
}
if (!self->m_read_enabled.IsSet())
+7 -2
View File
@@ -10,6 +10,7 @@
#include <fmt/format.h>
#include "Common/FrameClock.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Thread.h"
@@ -42,12 +43,14 @@ GeckoSockServer::~GeckoSockServer()
--client_count;
client_running.Clear();
frame_clock_wake_all();
Common::JoinThread(clientThread);
}
if (client_count <= 0 && connectionThread)
{
server_running.Clear();
frame_clock_wake_all();
Common::JoinThread(connectionThread);
}
@@ -86,7 +89,8 @@ void GeckoSockServer::GeckoConnectionWaiter(void*)
new_client = std::make_unique<sf::TcpSocket>();
}
Common::SleepCurrentThread(1);
// Look for a connection once a frame.
frame_clock_wait_until(frame_clock_now() + 1, server_running.Raw());
}
}
@@ -164,8 +168,9 @@ void GeckoSockServer::ClientThread()
}
}
// Idle: look again next frame.
if (did_nothing)
Common::YieldCPU();
frame_clock_wait_until(frame_clock_now() + 1, client_running.Raw());
}
client->disconnect();
@@ -5,7 +5,6 @@
#include "Core/HW/EXI/EXI.h"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <string>
#include <string_view>
@@ -14,6 +13,7 @@
#include <fmt/format.h>
#include "Common/FrameClock.h"
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonPaths.h"
@@ -125,7 +125,7 @@ GCMemcardDirectory::GCMemcardDirectory(std::string directory, ExpansionInterface
const Memcard::HeaderData& header_data, u32 game_id)
: MemoryCardBase(slot, header_data.m_size_mb), m_game_id(game_id), m_last_block(-1),
m_hdr(header_data), m_bat1(header_data.m_size_mb), m_saves(0),
m_save_directory(std::move(directory)), m_exiting(false)
m_save_directory(std::move(directory))
{
// Use existing header data if available
{
@@ -215,19 +215,23 @@ void GCMemcardDirectory::FlushThread()
Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_slot).c_str());
constexpr std::chrono::seconds flush_interval{1};
// About a second of frontend frames.
constexpr u64 FLUSH_QUIET_FRAMES = 60;
while (true)
{
// no-op until signalled
m_flush_trigger.Wait();
if (m_exiting.TestAndClear())
if (!m_flush_running.IsSet())
return;
// no-op as long as signalled within flush_interval
while (m_flush_trigger.WaitFor(flush_interval))
// no-op as long as written to again within the quiet time
for (;;)
{
if (m_exiting.TestAndClear())
const u64 quiet_from = static_cast<u64>(retro_atomic_load_acquire_64(&m_last_write_frame));
if (!frame_clock_wait_until(quiet_from + FLUSH_QUIET_FRAMES, m_flush_running.Raw()))
return;
if (static_cast<u64>(retro_atomic_load_acquire_64(&m_last_write_frame)) == quiet_from)
break;
}
WriteQueuedJobs();
@@ -236,8 +240,9 @@ void GCMemcardDirectory::FlushThread()
GCMemcardDirectory::~GCMemcardDirectory()
{
m_exiting.Set();
m_flush_running.Clear();
m_flush_trigger.Set();
frame_clock_wake_all();
#ifdef __LIBRETRO__
if (m_flush_thread)
@@ -368,6 +373,7 @@ s32 GCMemcardDirectory::Write(u32 dest_address, s32 length, const u8* src_addres
if (offset + length == Memcard::BLOCK_SIZE)
{
QueueDirtySaves();
retro_atomic_store_release_64(&m_last_write_frame, static_cast<int64_t>(frame_clock_now()));
m_flush_trigger.Set();
}
return length + extra;
@@ -80,6 +80,8 @@ private:
std::string m_save_directory;
Common::Event m_flush_trigger;
mpsc_stack_t m_flush_jobs{};
Common::Flag m_exiting;
Common::Flag m_flush_running{true};
// The frontend frame of the last block written; the flush waits for a quiet second after it.
retro_atomic_64_t m_last_write_frame{};
sthread_t* m_flush_thread = nullptr;
};
@@ -3,7 +3,6 @@
#include "Core/HW/GCMemcard/GCMemcardRaw.h"
#include <chrono>
#include <cstddef>
#include <cstdlib>
#include <cstring>
@@ -13,6 +12,7 @@
#include <fmt/format.h>
#include "Common/FrameClock.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
@@ -86,7 +86,8 @@ MemoryCard::~MemoryCard()
{
if (m_flush_thread)
{
m_flush_trigger.Set();
m_flush_running.Clear();
frame_clock_wake_all();
Common::JoinThread(m_flush_thread);
}
@@ -133,13 +134,14 @@ void MemoryCard::FlushThread()
Common::SetCurrentThreadName(fmt::format("Memcard {} flushing thread", m_card_slot).c_str());
const auto flush_interval = std::chrono::seconds(15);
// About fifteen seconds of frontend frames.
constexpr u64 FLUSH_INTERVAL_FRAMES = 15 * 60;
while (true)
{
// If triggered, we're exiting.
// If timed out, check if we need to flush.
const bool do_exit = m_flush_trigger.WaitFor(flush_interval);
// Stopped: flush once more and exit. Otherwise check whether there is anything to flush.
const bool do_exit = !frame_clock_wait_until(frame_clock_now() + FLUSH_INTERVAL_FRAMES,
m_flush_running.Raw());
if (!do_exit)
{
const bool is_dirty = m_dirty.TestAndClear();
+1 -1
View File
@@ -60,7 +60,7 @@ private:
std::unique_ptr<u8[]> m_flush_buffer;
mpsc_stack_t m_changes{};
sthread_t* m_flush_thread = nullptr;
Common::Event m_flush_trigger;
Common::Flag m_flush_running{true};
Common::Flag m_dirty;
u32 m_memory_card_size;
};
+4 -1
View File
@@ -10,6 +10,7 @@
#include <queues/mpsc_stack.h>
#include "Common/FrameClock.h"
#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "Common/Logging/Log.h"
@@ -102,13 +103,15 @@ static void GBAConnectionWaiter(void*)
new_client = std::make_unique<sf::TcpSocket>();
}
Common::SleepCurrentThread(1);
// Look for a connection once a frame.
frame_clock_wait_until(frame_clock_now() + 1, s_server_running.Raw());
}
}
void GBAConnectionWaiter_Shutdown()
{
s_server_running.Clear();
frame_clock_wake_all();
Common::JoinThread(s_connection_thread);
}
-8
View File
@@ -60,7 +60,6 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/PerformanceMetrics.h"
namespace SystemTimers
{
@@ -212,11 +211,6 @@ s64 SystemTimersManager::GetLocalTimeRTCOffset() const
return m_localtime_rtc_offset;
}
double SystemTimersManager::GetEstimatedEmulationPerformance() const
{
return m_system.GetPerfMetrics().GetMaxSpeed();
}
// split from Init to break a circular dependency between VideoInterface::Init and
// SystemTimers::Init
void SystemTimersManager::PreInit()
@@ -245,7 +239,6 @@ void SystemTimersManager::Init()
m_ipc_hle_period = GetTicksPerSecond() / freq;
}
Common::Timer::IncreaseResolution();
// store and convert localtime at boot to timebase ticks
if (Config::Get(Config::MAIN_CUSTOM_RTC_ENABLE))
{
@@ -290,7 +283,6 @@ void SystemTimersManager::Init()
void SystemTimersManager::Shutdown()
{
Common::Timer::RestoreResolution();
m_localtime_rtc_offset = 0;
}
-1
View File
@@ -87,7 +87,6 @@ public:
// - 0.5: the emulator is running at 50% speed (falling behind).
// - 1.0: the emulator is running at 100% speed.
// - 2.0: the emulator is running at 200% speed (or 100% speed but sleeping half of the time).
double GetEstimatedEmulationPerformance() const;
private:
static void DSPCallback(Core::System& system, u64 userdata, s64 cycles_late);
-20
View File
@@ -14,7 +14,6 @@
#include "Common/Logging/Log.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "Core/AchievementManager.h"
#include "Core/Config/GraphicsSettings.h"
@@ -886,16 +885,6 @@ void VideoInterfaceManager::EndField(FieldType field, u64 ticks)
if (!Config::Get(Config::GFX_HACK_EARLY_XFB_OUTPUT))
OutputField(field, ticks);
// Note: OutputField above doesn't present when using GPU-on-Thread or Early/Immediate XFB,
// giving "VBlank" measurements here poor pacing without a Throttle call.
// We'll throttle so long as Immediate XFB isn't enabled.
// That setting intends to minimize input latency and throttling would be counterproductive.
// The Rush Frame Presentation setting is handled by Throttle itself.
const bool is_vblank_data_wanted = !g_ActiveConfig.bImmediateXFB;
if (is_vblank_data_wanted)
m_system.GetCoreTiming().Throttle(ticks);
m_system.GetPerfMetrics().CountVBlank();
m_system.GetVideoEvents().vi_end_field_event.Trigger();
Core::OnFrameEnd(m_system);
}
@@ -944,14 +933,9 @@ void VideoInterfaceManager::Update(u64 ticks)
if (is_at_field_boundary)
Core::Callback_NewField(m_system);
auto& core_timing = m_system.GetCoreTiming();
// If an SI poll is scheduled to happen on this half-line, do it!
if (m_half_line_count == m_half_line_of_next_si_poll)
{
// Throttle before SI poll so user input is taken just before needed. (lower input latency)
core_timing.Throttle(ticks);
Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT),
Config::Get(Config::MAIN_LOCK_CURSOR));
auto& si = m_system.GetSerialInterface();
@@ -982,10 +966,6 @@ void VideoInterfaceManager::Update(u64 ticks)
m_ticks_last_line_start = ticks;
}
// TODO: Find out why skipping interrupts acts as a frameskip
if (core_timing.GetVISkip())
return;
// Check if we need to assert IR_INT. Note that the granularity of our current horizontal
// position is limited to half-lines.
-1
View File
@@ -70,7 +70,6 @@ namespace Wiimote
{
enum class InitializeMode
{
DO_WAIT_FOR_WIIMOTES,
DO_NOT_WAIT_FOR_WIIMOTES,
};
+4 -2
View File
@@ -21,7 +21,7 @@
#include "Common/Logging/Log.h"
#include "Common/Network.h"
#include "Common/ScopeGuard.h"
#include "Common/Thread.h"
#include "Common/FrameClock.h"
#include "Common/UnixUtil.h"
#include "Core/Config/MainSettings.h"
@@ -314,7 +314,9 @@ bool WiimoteLinux::ConnectInternal()
Common::LastStrerrorString());
// A socket state is unspecified after connect() fails, so close and recreate it.
close(descriptor);
Common::SleepCurrentThread(500);
// About half a second of frontend frames; give up if the remote is being dropped.
if (!frame_clock_wait_until(frame_clock_now() + 30, RunFlag()))
return -1;
}
return -1;
+79 -64
View File
@@ -18,7 +18,7 @@
#include "Common/Quiesce.h"
#include "Common/Swap.h"
#include "Common/Thread.h"
#include "Common/Timer.h"
#include "Common/FrameClock.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/WiimoteSettings.h"
@@ -95,17 +95,15 @@ static void EditKnownIds(Edit&& edit)
struct WiimotePoolEntry
{
using Clock = std::chrono::steady_clock;
std::unique_ptr<Wiimote> wiimote;
Clock::time_point entry_time = Clock::now();
u64 entry_frame = frame_clock_now();
bool IsExpired() const
{
// Keep wii remotes in the pool for a bit before disconnecting them.
constexpr auto POOL_TIME = std::chrono::seconds{5};
// Keep wii remotes in the pool for a bit, about five seconds, before disconnecting them.
constexpr u64 POOL_FRAMES = 5 * 60;
return (Clock::now() - entry_time) > POOL_TIME;
return frame_clock_now() - entry_frame > POOL_FRAMES;
}
};
@@ -131,8 +129,8 @@ struct ManagerCommand
std::unique_ptr<Wiimote> wiimote;
unsigned int index;
};
// The manager waits on the frame clock, so posting a command wakes it through that.
static mpsc_stack_t s_manager_commands;
static retro_eventcount_t s_manager_wake;
static retro_atomic_int_t s_manager_running;
static thread_local bool t_is_manager = false;
@@ -141,7 +139,7 @@ static void PostToManager(ManagerCommand::Kind kind, std::unique_ptr<Wiimote> wi
{
auto* command = new ManagerCommand{{}, kind, std::move(wiimote), index};
mpsc_stack_push(&s_manager_commands, &command->link);
retro_eventcount_notify(&s_manager_wake);
frame_clock_wake_all();
}
static void PutWiimoteInSlot(unsigned int index, std::unique_ptr<Wiimote> wiimote)
@@ -257,11 +255,11 @@ Wiimote::~Wiimote()
// Producer side of m_write_reports. A ring this deep is only full when the device has stopped
// taking reports, and the write thread is about to drop the connection then.
void Wiimote::QueueWrite(TimePoint time, const u8* data, size_t size)
void Wiimote::QueueWrite(u64 frame, const u8* data, size_t size)
{
ASSERT(size <= MAX_PAYLOAD);
WriteRecord record;
record.time = time;
record.frame = frame;
record.size = static_cast<u8>(size);
std::copy_n(data, size, record.data);
retro_spsc_write_frames(&m_write_reports, &record, 1, sizeof(record));
@@ -306,14 +304,8 @@ void Wiimote::WriteReport(Report rpt)
m_rumble_state = new_rumble_state;
}
auto& core_timing = Core::System::GetInstance().GetCoreTiming();
// When invoked from the CPU thread, send the report at the proper time.
// From other threads (e.g. on construction/destruction) send the report as soon as possible.
const auto report_time =
Core::IsCPUThread() ? core_timing.GetTargetHostTime(core_timing.GetTicks()) : Clock::now();
QueueWrite(report_time, rpt.data(), rpt.size());
// Sent as soon as the write thread gets to it.
QueueWrite(0, rpt.data(), rpt.size());
}
// to be called from CPU thread
@@ -428,6 +420,21 @@ bool Wiimote::Read()
return result != 0;
}
// Write thread: a report is queued and its frame has come.
bool Wiimote::IsWriteDue() const
{
if (retro_spsc_read_avail(&m_write_reports) < sizeof(WriteRecord))
return false;
u64 frame;
retro_spsc_peek(&m_write_reports, &frame, sizeof(frame));
return frame <= frame_clock_now();
}
void Wiimote::Poke()
{
retro_eventcount_notify(&m_write_event);
}
bool Wiimote::Write(const WriteRecord& record)
{
if (m_balance_board_dump_port > 0 && m_index == WIIMOTE_BALANCE_BOARD)
@@ -437,8 +444,6 @@ bool Wiimote::Write(const WriteRecord& record)
m_balance_board_dump_port);
}
// Write the report at the proper time, mainly for speaker data, not that it will help much.
Common::SleepUntil(record.time);
int ret = IOWrite(record.data, record.size);
return ret != 0;
@@ -618,16 +623,16 @@ ButtonData Wiimote::GetCurrentlyPressedButtons()
void Wiimote::Prepare()
{
const auto now = Clock::now();
const u64 now = frame_clock_now();
// Set reporting mode to non-continuous core buttons and turn on rumble.
const u8 mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1,
u8(InputReportID::ReportCore)};
QueueWrite(now, mode_report, sizeof(mode_report));
// Request status and turn off rumble.
// Request status and turn off rumble a dozen frames, about 200 ms, later.
const u8 req_status_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::RequestStatus), 0};
QueueWrite(now + std::chrono::milliseconds{200}, req_status_report, sizeof(req_status_report));
QueueWrite(now + 12, req_status_report, sizeof(req_status_report));
}
void Wiimote::EmuStop()
@@ -695,7 +700,7 @@ void WiimoteScanner::StopThread()
}
m_scan_thread_running.Clear();
retro_eventcount_notify(&s_manager_wake);
frame_clock_wake_all();
Common::JoinThread(m_scan_thread);
}
}
@@ -703,7 +708,8 @@ void WiimoteScanner::StopThread()
void WiimoteScanner::SetScanMode(WiimoteScanMode scan_mode)
{
m_scan_mode.store(scan_mode);
m_scan_mode_changed_or_population_event.Set();
retro_atomic_store_release_int(&m_scan_requested, 1);
frame_clock_wake_all();
}
// Manager only.
@@ -786,32 +792,38 @@ void WiimoteScanner::PoolThreadFunc()
// Toggle between 1010 and 0101.
u8 led_value = 0b1010;
constexpr auto TICK = std::chrono::milliseconds(250);
auto next_tick = std::chrono::steady_clock::now();
// About a quarter of a second of frontend frames.
constexpr u64 TICK_FRAMES = 15;
u64 next_tick = frame_clock_now();
while (m_scan_thread_running.IsSet())
{
RunManagerCommands();
const auto now = std::chrono::steady_clock::now();
if (now < next_tick)
if (frame_clock_now() < next_tick)
{
// Sleep until the next tick or a command, whichever comes first.
const int key = retro_eventcount_prepare_wait(&s_manager_wake);
if (!mpsc_stack_empty(&s_manager_commands) || !m_scan_thread_running.IsSet())
// Wait for the next tick or a command, whichever comes first.
const int key = frame_clock_prepare_wait();
if (!mpsc_stack_empty(&s_manager_commands) || !m_scan_thread_running.IsSet() ||
frame_clock_now() >= next_tick)
{
retro_eventcount_cancel_wait(&s_manager_wake);
frame_clock_cancel_wait();
continue;
}
retro_eventcount_commit_wait_timeout(
&s_manager_wake, key,
std::chrono::duration_cast<std::chrono::microseconds>(next_tick - now).count());
frame_clock_commit_wait(key);
continue;
}
next_tick = now + TICK;
next_tick = frame_clock_now() + TICK_FRAMES;
CheckForDisconnectedWiimotes();
// Let the write threads look at their idle time and delayed reports.
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
if (Wiimote* const wiimote = GetWiimote(i))
wiimote->Poke();
for (auto& entry : s_wiimote_pool)
entry.wiimote->Poke();
// Remove stale pool entries.
for (auto it = s_wiimote_pool.begin(); it != s_wiimote_pool.end();)
{
@@ -880,9 +892,24 @@ void WiimoteScanner::ThreadFunc()
published.push_back(backend.get());
retro_atomic_store_release_ptr(&m_backends_published, &published);
// About half a second of frontend frames between looks for remotes.
constexpr u64 SCAN_FRAMES = 30;
while (m_scan_thread_running.IsSet())
{
m_scan_mode_changed_or_population_event.WaitFor(std::chrono::milliseconds(500));
const u64 next_scan = frame_clock_now() + SCAN_FRAMES;
for (;;)
{
const int key = frame_clock_prepare_wait();
if (!m_scan_thread_running.IsSet() || frame_clock_now() >= next_scan ||
retro_atomic_exchange_int(&m_scan_requested, 0))
{
frame_clock_cancel_wait();
break;
}
frame_clock_commit_wait(key);
}
if (!m_scan_thread_running.IsSet())
break;
// Currently does nothing. To be removed.
for (const auto& backend : backends)
@@ -1001,9 +1028,9 @@ void Wiimote::WriteThreadFunc()
if (!ok)
{
// try again, it might take a moment to settle
Common::SleepCurrentThread(100);
ok = ConnectInternal();
// try again a few frames later, it might take a moment to settle
if (frame_clock_wait_until(frame_clock_now() + 6, m_run_thread.Raw()))
ok = ConnectInternal();
}
m_thread_ready_event.Set();
@@ -1019,22 +1046,24 @@ void Wiimote::WriteThreadFunc()
// to detect disconnections in a timely manner
// If we haven't written a report in some time, attempt a rumble-off report.
// This also has a minor benefit of preventing rumble from being stuck on.
constexpr auto WRITE_TEST_INTERVAL = std::chrono::milliseconds{1000};
// About a second of frontend frames. The pool manager pokes this thread every few frames,
// so it notices the time passing without a timed wait.
constexpr u64 WRITE_TEST_FRAMES = 60;
TimePoint last_write_time = Clock::now();
u64 last_write_frame = frame_clock_now();
while (m_run_thread.IsSet())
{
bool write_success = false;
if (retro_spsc_read_avail(&m_write_reports) >= sizeof(WriteRecord))
if (IsWriteDue())
{
// Send a normal report.
WriteRecord record;
retro_spsc_read(&m_write_reports, &record, sizeof(record));
write_success = Write(record);
}
else if (Clock::now() - last_write_time >= WRITE_TEST_INTERVAL)
else if (frame_clock_now() - last_write_frame >= WRITE_TEST_FRAMES)
{
// We haven't written in a while, test a write so we can check for a disconnect.
DEBUG_LOG_FMT(WIIMOTE, "Sending periodic write test for Wiimote {}.", m_index + 1);
@@ -1044,18 +1073,16 @@ void Wiimote::WriteThreadFunc()
}
else
{
// Nothing to do. Wait a while for a kick.
// Nothing to do. Wait for a report or a poke.
const int key = retro_eventcount_prepare_wait(&m_write_event);
if (retro_spsc_read_avail(&m_write_reports) >= sizeof(WriteRecord) || !m_run_thread.IsSet())
if (IsWriteDue() || !m_run_thread.IsSet())
retro_eventcount_cancel_wait(&m_write_event);
else
retro_eventcount_commit_wait_timeout(
&m_write_event, key,
std::chrono::duration_cast<std::chrono::microseconds>(WRITE_TEST_INTERVAL).count());
retro_eventcount_commit_wait(&m_write_event, key);
continue;
}
last_write_time = Clock::now();
last_write_frame = frame_clock_now();
if (!write_success)
{
@@ -1096,18 +1123,6 @@ void Initialize(::Wiimote::InitializeMode init_mode)
else
s_wiimote_scanner.SetScanMode(WiimoteScanMode::DO_NOT_SCAN);
// wait for connection because it should exist before state load
if (init_mode == ::Wiimote::InitializeMode::DO_WAIT_FOR_WIIMOTES)
{
int timeout = 100;
s_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE);
while (CalculateWantedWiimotes() && timeout)
{
Common::SleepCurrentThread(100);
timeout--;
}
}
if (s_real_wiimotes_initialized)
return;
@@ -88,6 +88,8 @@ public:
bool Connect(int index);
void Prepare();
// Wakes the write thread to look at its idle time and delayed reports.
void Poke();
virtual bool IsConnected() const = 0;
@@ -102,6 +104,9 @@ public:
int GetIndex() const;
protected:
// For waits on the write thread that end when the remote is being disconnected.
const retro_atomic_int_t* RunFlag() const { return m_run_thread.Raw(); }
// This needs to be called in derived destructors!
void Shutdown();
@@ -128,13 +133,14 @@ private:
};
struct WriteRecord
{
TimePoint time;
u64 frame; // first: the write thread peeks it. Sent once the frame clock reaches it.
u8 size;
u8 data[MAX_PAYLOAD];
};
static_assert(sizeof(ReadRecord) == 32 && sizeof(WriteRecord) == 32);
void QueueWrite(TimePoint time, const u8* data, size_t size);
void QueueWrite(u64 frame, const u8* data, size_t size);
bool IsWriteDue() const;
bool Read();
bool Write(const WriteRecord& record);
@@ -242,7 +248,7 @@ private:
sthread_t* m_scan_thread = nullptr;
Common::Flag m_scan_thread_running;
Common::Flag m_populate_devices;
Common::Event m_scan_mode_changed_or_population_event;
retro_atomic_int_t m_scan_requested{};
std::atomic<WiimoteScanMode> m_scan_mode{WiimoteScanMode::DO_NOT_SCAN};
};
-1
View File
@@ -62,7 +62,6 @@ void Host_UpdateDisasmDialog();
void Host_JitCacheInvalidation();
void Host_JitProfileDataWiped();
void Host_UpdateTitle(const std::string& title);
void Host_YieldToUI();
void Host_TitleChanged();
+3 -7
View File
@@ -86,10 +86,9 @@ IPCReply GetSpeedLimit(Core::System& system, const IOCtlVRequest& request)
return IPCReply(IPC_EINVAL);
}
const u32 speed_percent = Config::Get(Config::MAIN_EMULATION_SPEED) * 100;
// The frontend paces the core, which always runs at full speed.
auto& memory = system.GetMemory();
memory.Write_U32(speed_percent, request.io_vectors[0].address);
memory.Write_U32(100, request.io_vectors[0].address);
return IPCReply(IPC_SUCCESS);
}
@@ -107,10 +106,7 @@ IPCReply SetSpeedLimit(Core::System& system, const IOCtlVRequest& request)
return IPCReply(IPC_EINVAL);
}
auto& memory = system.GetMemory();
const float speed = float(memory.Read_U32(request.in_vectors[0].address)) / 100.0f;
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, speed);
// The frontend owns pacing; a limit asked for by the guest is accepted and has no effect.
return IPCReply(IPC_SUCCESS);
}
@@ -13,6 +13,7 @@
#include "Common/BitUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FrameClock.h"
#include "Common/Crypto/HMAC.h"
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
@@ -185,7 +186,8 @@ NetKDRequestDevice::~NetKDRequestDevice()
if (!m_scheduler_timer_thread)
return;
m_shutdown_event.Set();
m_scheduler_running.Clear();
frame_clock_wake_all();
Common::JoinThread(m_scheduler_timer_thread);
m_scheduler_work_queue.Shutdown();
@@ -262,7 +264,9 @@ void NetKDRequestDevice::SchedulerTimer()
}
}
if (m_shutdown_event.WaitFor(std::chrono::minutes{1}))
// A minute of frontend frames; the scheduler's spans are in minutes.
if (!frame_clock_wait_until(frame_clock_now() + FRAMES_PER_MINUTE,
m_scheduler_running.Raw()))
return;
mail_time_state++;
@@ -12,6 +12,7 @@
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/Flag.h"
#include "Common/HttpRequest.h"
#include "Common/WorkQueueThread.h"
#include "Core/IOS/Device.h"
@@ -133,7 +134,9 @@ private:
retro_atomic_int_t m_download_span{2};
retro_atomic_int_t m_mail_span{1};
bool m_handle_mail; // set before the threads start
Common::Event m_shutdown_event;
Common::Flag m_scheduler_running{true};
// A minute at 60 frames a second; 50 Hz games schedule a fifth slower, as WC24 allows.
static constexpr u64 FRAMES_PER_MINUTE = 60 * 60;
sthread_t* m_scheduler_timer_thread = nullptr;
};
} // namespace IOS::HLE
@@ -336,8 +336,6 @@ void BluetoothEmuDevice::Update()
if (now - m_last_ticks > interval)
{
// Throttle before Wii Remote update so input is taken just before needed. (lower input latency)
core_timing.Throttle(now);
g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth);
g_controller_interface.UpdateInput();
+3 -13
View File
@@ -104,8 +104,8 @@ std::optional<IPCReply> BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request
}
const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length);
m_lib_usb_bt_adapter->ScheduleControlTransfer(cmd->request_type, cmd->request, cmd->value,
cmd->index, payload, GetTargetTime());
m_lib_usb_bt_adapter->SendControlTransfer(cmd->request_type, cmd->request, cmd->value,
cmd->index, payload);
if (opcode == HCI_CMD_RESET)
{
@@ -133,7 +133,7 @@ std::optional<IPCReply> BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request
auto& memory = GetSystem().GetMemory();
const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length);
m_lib_usb_bt_adapter->ScheduleBulkTransfer(cmd->endpoint, payload, GetTargetTime());
m_lib_usb_bt_adapter->SendBulkTransfer(cmd->endpoint, payload);
break;
}
// Incoming HCI events
@@ -300,10 +300,6 @@ void BluetoothRealDevice::TryToFillACLEndpoint()
if (!m_acl_endpoint)
return;
// Throttle to minimize input latency.
auto& core_timing = GetSystem().GetCoreTiming();
core_timing.Throttle(core_timing.GetTicks());
const auto buffer = m_lib_usb_bt_adapter->PeekACLData();
if (buffer.empty())
return;
@@ -317,12 +313,6 @@ void BluetoothRealDevice::TryToFillACLEndpoint()
m_acl_endpoint.reset();
}
TimePoint BluetoothRealDevice::GetTargetTime() const
{
auto& core_timing = GetSystem().GetCoreTiming();
return core_timing.GetTargetHostTime(core_timing.GetTicks());
}
void BluetoothRealDevice::DoState(PointerWrap& p)
{
bool passthrough_bluetooth = true;
@@ -76,7 +76,6 @@ private:
std::unique_ptr<USB::V0BulkMessage> m_acl_endpoint;
// Used for proper Bluetooth packet timing, especially for Wii remote speaker data.
TimePoint GetTargetTime() const;
void TryToFillHCIEndpoint();
void TryToFillACLEndpoint();
@@ -2,13 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
#include "Common/Thread.h"
#include "Common/Timer.h"
#include <cassert>
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ranges>
@@ -21,6 +18,9 @@
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/HW/SystemTimers.h"
#include "Core/System.h"
#include "Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.h"
#include "Core/IOS/USB/Bluetooth/hci.h"
@@ -28,7 +28,13 @@ namespace
{
constexpr std::size_t BUFFER_SIZE = 1024;
constexpr auto HCI_COMMAND_TIMEOUT = std::chrono::milliseconds{1000};
// An HCI command not acknowledged within this much emulated time is given up on.
constexpr u64 HCI_COMMAND_TIMEOUT_MS = 1000;
u64 EmulatedTicks()
{
return Core::System::GetInstance().GetCoreTiming().GetTicks();
}
constexpr u8 REQUEST_TYPE =
u8(u8(LIBUSB_ENDPOINT_OUT) | u8(LIBUSB_REQUEST_TYPE_CLASS)) | u8(LIBUSB_RECIPIENT_DEVICE);
@@ -90,6 +96,7 @@ bool LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(u16 vid, u16 pid)
LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
{
retro_atomic_int_init(&m_run_input_transfers, 1);
retro_atomic_int_init(&m_input_failed, 0);
mpsc_stack_init(&m_transfers_to_free);
retro_spsc_init_mirrored(&m_hci_event_queue, INPUT_RING_SIZE);
retro_spsc_init_mirrored(&m_acl_data_queue, INPUT_RING_SIZE);
@@ -166,9 +173,6 @@ LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
StartInputTransfers();
m_output_worker.Reset("Bluetooth Output",
std::bind_front(&LibUSBBluetoothAdapter::SubmitTimedTransfer, this));
if (IsRealtekVID(m_device_vid) || IsKnownRealtekBluetoothDevice(m_device_vid, m_device_pid))
{
INFO_LOG_FMT(IOS_WIIMOTE, "Initializing Realtek Bluetooth device: {:04x}:{:04x}", m_device_vid,
@@ -192,17 +196,14 @@ LibUSBBluetoothAdapter::~LibUSBBluetoothAdapter()
void LibUSBBluetoothAdapter::Close()
{
// Wait for completion (or time out) of all HCI commands.
while (AreCommandsPendingResponse())
{
(void)ReceiveHCIEvent();
Common::YieldCPU();
}
// Commands still awaiting a reply are abandoned with their transfers below.
m_pending_hci_transfers = {};
m_unacknowledged_commands.clear();
m_output_worker.Shutdown();
// Stop the repeating input transfers.
retro_atomic_store_release_int(&m_run_input_transfers, 0);
// Stop the repeating input transfers. A callback resubmitting meanwhile sees the flag after
// its submit and cancels its own transfer (see HandleInputTransfer).
retro_atomic_exchange_int(&m_run_input_transfers, 0);
retro_atomic_thread_fence_seq_cst();
// Cancel all LibUSB transfers, wait for their callbacks and clean up all the buffers. A
// callback that read the run flag before it was cleared may resubmit its transfer after the
@@ -210,11 +211,11 @@ void LibUSBBluetoothAdapter::Close()
while (!m_transfer_buffers.empty())
{
std::ranges::for_each(m_transfer_buffers | std::ranges::views::keys, libusb_cancel_transfer);
const int key = retro_eventcount_prepare_wait(&m_transfer_freed.ec);
const int key = retro_eventcount_prepare_wait(&m_adapter_event.ec);
if (!mpsc_stack_empty(&m_transfers_to_free))
retro_eventcount_cancel_wait(&m_transfer_freed.ec);
retro_eventcount_cancel_wait(&m_adapter_event.ec);
else
retro_eventcount_commit_wait_timeout(&m_transfer_freed.ec, key, 10000);
retro_eventcount_commit_wait(&m_adapter_event.ec, key);
CleanCompletedTransfers();
}
@@ -227,10 +228,13 @@ void LibUSBBluetoothAdapter::Close()
void LibUSBBluetoothAdapter::Update()
{
// Remove timed out commands.
const auto expired_time = Clock::now() - HCI_COMMAND_TIMEOUT;
// Remove timed out commands. Emulated time stands still outside emulation, where the
// blocking callers wait for the reply itself.
const u64 timeout_ticks = Core::System::GetInstance().GetSystemTimers().GetTicksPerSecond() *
HCI_COMMAND_TIMEOUT_MS / 1000;
const u64 now = EmulatedTicks();
while (!m_unacknowledged_commands.empty() &&
m_unacknowledged_commands.front().submit_time < expired_time)
now - m_unacknowledged_commands.front().submit_ticks > timeout_ticks)
{
WARN_LOG_FMT(IOS_WIIMOTE, "HCI command 0x{:04x} timed out.",
m_unacknowledged_commands.front().opcode);
@@ -373,7 +377,7 @@ void LibUSBBluetoothAdapter::QueueFree(libusb_transfer* transfer)
FreeNode* const node = reinterpret_cast<FreeNode*>(transfer->buffer - BUFFER_HEADER_SIZE);
node->transfer = transfer;
mpsc_stack_push(&m_transfers_to_free, &node->link);
retro_eventcount_notify(&m_transfer_freed.ec);
retro_eventcount_notify(&m_adapter_event.ec);
}
void LibUSBBluetoothAdapter::CleanCompletedTransfers()
@@ -388,31 +392,19 @@ void LibUSBBluetoothAdapter::CleanCompletedTransfers()
}
}
void LibUSBBluetoothAdapter::PushHCICommand(TimedTransfer transfer)
void LibUSBBluetoothAdapter::PushHCICommand(libusb_transfer* transfer)
{
hci_cmd_hdr_t cmd;
std::memcpy(&cmd, libusb_control_transfer_get_data(transfer.transfer), sizeof(cmd));
std::memcpy(&cmd, libusb_control_transfer_get_data(transfer), sizeof(cmd));
// Push the time forward so our timeouts work properly if command was queued and delayed.
const auto submit_time = std::max(transfer.target_time, Clock::now());
m_unacknowledged_commands.emplace_back(OutstandingCommand{cmd.opcode, submit_time});
// Timed from now, not from when it was queued, so a delayed command gets its full time.
m_unacknowledged_commands.emplace_back(OutstandingCommand{cmd.opcode, EmulatedTicks()});
// This function is only invoked when this value is at least 1.
assert(m_num_hci_command_packets >= 1);
--m_num_hci_command_packets;
m_output_worker.EmplaceItem(transfer);
}
void LibUSBBluetoothAdapter::SubmitTimedTransfer(TimedTransfer transfer)
{
if (Config::Get(Config::MAIN_PRECISION_FRAME_TIMING))
m_precision_timer.SleepUntil(transfer.target_time);
else
Common::SleepUntil(transfer.target_time);
SubmitTransfer(transfer.transfer);
SubmitTransfer(transfer);
}
void LibUSBBluetoothAdapter::SubmitTransfer(libusb_transfer* transfer)
@@ -428,8 +420,7 @@ void LibUSBBluetoothAdapter::SubmitTransfer(libusb_transfer* transfer)
QueueFree(transfer);
}
void LibUSBBluetoothAdapter::ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data,
TimePoint target_time)
void LibUSBBluetoothAdapter::SendBulkTransfer(u8 endpoint, std::span<const u8> data)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
@@ -439,12 +430,11 @@ void LibUSBBluetoothAdapter::ScheduleBulkTransfer(u8 endpoint, std::span<const u
std::ranges::copy(data, transfer->buffer);
m_output_worker.EmplaceItem(transfer, target_time);
SubmitTransfer(transfer);
}
void LibUSBBluetoothAdapter::ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index,
std::span<const u8> data,
TimePoint target_time)
void LibUSBBluetoothAdapter::SendControlTransfer(u8 type, u8 request, u8 value, u8 index,
std::span<const u8> data)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
@@ -455,22 +445,20 @@ void LibUSBBluetoothAdapter::ScheduleControlTransfer(u8 type, u8 request, u8 val
std::ranges::copy(data, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE);
const TimedTransfer timed_transfer{transfer, target_time};
if (IsControllerReadyForCommand())
{
PushHCICommand(timed_transfer);
PushHCICommand(transfer);
}
else
{
DEBUG_LOG_FMT(IOS_WIIMOTE, "Queueing HCI command while controller is busy.");
m_pending_hci_transfers.emplace(timed_transfer);
m_pending_hci_transfers.emplace(transfer);
}
}
void LibUSBBluetoothAdapter::SendControlTransfer(std::span<const u8> data)
{
ScheduleControlTransfer(REQUEST_TYPE, 0, 0, 0, data, Clock::now());
SendControlTransfer(REQUEST_TYPE, 0, 0, 0, data);
}
bool LibUSBBluetoothAdapter::SendBlockingCommand(std::span<const u8> data, std::span<u8> response)
@@ -481,11 +469,25 @@ bool LibUSBBluetoothAdapter::SendBlockingCommand(std::span<const u8> data, std::
while (AreCommandsPendingResponse())
{
// Waits for the input callback to queue an event or to see the device fail.
const int key = retro_eventcount_prepare_wait(&m_adapter_event.ec);
if (PeekPacketLength(&m_hci_event_queue) == 0 &&
!retro_atomic_load_acquire_int(&m_input_failed))
{
retro_eventcount_commit_wait(&m_adapter_event.ec, key);
continue;
}
retro_eventcount_cancel_wait(&m_adapter_event.ec);
const auto event = ReceiveHCIEvent();
if (event.empty())
{
Common::YieldCPU();
if (retro_atomic_load_acquire_int(&m_input_failed))
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Bluetooth adapter stopped answering HCI commands.");
return false;
}
continue;
}
@@ -616,12 +618,22 @@ void LibUSBBluetoothAdapter::HandleInputTransfer(libusb_transfer* tr)
// Continuously repeat this input transfer so long as we're still running.
const auto ret = libusb_submit_transfer(tr);
if (ret == LIBUSB_SUCCESS)
{
// Close cleared the flag before cancelling; if that was after the check above, this
// submit may have missed its cancel, so it cancels itself.
retro_atomic_thread_fence_seq_cst();
if (!retro_atomic_load_acquire_int(&m_run_input_transfers))
libusb_cancel_transfer(tr);
retro_eventcount_notify(&m_adapter_event.ec);
return;
}
ERROR_LOG_FMT(IOS_WIIMOTE, "HandleInputTransfer libusb_submit_transfer: {}",
LibusbUtils::ErrorWrap(ret));
}
// No longer listening: nothing more will arrive for a blocking command to wait on.
retro_atomic_store_release_int(&m_input_failed, 1);
QueueFree(tr);
}
@@ -10,8 +10,6 @@
#include <vector>
#include "Common/Buffer.h"
#include "Common/Timer.h"
#include "Common/WorkQueueThread.h"
#include "Core/IOS/USB/Bluetooth/hci.h"
#include "Core/LibusbUtils.h"
@@ -55,16 +53,13 @@ public:
[[nodiscard]] std::span<const u8> PeekACLData();
void PopACLData();
// Schedule a transfer to submit at a specific time.
void ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data, TimePoint target_time);
void ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data,
TimePoint target_time);
// Schedule a transfer to be submitted as soon as possible.
// Submit a transfer now; libusb completes it asynchronously.
void SendBulkTransfer(u8 endpoint, std::span<const u8> data);
void SendControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data);
void SendControlTransfer(std::span<const u8> data);
// Blocks and eats events until a command complete event of the correct opcode is received.
// Returns true on success or false on error or timeout.
// Returns true on success or false on error or when the device stops responding.
// The written response does not include the event header or command complete data.
bool SendBlockingCommand(std::span<const u8> data, std::span<u8> response);
@@ -78,19 +73,22 @@ private:
std::string m_last_open_error;
// Callbacks on the libusb event thread notify this until that thread is joined, which happens
// when m_context goes, so it is declared first to be released last.
struct TransferFreedEvent
// Notified when a transfer is freed or an input transfer completes. Callbacks on the libusb
// event thread notify this until that thread is joined, which happens when m_context goes, so
// it is declared first to be released last.
struct AdapterEvent
{
TransferFreedEvent() { retro_eventcount_init(&ec); }
~TransferFreedEvent() { retro_eventcount_free(&ec); }
AdapterEvent() { retro_eventcount_init(&ec); }
~AdapterEvent() { retro_eventcount_free(&ec); }
retro_eventcount_t ec;
} m_transfer_freed;
} m_adapter_event;
LibusbUtils::Context m_context;
libusb_device_handle* m_handle = nullptr;
retro_atomic_int_t m_run_input_transfers;
// Set once an input transfer stops repeating, as when the device is gone.
retro_atomic_int_t m_input_failed;
// Callback and worker threads push transfers here, lock-free, when they are ready for clean up.
// The node lives in the front of the transfer's own buffer, so this allocates nothing.
@@ -112,19 +110,6 @@ private:
// All transfers are alloc'd and kept here until complete.
std::map<libusb_transfer*, Common::UniqueBuffer<u8>> m_transfer_buffers;
struct TimedTransfer
{
libusb_transfer* transfer{};
TimePoint target_time{};
};
// Outgoing data is submitted on a worker thread.
// This allows proper packet timing without throttling the core thread.
Common::WorkQueueThreadSP<TimedTransfer> m_output_worker;
// Used by the output worker.
Common::PrecisionTimer m_precision_timer;
// Set to reduce OSD and log spam.
bool m_showed_failed_transfer = false;
bool m_showed_long_queue_drop = false;
@@ -139,12 +124,12 @@ private:
// HCI commands are queued when the controller isn't ready for them.
// They will be sent after COMMAND_COMPL or COMMAND_STATUS signal readiness.
std::queue<TimedTransfer> m_pending_hci_transfers;
std::queue<libusb_transfer*> m_pending_hci_transfers;
struct OutstandingCommand
{
u16 opcode{};
TimePoint submit_time{};
u64 submit_ticks{}; // emulated time
};
// Sent HCI commands that have yet to receive a COMMAND_COMPL or COMMAND_STATUS response.
@@ -156,7 +141,7 @@ private:
// Give the transfer to the worker and track the command appropriately.
// This should only be used when IsControllerReadyForCommand is true.
void PushHCICommand(TimedTransfer);
void PushHCICommand(libusb_transfer*);
template <typename EventType>
void AcknowledgeCommand(std::span<const u8> buffer);
@@ -169,10 +154,6 @@ private:
// Do some HCI logic and clean up completed transfers.
void Update();
// Sleep until the target time and then submit the transfer.
// Used by worker thread.
void SubmitTimedTransfer(TimedTransfer);
// Immediately submit transfer with libusb.
void SubmitTransfer(libusb_transfer*);
+6 -1
View File
@@ -13,6 +13,7 @@
#include <libusb.h>
#endif
#include "Common/FrameClock.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Thread.h"
@@ -126,7 +127,8 @@ void USBScanner::ScanThread()
m_first_scan_complete_flag.Set(true);
if (m_notify_pending)
NotifyClients();
Common::SleepCurrentThread(50);
// Rescan every few frames, about 50 ms.
frame_clock_wait_until(frame_clock_now() + 3, m_thread_running.Raw());
}
PublishDevices({});
m_first_scan_complete_flag.Set(false);
@@ -135,7 +137,10 @@ void USBScanner::ScanThread()
void USBScanner::StopScanning()
{
if (m_thread_running.TestAndClear())
{
frame_clock_wake_all();
Common::JoinThread(m_thread);
}
}
USBScanner::DeviceMap USBScanner::GetDevices() const
-7
View File
@@ -35,7 +35,6 @@
#include "VideoCommon/CommandProcessor.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/GeometryShaderManager.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/PixelEngine.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
@@ -86,7 +85,6 @@ struct System::Impl
IOS::WiiIPC m_wii_ipc;
Memory::MemoryManager m_memory;
MemoryInterface::MemoryInterfaceManager m_memory_interface;
PerformanceMetrics m_perf_metrics;
PixelEngine::PixelEngineManager m_pixel_engine;
PixelShaderManager m_pixel_shader_manager;
PowerPC::PowerPCManager m_power_pc;
@@ -270,11 +268,6 @@ Movie::MovieManager& System::GetMovie() const
return m_impl->m_movie;
}
PerformanceMetrics& System::GetPerfMetrics() const
{
return m_impl->m_perf_metrics;
}
PixelEngine::PixelEngineManager& System::GetPixelEngine() const
{
return m_impl->m_pixel_engine;
-2
View File
@@ -10,7 +10,6 @@
class GeometryShaderManager;
class Interpreter;
class JitInterface;
class PerformanceMetrics;
class PixelShaderManager;
class SoundStream;
struct Sram;
@@ -187,7 +186,6 @@ public:
MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
PowerPC::MMU& GetMMU() const;
Movie::MovieManager& GetMovie() const;
PerformanceMetrics& GetPerfMetrics() const;
PixelEngine::PixelEngineManager& GetPixelEngine() const;
PixelShaderManager& GetPixelShaderManager() const;
PowerPC::PowerPCManager& GetPowerPC() const;
-42
View File
@@ -1,42 +0,0 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/TimePlayed.h"
#include <chrono>
#include <string>
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/NandPaths.h"
TimePlayed::TimePlayed() : m_ini_path(File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini")
{
Reload();
}
TimePlayed::~TimePlayed() = default;
void TimePlayed::AddTime(const std::string& game_id, std::chrono::milliseconds time_emulated)
{
std::string filtered_game_id = Common::EscapeFileName(game_id);
u64 previous_time;
m_time_list->Get(filtered_game_id, &previous_time);
m_time_list->Set(filtered_game_id, previous_time + static_cast<u64>(time_emulated.count()));
m_ini.Save(m_ini_path);
}
std::chrono::milliseconds TimePlayed::GetTimePlayed(const std::string& game_id) const
{
std::string filtered_game_id = Common::EscapeFileName(game_id);
u64 previous_time;
m_time_list->Get(filtered_game_id, &previous_time);
return std::chrono::milliseconds(previous_time);
}
void TimePlayed::Reload()
{
m_ini.Load(m_ini_path);
m_time_list = m_ini.GetOrCreateSection("TimePlayed");
}
-35
View File
@@ -1,35 +0,0 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <string>
#include "Common/CommonTypes.h"
#include "Common/IniFile.h"
class TimePlayed
{
public:
TimePlayed();
// not copyable due to the stored section pointer
TimePlayed(const TimePlayed& other) = delete;
TimePlayed(TimePlayed&& other) = delete;
TimePlayed& operator=(const TimePlayed& other) = delete;
TimePlayed& operator=(TimePlayed&& other) = delete;
~TimePlayed();
void AddTime(const std::string& game_id, std::chrono::milliseconds time_emulated);
std::chrono::milliseconds GetTimePlayed(const std::string& game_id) const;
void Reload();
private:
std::string m_ini_path;
Common::IniFile m_ini;
Common::IniFile::Section* m_time_list;
};
-17
View File
@@ -416,8 +416,6 @@ bool retro_load_game(const struct retro_game_info* game)
Config::SetBase(Config::MAIN_DPL2_DECODER, false);
Config::SetBase(Config::MAIN_AUDIO_FILL_GAPS, false);
Config::SetBase(Config::MAIN_EMULATION_SPEED,
Libretro::GetOption<double>(core::EMULATION_SPEED, /*def=*/0.0));
{
// Overclock (cpu clock rate) — option values in option_defs used strings like "100%" etc.
double multiplier = Libretro::GetOption<double>(core::CPU_CLOCK_RATE, 1.0);
@@ -425,10 +423,6 @@ bool retro_load_game(const struct retro_game_info* game)
Config::SetBase(Config::MAIN_OVERCLOCK_ENABLE, multiplier != 1.0);
}
Config::SetBase(Config::MAIN_PRECISION_FRAME_TIMING,
Libretro::GetOption<bool>(core::MAIN_PRECISION_FRAME_TIMING,
/*def=*/false)); // true is the standalone default
Config::SetBase(Config::MAIN_WIIMOTE_CONTINUOUS_SCANNING,
Libretro::GetOption<bool>(sysconf::WIIMOTE_CONTINUOUS_SCANNING,
/*def=*/false) ||
@@ -439,12 +433,6 @@ bool retro_load_game(const struct retro_game_info* game)
Config::SetBase(Config::MAIN_FAST_DISC_SPEED,
Libretro::GetOption<bool>(core::FAST_DISC_SPEED, /*def=*/false));
Config::SetBase(Config::MAIN_RUSH_FRAME_PRESENTATION,
Libretro::GetOption<bool>(core::RUSH_FRAME_PRESENTATION, /*def=*/false));
Config::SetBase(Config::MAIN_SMOOTH_EARLY_PRESENTATION,
Libretro::GetOption<bool>(core::SMOOTH_EARLY_PRESENTATION, /*def=*/false));
// Main.Interface
Config::SetBase(Config::MAIN_OSD_MESSAGES,
Libretro::GetOption<bool>(main_interface::OSD_ENABLED, /*def=*/true));
@@ -452,7 +440,6 @@ bool retro_load_game(const struct retro_game_info* game)
Libretro::GetOption<bool>(main_interface::ENABLE_DEBUGGING, /*def=*/false));
// Main.General
Config::SetBase(Config::MAIN_TIME_TRACKING, false);
// Main.DSP
Config::SetBase(Config::MAIN_DSP_JIT,
@@ -596,8 +583,6 @@ bool retro_load_game(const struct retro_game_info* game)
Libretro::GetOption<bool>(gfx_hacks::EFB_EMULATE_FORMAT_CHANGES, /*def=*/false));
Config::SetBase(Config::GFX_HACK_VERTEX_ROUNDING,
Libretro::GetOption<bool>(gfx_hacks::VERTEX_ROUNDING, /*def=*/false));
Config::SetBase(Config::GFX_HACK_VI_SKIP,
Libretro::GetOption<bool>(gfx_hacks::VI_SKIP, /*def=*/false));
Config::SetBase(Config::GFX_HACK_FAST_TEXTURE_SAMPLING,
Libretro::GetOption<bool>(gfx_hacks::FAST_TEXTURE_SAMPLING, /*def=*/true));
#ifdef __APPLE__
@@ -668,8 +653,6 @@ bool retro_load_game(const struct retro_game_info* game)
}
#endif
/* disable throttling emulation to match GetTargetRefreshRate() */
Core::SetIsThrottlerTempDisabled(true);
SConfig::GetInstance().bBootToPause = true;
#ifdef IPHONEOS
@@ -147,20 +147,6 @@ static struct retro_core_option_v2_definition option_defs[] = {
},
"1.00"
},
{
Libretro::Options::core::EMULATION_SPEED,
"Core > Emulation Speed",
"Emulation Speed",
"Set speed limit for emulation.",
nullptr,
CATEGORY_CORE,
{
{ "1.0", "100% (Normal Speed)" },
{ "0.0", "Unlimited" },
{ nullptr, nullptr }
},
"0.0"
},
{
Libretro::Options::core::MAIN_CPU_THREAD,
"Core > Dual Core Mode",
@@ -203,24 +189,6 @@ static struct retro_core_option_v2_definition option_defs[] = {
},
"disabled"
},
{
Libretro::Options::core::MAIN_PRECISION_FRAME_TIMING,
"Core > Precision Frame Timing",
"Precision Frame Timing",
"Use busy-wait for more accurate frame timing.",
nullptr,
CATEGORY_CORE,
{
{ "disabled", nullptr },
{ "enabled", nullptr },
{ nullptr, nullptr }
},
//#if defined(ANDROID)
"disabled"
//#else
// "enabled"
//#endif
},
{
Libretro::Options::core::FASTMEM,
"Core > Fastmem",
@@ -356,34 +324,6 @@ static struct retro_core_option_v2_definition option_defs[] = {
},
"disabled"
},
{
Libretro::Options::core::RUSH_FRAME_PRESENTATION,
"Core > Rush Frame Presentation",
"Rush Frame Presentation",
"Enable rushing frame presentation for lower latency.",
nullptr,
CATEGORY_CORE,
{
{ "disabled", nullptr },
{ "enabled", nullptr },
{ nullptr, nullptr }
},
"disabled"
},
{
Libretro::Options::core::SMOOTH_EARLY_PRESENTATION,
"Core > Smooth Early Presentation",
"Smooth Early Presentation",
"Enable smoother early frame presentation timing.",
nullptr,
CATEGORY_CORE,
{
{ "disabled", nullptr },
{ "enabled", nullptr },
{ nullptr, nullptr }
},
"disabled"
},
// ========== Main.Interface ==========
{
@@ -1452,20 +1392,6 @@ static struct retro_core_option_v2_definition option_defs[] = {
},
"disabled"
},
{
Libretro::Options::gfx_hacks::VI_SKIP,
"Graphics > Hacks > VI Skip",
"VI Skip",
"Skip VI updates to improve performance.",
nullptr,
CATEGORY_GFX_HACKS,
{
{ "disabled", nullptr },
{ "enabled", nullptr },
{ nullptr, nullptr }
},
"disabled"
},
{
Libretro::Options::gfx_hacks::FAST_TEXTURE_SAMPLING,
"Graphics > Hacks > Fast Texture Sampling",
@@ -162,11 +162,9 @@ inline std::string CPUCoreToString(PowerPC::CPUCore core)
namespace core {
constexpr const char CPU_CORE[] = "dolphin_cpu_core";
constexpr const char CPU_CLOCK_RATE[] = "dolphin_cpu_clock_rate";
constexpr const char EMULATION_SPEED[] = "dolphin_emulation_speed";
constexpr const char MAIN_CPU_THREAD[] = "dolphin_main_cpu_thread";
constexpr const char MAIN_SYNC_GPU[] = "dolphin_sync_gpu";
constexpr const char MAIN_LOAD_GAME_INTO_MEMORY[] = "dolphin_main_load_game_into_memory";
constexpr const char MAIN_PRECISION_FRAME_TIMING[] = "dolphin_precision_frame_timing";
constexpr const char FASTMEM[] = "dolphin_fastmem";
constexpr const char FASTMEM_ARENA[] = "dolphin_fastmem_arena";
constexpr const char MAIN_ACCURATE_CPU_CACHE[] = "dolphin_main_accurate_cpu_cache";
@@ -176,8 +174,6 @@ namespace core {
constexpr const char LANGUAGE[] = "dolphin_language";
constexpr const char FAST_DISC_SPEED[] = "dolphin_fast_disc_speed";
constexpr const char MAIN_MMU[] = "dolphin_main_mmu";
constexpr const char RUSH_FRAME_PRESENTATION[] = "dolphin_rush_presentation";
constexpr const char SMOOTH_EARLY_PRESENTATION[] = "dolphin_early_presentation";
} // namespace core
// ======================================================
@@ -302,7 +298,6 @@ namespace gfx_hacks {
constexpr const char EFB_SCALED_COPY[] = "dolphin_efb_scaled_copy";
constexpr const char EFB_EMULATE_FORMAT_CHANGES[] = "dolphin_efb_emulate_format_changes";
constexpr const char VERTEX_ROUNDING[] = "dolphin_vertex_rounding";
constexpr const char VI_SKIP[] = "dolphin_vi_skip";
constexpr const char FAST_TEXTURE_SAMPLING[] = "dolphin_fast_texture_sampling";
#ifdef __APPLE__
constexpr const char NO_MIPMAPPING[] = "dolphin_no_mipmapping";
-4
View File
@@ -67,10 +67,6 @@ bool Host_RendererIsFullscreen()
return false;
}
void Host_YieldToUI()
{
}
void Host_TitleChanged()
{
}
+4 -5
View File
@@ -10,6 +10,7 @@
#include "AudioCommon/AudioCommon.h"
#include "Common/ChunkFile.h"
#include "Common/Event.h"
#include "Common/FrameClock.h"
#include "Common/IniFile.h"
#include "Common/FileUtil.h"
#include "Common/GL/GLContext.h"
@@ -245,6 +246,9 @@ void retro_reset(void)
void retro_run(void)
{
// One retro_run is one frame of the core's background timebase.
frame_clock_advance();
Libretro::Input::InitSensors();
Libretro::Options::CheckForUpdatedVariables();
Libretro::FrameTiming::CheckForFastForwarding();
@@ -308,9 +312,6 @@ void retro_run(void)
g_gfx = std::make_unique<Libretro::Video::NullGfx>();
}
while (!Core::IsRunningOrStarting(system))
Common::SleepCurrentThread(100);
Libretro::g_core_refresh_rate = system.GetVideoInterface().GetTargetRefreshRate();
Libretro::s_refresh_rate_settled = false;
@@ -499,8 +500,6 @@ void retro_run(void)
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_hacks::VERTEX_ROUNDING))
Config::SetBase(Config::GFX_HACK_VERTEX_ROUNDING, Libretro::Options::GetCached<bool>(Libretro::Options::gfx_hacks::VERTEX_ROUNDING, false));
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_hacks::VI_SKIP))
Config::SetBase(Config::GFX_HACK_VI_SKIP, Libretro::Options::GetCached<bool>(Libretro::Options::gfx_hacks::VI_SKIP, false));
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_hacks::FAST_TEXTURE_SAMPLING))
Config::SetBase(Config::GFX_HACK_FAST_TEXTURE_SAMPLING, Libretro::Options::GetCached<bool>(Libretro::Options::gfx_hacks::FAST_TEXTURE_SAMPLING, true));
@@ -26,7 +26,7 @@ AsyncShaderCompiler::~AsyncShaderCompiler()
retro_eventcount_free(&m_work_done);
}
bool AsyncShaderCompiler::WaitForWorkDone(s64 timeout_us)
bool AsyncShaderCompiler::WaitForWorkDone()
{
const int key = retro_eventcount_prepare_wait(&m_work_done);
if (!HasPendingWork())
@@ -34,7 +34,7 @@ bool AsyncShaderCompiler::WaitForWorkDone(s64 timeout_us)
retro_eventcount_cancel_wait(&m_work_done);
return true;
}
retro_eventcount_commit_wait_timeout(&m_work_done, key, timeout_us);
retro_eventcount_commit_wait(&m_work_done, key);
return !HasPendingWork();
}
@@ -147,20 +147,6 @@ bool AsyncShaderCompiler::WaitUntilCompletion(
if (!HasPendingWork())
return true;
// Wait a second before reporting progress, woken as each item finishes.
// This way, if the operation completes quickly, we don't annoy the user.
constexpr s64 PROGRESS_INTERVAL_US = 1000000 / 30;
const TimePoint quiet_until = Clock::now() + std::chrono::seconds(1);
for (;;)
{
const s64 left_us =
std::chrono::duration_cast<std::chrono::microseconds>(quiet_until - Clock::now()).count();
if (left_us <= 0)
break;
if (WaitForWorkDone(left_us))
return true;
}
// Grab the number of pending items. We use this to work out how many are left.
const size_t total_items = m_completed_sync.size() + m_pending_work.size() + ItemsInSlots() +
retro_atomic_load_acquire_int(&m_busy_workers) + 1;
@@ -173,7 +159,8 @@ bool AsyncShaderCompiler::WaitUntilCompletion(
const size_t remaining_items = m_pending_work.size() + ItemsInSlots();
progress_callback(total_items - remaining_items, total_items);
WaitForWorkDone(PROGRESS_INTERVAL_US);
// Woken as each item finishes, to report it.
WaitForWorkDone();
}
return false;
}
@@ -67,8 +67,8 @@ protected:
virtual void WorkerThreadExit(void* param);
private:
// Waits up to timeout_us for a worker to finish an item; true once nothing is pending.
bool WaitForWorkDone(s64 timeout_us);
// Waits for a worker to finish an item; true once nothing is pending.
bool WaitForWorkDone();
void WorkerThreadStart();
void WorkerThreadEntryPoint(void* param);
+2 -3
View File
@@ -75,7 +75,7 @@ void FifoManager::PauseAndLock()
if (!m_system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
return;
m_gpu_mainloop.WaitYield(std::chrono::milliseconds(100), Host_YieldToUI);
m_gpu_mainloop.Wait();
}
void FifoManager::RestoreState(const bool was_running)
@@ -400,8 +400,7 @@ void FifoManager::RunGpuLoop()
g_vertex_manager->Flush();
g_framebuffer_manager->RefreshPeekCache();
}
},
100);
});
#ifdef __LIBRETRO__
m_stop_gpu_loop_requested.store(false);
#endif
-3
View File
@@ -19,7 +19,6 @@
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/FramebufferShaderGen.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/VertexManagerBase.h"
@@ -308,8 +307,6 @@ void OnScreenUI::DrawDebugText()
void OnScreenUI::Finalize()
{
auto& perf_metrics = Core::System::GetInstance().GetPerfMetrics();
perf_metrics.DrawImGuiStats(m_backbuffer_scale);
DrawDebugText();
OSD::DrawMessages();
ImGui::Render();
@@ -1,375 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/PerformanceMetrics.h"
#include <algorithm>
#include <imgui.h>
#include <implot.h>
#include "Common/HookableEvent.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/Core.h"
#include "VideoCommon/VideoConfig.h"
PerformanceMetrics::PerformanceMetrics()
{
const auto invalidate_counters_last_time = [this](Core::State) {
m_fps_counter.InvalidateLastTime();
m_vps_counter.InvalidateLastTime();
};
m_state_change_hook = Core::AddOnStateChangedCallback(invalidate_counters_last_time);
}
void PerformanceMetrics::Reset()
{
m_fps_counter.Reset();
m_vps_counter.Reset();
m_time_sleeping = DT::zero();
m_samples = {};
m_speed = 0;
m_max_speed = 0;
m_frame_presentation_offset = DT{};
}
void PerformanceMetrics::CountFrame()
{
m_fps_counter.Count();
}
void PerformanceMetrics::CountVBlank()
{
m_vps_counter.Count();
}
void PerformanceMetrics::CountThrottleSleep(DT sleep)
{
m_time_sleeping += sleep;
}
void PerformanceMetrics::AdjustClockSpeed(s64 ticks, u32 new_ppc_clock, u32 old_ppc_clock)
{
for (auto& sample : m_samples)
{
const s64 diff = (sample.core_ticks - ticks) * new_ppc_clock / old_ppc_clock;
sample.core_ticks = ticks + diff;
}
}
void PerformanceMetrics::CountPerformanceMarker(s64 core_ticks, u32 ticks_per_second)
{
const auto clock_time = Clock::now();
const auto work_time = clock_time - m_time_sleeping;
m_samples.emplace_back(
PerfSample{.clock_time = clock_time, .work_time = work_time, .core_ticks = core_ticks});
const auto sample_window = std::chrono::microseconds{g_ActiveConfig.iPerfSampleUSec};
while (clock_time - m_samples.front().clock_time > sample_window)
m_samples.pop_front();
// Avoid division by zero when we just have one sample.
if (m_samples.size() < 2)
return;
const PerfSample& oldest = m_samples.front();
const auto elapsed_core_time = DT_s(core_ticks - oldest.core_ticks) / ticks_per_second;
m_speed.store(elapsed_core_time / (clock_time - oldest.clock_time), std::memory_order_relaxed);
m_max_speed.store(elapsed_core_time / (work_time - oldest.work_time), std::memory_order_relaxed);
}
double PerformanceMetrics::GetFPS() const
{
return m_fps_counter.GetHzAvg();
}
double PerformanceMetrics::GetVPS() const
{
return m_vps_counter.GetHzAvg();
}
double PerformanceMetrics::GetSpeed() const
{
return m_speed.load(std::memory_order_relaxed);
}
double PerformanceMetrics::GetMaxSpeed() const
{
return m_max_speed.load(std::memory_order_relaxed);
}
void PerformanceMetrics::SetLatestFramePresentationOffset(DT offset)
{
m_frame_presentation_offset.store(offset, std::memory_order_relaxed);
}
void PerformanceMetrics::SetLatestFrameBufferSize(u32 width, u32 height)
{
m_frame_buffer_size.store(FrameBufferSize{width, height}, std::memory_order_relaxed);
}
void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale)
{
m_vps_counter.UpdateStats();
m_fps_counter.UpdateStats();
const bool movable_overlays = Config::Get(Config::GFX_MOVABLE_PERFORMANCE_METRICS);
const int movable_flag = movable_overlays ? ImGuiWindowFlags_None : ImGuiWindowFlags_NoMove;
const float bg_alpha = 0.7f;
const auto imgui_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | movable_flag |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
const double fps = GetFPS();
const double vps = GetVPS();
const double speed = GetSpeed();
static ImVec2 last_display_size(-1.0f, -1.0f);
// Change Color based on % Speed
float r = 0.0f, g = 1.0f, b = 1.0f;
if (g_ActiveConfig.bShowSpeedColors)
{
r = 1.0 - (speed - 0.8) / 0.2;
g = speed / 0.8;
b = (speed - 0.9) / 0.1;
}
const float window_padding = 8.f * backbuffer_scale;
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
const bool display_size_changed =
display_size.x != last_display_size.x || display_size.y != last_display_size.y;
last_display_size = display_size;
// There are too many edge cases to reasonably handle when the display size changes, so just reset
// the layout to default. Hopefully users aren't changing window sizes or resolutions too often.
const ImGuiCond set_next_position_condition =
(display_size_changed || !movable_overlays) ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
// Reset the graph size when changing resolutions, and otherwise let the user manually resize it.
const ImGuiCond set_next_size_condition =
display_size_changed ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
float window_y = window_padding;
float window_x = display_size.x - window_padding;
const auto clamp_window_position = [&] {
const ImVec2 position = ImGui::GetWindowPos();
const ImVec2 size = ImGui::GetWindowSize();
const float window_min_x = window_padding;
const float window_max_x = display_size.x - window_padding - size.x;
const float window_min_y = window_padding;
const float window_max_y = display_size.y - window_padding - size.y;
if (window_min_x > window_max_x || window_min_y > window_max_y)
return;
const float clamped_window_x = std::clamp(position.x, window_min_x, window_max_x);
const float clamped_window_y = std::clamp(position.y, window_min_y, window_max_y);
const bool window_needs_clamping =
(clamped_window_x != position.x) || (clamped_window_y != position.y);
if (window_needs_clamping)
ImGui::SetWindowPos(ImVec2(clamped_window_x, clamped_window_y), ImGuiCond_Always);
};
const float min_auto_graph_width = 200.f * backbuffer_scale + 2.f * window_padding;
const float min_auto_graph_height = 144.f * backbuffer_scale + 2.f * window_padding;
const float graph_width = std::max(min_auto_graph_width, display_size.x / 4.f);
const float graph_height = std::max(min_auto_graph_height, display_size.y / 4.f);
const bool stack_vertically = !g_ActiveConfig.bShowGraphs;
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 14.f * backbuffer_scale);
if (g_ActiveConfig.bShowGraphs)
{
// A font size of 13 is small enough to keep the tick numbers from overlapping too much.
ImGui::PushFont(nullptr, 13.0f);
ImGui::PushStyleColor(ImGuiCol_ResizeGrip, 0);
const auto graph_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | movable_flag |
ImGuiWindowFlags_NoFocusOnAppearing;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 4.f * backbuffer_scale));
// Position in the top-right corner of the screen.
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), set_next_position_condition,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowSize(ImVec2(graph_width, graph_height), set_next_size_condition);
ImGui::SetNextWindowBgAlpha(bg_alpha);
if (ImGui::Begin("PerformanceGraphs", nullptr, graph_flags))
{
static constexpr std::size_t num_ticks = 17;
static constexpr std::array<double, num_ticks> tick_marks = {0.0,
1000.0 / 360.0,
1000.0 / 240.0,
1000.0 / 180.0,
1000.0 / 120.0,
1000.0 / 90.00,
1000.0 / 59.94,
1000.0 / 40.00,
1000.0 / 29.97,
1000.0 / 24.00,
1000.0 / 20.00,
1000.0 / 15.00,
1000.0 / 10.00,
1000.0 / 5.000,
1000.0 / 2.000,
1000.0,
2000.0};
clamp_window_position();
window_y += ImGui::GetWindowHeight() + window_padding;
const DT vblank_time = m_vps_counter.GetDtAvg() + 2 * m_vps_counter.GetDtStd();
const DT frame_time = m_fps_counter.GetDtAvg() + 2 * m_fps_counter.GetDtStd();
const double target_max_time = DT_ms(vblank_time + frame_time).count();
const double a =
std::max(0.0, 1.0 - std::exp(-4.0 * (DT_s(m_fps_counter.GetLastRawDt()) /
DT_s(m_fps_counter.GetSampleWindow()))));
if (std::isfinite(m_graph_max_time))
m_graph_max_time += a * (target_max_time - m_graph_max_time);
else
m_graph_max_time = target_max_time;
const double total_frame_time =
DT_ms(std::max(m_fps_counter.GetSampleWindow(), m_vps_counter.GetSampleWindow())).count();
if (ImPlot::BeginPlot("PerformanceGraphs", ImVec2(-1.0, -1.0),
ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMenus |
ImPlotFlags_NoInputs))
{
ImPlot::PushStyleColor(ImPlotCol_PlotBg, {0, 0, 0, 0});
ImPlot::PushStyleColor(ImPlotCol_LegendBg, {0, 0, 0, 0.2f});
ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, ImVec2(0.f, 0.f));
ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, 1.5f * backbuffer_scale);
ImPlot::SetupAxes(nullptr, nullptr,
ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert |
ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight,
ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert | ImPlotAxisFlags_NoLabel |
ImPlotAxisFlags_NoHighlight);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
ImPlot::SetupAxisTicks(ImAxis_Y1, tick_marks.data(), num_ticks);
ImPlot::SetupAxesLimits(0, total_frame_time, 0, m_graph_max_time, ImGuiCond_Always);
ImPlot::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None);
m_vps_counter.ImPlotPlotLines("V-Blank (ms)");
m_fps_counter.ImPlotPlotLines("Frame (ms)");
ImPlot::EndPlot();
ImPlot::PopStyleVar(2);
ImPlot::PopStyleColor(2);
}
ImGui::PopStyleVar();
}
ImGui::End();
ImGui::PopFont();
ImGui::PopStyleColor();
}
if (g_ActiveConfig.bShowSpeed)
{
// Position in the top-right corner of the screen.
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), set_next_position_condition,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(bg_alpha);
if (ImGui::Begin("SpeedStats", nullptr, imgui_flags))
{
if (stack_vertically)
window_y += ImGui::GetWindowHeight() + window_padding;
else
window_x -= ImGui::GetWindowWidth() + window_padding;
clamp_window_position();
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", 100.0 * speed);
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Max:%6.0lf%%", 100.0 * GetMaxSpeed());
}
ImGui::End();
}
if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes)
{
// Position in the top-right corner of the screen.
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), set_next_position_condition,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(bg_alpha);
if (ImGui::Begin("FPSStats", nullptr, imgui_flags))
{
if (stack_vertically)
window_y += ImGui::GetWindowHeight() + window_padding;
else
window_x -= ImGui::GetWindowWidth() + window_padding;
clamp_window_position();
if (g_ActiveConfig.bShowFPS)
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "FPS:%7.2lf", fps);
if (g_ActiveConfig.bShowFTimes)
{
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "dt:%6.2lfms",
DT_ms(m_fps_counter.GetDtAvg()).count());
ImGui::TextColored(ImVec4(r, g, b, 1.0f), " ±:%6.2lfms",
DT_ms(m_fps_counter.GetDtStd()).count());
const auto offset =
DT_ms(m_frame_presentation_offset.load(std::memory_order_relaxed)).count();
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "ofs:%5.1lfms", offset);
}
}
ImGui::End();
}
if (g_ActiveConfig.bShowVPS || g_ActiveConfig.bShowVTimes)
{
// Position in the top-right corner of the screen.
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), set_next_position_condition,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(bg_alpha);
if (ImGui::Begin("VPSStats", nullptr, imgui_flags))
{
if (stack_vertically)
window_y += ImGui::GetWindowHeight() + window_padding;
else
window_x -= ImGui::GetWindowWidth() + window_padding;
clamp_window_position();
if (g_ActiveConfig.bShowVPS)
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "VPS:%7.2lf", vps);
if (g_ActiveConfig.bShowVTimes)
{
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "dt:%6.2lfms",
DT_ms(m_vps_counter.GetDtAvg()).count());
ImGui::TextColored(ImVec4(r, g, b, 1.0f), " ±:%6.2lfms",
DT_ms(m_vps_counter.GetDtStd()).count());
}
}
ImGui::End();
}
if (g_ActiveConfig.bShowInternalResolution)
{
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), set_next_position_condition,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(bg_alpha);
if (ImGui::Begin("ResolutionStats", nullptr, imgui_flags))
{
if (stack_vertically)
window_y += ImGui::GetWindowHeight() + window_padding;
else
window_x -= ImGui::GetWindowWidth() + window_padding;
clamp_window_position();
const FrameBufferSize size = m_frame_buffer_size.load(std::memory_order_relaxed);
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "XFB res: %ux%u", size.width, size.height);
}
ImGui::End();
}
ImGui::PopStyleVar(2);
}
@@ -1,82 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <deque>
#include "Common/CommonTypes.h"
#include "Common/HookableEvent.h"
#include "VideoCommon/PerformanceTracker.h"
namespace Core
{
class System;
}
class PerformanceMetrics
{
public:
PerformanceMetrics();
~PerformanceMetrics() = default;
PerformanceMetrics(const PerformanceMetrics&) = delete;
PerformanceMetrics& operator=(const PerformanceMetrics&) = delete;
PerformanceMetrics(PerformanceMetrics&&) = delete;
PerformanceMetrics& operator=(PerformanceMetrics&&) = delete;
void Reset();
void CountFrame();
void CountVBlank();
// Call from CPU thread.
void CountThrottleSleep(DT sleep);
void AdjustClockSpeed(s64 ticks, u32 new_ppc_clock, u32 old_ppc_clock);
void CountPerformanceMarker(s64 ticks, u32 ticks_per_second);
// Getter Functions. May be called from any thread.
double GetFPS() const;
double GetVPS() const;
double GetSpeed() const;
double GetMaxSpeed() const;
// Call from any thread.
void SetLatestFramePresentationOffset(DT offset);
void SetLatestFrameBufferSize(u32 width, u32 height);
// ImGui Functions
void DrawImGuiStats(const float backbuffer_scale);
private:
struct FrameBufferSize
{
FrameBufferSize() : width(0), height(0) {}
FrameBufferSize(u32 w, u32 h) : width(w), height(h) {}
u32 width;
u32 height;
};
PerformanceTracker m_fps_counter{"render_times.txt"};
PerformanceTracker m_vps_counter{"vblank_times.txt"};
double m_graph_max_time = 0.0;
std::atomic<double> m_speed{};
std::atomic<double> m_max_speed{};
std::atomic<DT> m_frame_presentation_offset{};
std::atomic<FrameBufferSize> m_frame_buffer_size{};
struct PerfSample
{
TimePoint clock_time;
TimePoint work_time;
s64 core_ticks;
};
std::deque<PerfSample> m_samples;
DT m_time_sleeping{};
Common::EventHook m_state_change_hook;
};
@@ -1,209 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/PerformanceTracker.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <utility>
#include <implot.h>
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/MathUtil.h"
#include "VideoCommon/VideoConfig.h"
static constexpr double SAMPLE_RC_RATIO = 0.25;
static constexpr u64 MAX_DT_QUEUE_SIZE = 1UL << 12;
static constexpr u64 MAX_QUALITY_GRAPH_SIZE = 1UL << 8;
PerformanceTracker::PerformanceTracker(std::optional<std::string> log_name,
const std::optional<DT> sample_window_duration)
: m_log_name{std::move(log_name)}, m_sample_window_duration{sample_window_duration}
{
retro_spsc_init(&m_raw_dts, MAX_DT_QUEUE_SIZE * sizeof(DT));
Reset();
}
PerformanceTracker::~PerformanceTracker()
{
retro_spsc_free(&m_raw_dts);
}
void PerformanceTracker::Reset()
{
retro_spsc_skip(&m_raw_dts, retro_spsc_read_avail(&m_raw_dts));
m_dt_queue.clear();
m_dt_total = DT::zero();
m_last_raw_dt = DT::zero();
m_last_time = Clock::now();
m_hz_avg = 0.0;
m_dt_avg = DT::zero();
m_dt_std = DT::zero();
m_is_last_time_sane = false;
}
void PerformanceTracker::Count()
{
const TimePoint current_time{Clock::now()};
const DT diff{current_time - m_last_time};
m_last_time = current_time;
if (!m_is_last_time_sane)
{
m_is_last_time_sane = true;
return;
}
m_last_raw_dt = diff;
// A full ring already holds the MAX_DT_QUEUE_SIZE samples UpdateStats keeps.
retro_spsc_write_frames(&m_raw_dts, &diff, 1, sizeof(diff));
}
void PerformanceTracker::UpdateStats()
{
DT diff{};
while (retro_spsc_read_avail(&m_raw_dts) >= sizeof(diff))
{
retro_spsc_read(&m_raw_dts, &diff, sizeof(diff));
HandleRawDt(diff);
}
// Update Std Dev
MathUtil::RunningVariance<double> variance;
for (auto dt : m_dt_queue)
variance.Push(DT_s(dt).count());
m_dt_std = std::chrono::duration_cast<DT>(DT_s(variance.PopulationStandardDeviation()));
}
void PerformanceTracker::HandleRawDt(DT diff)
{
if (m_dt_queue.size() == MAX_DT_QUEUE_SIZE)
PopBack();
PushFront(diff);
const DT window{GetSampleWindow()};
while (m_dt_total - m_dt_queue.back() >= window)
PopBack();
// Simple Moving Average Throughout the Window
const DT dt_avg = m_dt_total / m_dt_queue.size();
const double hz = DT_s(1.0) / dt_avg;
m_dt_avg = dt_avg;
// Exponential Moving Average
const DT_s rc = SAMPLE_RC_RATIO * std::min(window, m_dt_total);
const double a = 1.0 - std::exp(-(DT_s(diff) / rc));
// Sometimes euler averages can break when the average is inf/nan
const auto hz_avg = m_hz_avg.load();
if (std::isfinite(hz_avg))
m_hz_avg = hz_avg + a * (hz - hz_avg);
else
m_hz_avg = hz;
LogRenderTimeToFile(diff);
}
DT PerformanceTracker::GetSampleWindow() const
{
return m_sample_window_duration.value_or(
duration_cast<DT>(DT_us{std::max(1, g_ActiveConfig.iPerfSampleUSec)}));
}
double PerformanceTracker::GetHzAvg() const
{
return m_hz_avg;
}
DT PerformanceTracker::GetDtAvg() const
{
return m_dt_avg;
}
DT PerformanceTracker::GetDtStd() const
{
return m_dt_std;
}
DT PerformanceTracker::GetLastRawDt() const
{
return m_last_raw_dt;
}
void PerformanceTracker::InvalidateLastTime()
{
m_is_last_time_sane = false;
}
void PerformanceTracker::ImPlotPlotLines(const char* label) const
{
// "quality" graph uses twice as many points.
static_assert(MAX_QUALITY_GRAPH_SIZE * 2 <= MAX_DT_QUEUE_SIZE);
static std::array<float, MAX_DT_QUEUE_SIZE + 1> x, y;
if (m_dt_queue.empty())
return;
// Decides if there are too many points to plot using rectangles
const bool quality = m_dt_queue.size() < MAX_QUALITY_GRAPH_SIZE;
std::size_t point_index = 0;
const auto add_point = [&](DT dt, DT shift_x, float prev_ms) {
const float ms = DT_ms{dt}.count();
if (quality)
{
x[point_index] = prev_ms;
y[point_index] = ms;
++point_index;
}
x[point_index] = prev_ms + DT_ms{shift_x}.count();
y[point_index] = ms;
++point_index;
};
// Rightmost point.
const auto update_time = Clock::now() - m_last_time;
const auto predicted_frame_time = std::max(update_time, m_dt_queue.front());
add_point(predicted_frame_time, DT{}, 0);
// Other points, right to left.
for (auto dt : m_dt_queue)
add_point(dt, dt, x[point_index - 1]);
ImPlot::PlotLine(label, x.data(), y.data(), static_cast<int>(point_index));
}
void PerformanceTracker::PushFront(DT value)
{
m_dt_queue.push_front(value);
m_dt_total += value;
}
void PerformanceTracker::PopBack()
{
m_dt_total -= m_dt_queue.back();
m_dt_queue.pop_back();
}
void PerformanceTracker::LogRenderTimeToFile(DT val)
{
if (!m_log_name || !g_ActiveConfig.bLogRenderTimeToFile)
return;
if (!m_bench_file.is_open())
{
File::OpenFStream(m_bench_file, File::GetUserPath(D_LOGS_IDX) + *m_log_name,
std::ios_base::out);
}
m_bench_file << std::fixed << std::setprecision(8) << DT_ms(val).count() << std::endl;
}
@@ -1,77 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <deque>
#include <fstream>
#include <optional>
#include "Common/CommonTypes.h"
#include <retro_spsc.h>
class PerformanceTracker
{
public:
PerformanceTracker(std::optional<std::string> log_name = std::nullopt,
const std::optional<DT> sample_window_duration = std::nullopt);
~PerformanceTracker();
PerformanceTracker(const PerformanceTracker&) = delete;
PerformanceTracker& operator=(const PerformanceTracker&) = delete;
PerformanceTracker(PerformanceTracker&&) = delete;
PerformanceTracker& operator=(PerformanceTracker&&) = delete;
void Reset();
// Calls must come from the same thread.
// UpdateStats is expected to be called regularly to empty the SPSC queue.
void UpdateStats();
void ImPlotPlotLines(const char* label) const;
// May call from any thread, but not concurrently, not that you'd want to..
void Count();
// May call from any thread.
DT GetSampleWindow() const;
double GetHzAvg() const;
DT GetDtAvg() const;
DT GetDtStd() const;
DT GetLastRawDt() const;
void InvalidateLastTime();
private:
void LogRenderTimeToFile(DT val);
void HandleRawDt(DT value);
void PushFront(DT value);
void PopBack();
// Name of log file and file stream
std::optional<std::string> m_log_name;
std::ofstream m_bench_file;
// Last time Count() was called
TimePoint m_last_time;
std::atomic<bool> m_is_last_time_sane = false;
// Push'd from Count()
// and Pop'd from UpdateStats()
// Frame times from Count() to UpdateStats(), lock-free.
retro_spsc_t m_raw_dts;
std::atomic<DT> m_last_raw_dt = DT::zero();
// Amount of time to sample dt's over (defaults to config)
const std::optional<DT> m_sample_window_duration;
// Queue + Running Total used to calculate average dt
DT m_dt_total = DT::zero();
std::deque<DT> m_dt_queue;
// Average rate/time throughout the window
std::atomic<DT> m_dt_avg = DT::zero(); // Uses Moving Average
std::atomic<double> m_hz_avg = 0.0; // Uses Moving Average + Euler Average
std::atomic<DT> m_dt_std = DT::zero();
};
+6 -53
View File
@@ -172,15 +172,13 @@ bool Presenter::FetchXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_heigh
return old_xfb_id == m_last_xfb_id;
}
void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks,
TimePoint presentation_time)
void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks)
{
bool is_duplicate = FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks);
PresentInfo present_info{
.present_count = m_present_count++,
.emulated_timestamp = ticks,
.intended_present_time = presentation_time,
.frame_buffer_width = fb_width,
.frame_buffer_height = fb_height,
};
@@ -239,7 +237,7 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs)
{
Present(&present_info);
Present();
ProcessFrameDumping(ticks);
video_events.after_present_event.Trigger(present_info);
@@ -262,7 +260,6 @@ void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_
.frame_count = m_frame_count++,
.present_count = m_present_count++,
.emulated_timestamp = ticks,
.intended_present_time = m_next_swap_estimated_time,
.reason = PresentInfo::PresentReason::Immediate,
.frame_buffer_width = fb_width,
.frame_buffer_height = fb_height,
@@ -272,16 +269,15 @@ void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_
video_events.before_present_event.Trigger(present_info);
Present(&present_info);
Present();
ProcessFrameDumping(ticks);
video_events.after_present_event.Trigger(present_info);
}
void Presenter::SetNextSwapEstimatedTime(u64 ticks, TimePoint host_time)
void Presenter::SetNextSwapEstimatedTicks(u64 ticks)
{
m_next_swap_estimated_ticks = ticks;
m_next_swap_estimated_time = host_time;
}
void Presenter::ProcessFrameDumping(u64 ticks) const
@@ -902,7 +898,7 @@ void Presenter::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
}
}
void Presenter::Present(PresentInfo* present_info)
void Presenter::Present()
{
m_present_count++;
@@ -954,21 +950,7 @@ void Presenter::Present(PresentInfo* present_info)
}
// Present to the window system.
{
if (present_info != nullptr)
{
#ifndef __LIBRETRO__
const auto present_time = GetUpdatedPresentationTime(present_info->intended_present_time);
Core::System::GetInstance().GetCoreTiming().SleepUntil(present_time);
#endif
// Perhaps in the future a more accurate time can be acquired from the various backends.
present_info->actual_present_time = Clock::now();
present_info->present_time_accuracy = PresentInfo::PresentTimeAccuracy::PresentInProgress;
}
g_gfx->PresentBackbuffer();
}
g_gfx->PresentBackbuffer();
if (m_xfb_entry)
{
@@ -984,34 +966,6 @@ void Presenter::Present(PresentInfo* present_info)
g_gfx->EndUtilityDrawing();
}
TimePoint Presenter::GetUpdatedPresentationTime(TimePoint intended_presentation_time)
{
const auto now = Clock::now();
const auto arrival_offset = std::min(now - intended_presentation_time, DT{});
if (!Config::Get(Config::MAIN_SMOOTH_EARLY_PRESENTATION))
{
m_presentation_time_offset = arrival_offset;
// When SmoothEarlyPresentation is off and ImmediateXFB or RushFramePresentation are on,
// present as soon as possible as the goal is to achieve low input latency.
if (g_ActiveConfig.bImmediateXFB || Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION))
return now;
return intended_presentation_time;
}
// Adjust slowly backward in time but quickly forward in time.
// This keeps the pacing moderately smooth even if games produce regular sporadic bumps.
// This was tuned to handle the terrible pacing in Brawl with "Immediate XFB".
// Super Mario Galaxy 1 + 2 still perform poorly here in SingleCore mode.
const auto adjustment_divisor = (arrival_offset < m_presentation_time_offset) ? 100 : 2;
m_presentation_time_offset += (arrival_offset - m_presentation_time_offset) / adjustment_divisor;
return intended_presentation_time + m_presentation_time_offset;
}
void Presenter::DoState(PointerWrap& p)
{
p.Do(m_frame_count);
@@ -1028,7 +982,6 @@ void Presenter::DoState(PointerWrap& p)
GetVideoEvents().after_frame_event.Trigger(Core::System::GetInstance());
m_next_swap_estimated_ticks = m_last_xfb_ticks;
m_next_swap_estimated_time = Clock::now();
m_immediate_swap_happened_this_field.store(false, std::memory_order_relaxed);
+3 -12
View File
@@ -34,13 +34,12 @@ public:
Presenter();
virtual ~Presenter();
void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks,
TimePoint presentation_time);
void ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u64 ticks);
void ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height);
void SetNextSwapEstimatedTime(u64 ticks, TimePoint host_time);
void SetNextSwapEstimatedTicks(u64 ticks);
void Present(PresentInfo* present_info = nullptr);
void Present();
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::max(); }
bool Initialize();
@@ -164,17 +163,9 @@ private:
Common::EventHook m_config_changed;
Common::EventHook m_end_field_hook;
// Updates state for the SmoothEarlyPresentation setting if enabled.
// Returns the desired presentation time regardless.
TimePoint GetUpdatedPresentationTime(TimePoint intended_presentation_time);
// Used by the SmoothEarlyPresentation setting.
DT m_presentation_time_offset{};
// Calculated from the previous swap time and current refresh rate.
// Can be used for presentation of ImmediateXFB swaps which don't have timing information.
u64 m_next_swap_estimated_ticks = 0;
TimePoint m_next_swap_estimated_time{Clock::now()};
std::atomic_bool m_immediate_swap_happened_this_field{};
};
+3 -8
View File
@@ -101,15 +101,13 @@ void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride
return;
auto& system = Core::System::GetInstance();
auto& core_timing = system.GetCoreTiming();
if (!g_ActiveConfig.bImmediateXFB)
{
system.GetFifo().SyncGPU(Fifo::SyncGPUReason::Swap);
const TimePoint presentation_time = core_timing.GetTargetHostTime(ticks);
AsyncRequests::GetInstance()->PushEvent([=] {
g_presenter->ViSwap(xfb_addr, fb_width, fb_stride, fb_height, ticks, presentation_time);
g_presenter->ViSwap(xfb_addr, fb_width, fb_stride, fb_height, ticks);
});
}
@@ -121,11 +119,8 @@ void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride
const auto next_swap_estimated_ticks =
ticks + (system.GetSystemTimers().GetTicksPerSecond() * refresh_rate_den / refresh_rate_num);
const auto next_swap_estimated_time = core_timing.GetTargetHostTime(next_swap_estimated_ticks);
AsyncRequests::GetInstance()->PushEvent([=] {
g_presenter->SetNextSwapEstimatedTime(next_swap_estimated_ticks, next_swap_estimated_time);
});
AsyncRequests::GetInstance()->PushEvent(
[=] { g_presenter->SetNextSwapEstimatedTicks(next_swap_estimated_ticks); });
}
u32 VideoBackendBase::Video_GetQueryResult(PerfQueryType type)
+2 -5
View File
@@ -42,9 +42,7 @@ static Common::EventHook s_check_config_event;
static bool IsVSyncActive(bool enabled)
{
// Vsync is disabled when the throttler is disabled by the tab key.
return enabled && !Core::GetIsThrottlerTempDisabled() &&
Config::Get(Config::MAIN_EMULATION_SPEED) == 1.0;
return enabled;
}
void UpdateActiveConfig()
@@ -182,8 +180,7 @@ void VideoConfig::Refresh()
bDisableCopyToVRAM = Config::Get(Config::GFX_HACK_DISABLE_COPY_TO_VRAM);
bDeferEFBCopies = Config::Get(Config::GFX_HACK_DEFER_EFB_COPIES);
bImmediateXFB = Config::Get(Config::GFX_HACK_IMMEDIATE_XFB);
bVISkip = Config::Get(Config::GFX_HACK_VI_SKIP);
bSkipPresentingDuplicateXFBs = bVISkip || Config::Get(Config::GFX_HACK_SKIP_DUPLICATE_XFBS);
bSkipPresentingDuplicateXFBs = Config::Get(Config::GFX_HACK_SKIP_DUPLICATE_XFBS);
bCopyEFBScaled = Config::Get(Config::GFX_HACK_COPY_EFB_SCALED);
bEFBEmulateFormatChanges = Config::Get(Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES);
bVertexRounding = Config::Get(Config::GFX_HACK_VERTEX_ROUNDING);
-1
View File
@@ -301,7 +301,6 @@ struct VideoConfig final
bool bEnablePixelLighting = false;
bool bFastDepthCalc = false;
bool bVertexRounding = false;
bool bVISkip = false;
int iEFBAccessTileSize = 0;
int iSaveTargetId = 0; // TODO: Should be dropped
u32 iMissingColorValue = 0;
-25
View File
@@ -16,23 +16,6 @@ class System;
struct PresentInfo
{
enum class PresentTimeAccuracy
{
// The Driver/OS has given us an exact timestamp of when the first line of the frame started
// scanning out to the monitor
PresentOnScreenExact,
// An approximate timestamp of scanout.
PresentOnScreen,
// Dolphin doesn't have visibility of the present time. But the present operation has
// been queued with the GPU driver and will happen in the near future.
PresentInProgress,
// Not implemented
Unimplemented,
};
enum class PresentReason
{
Immediate, // FIFO is Presenting the XFB immediately, straight after the XFB copy
@@ -50,14 +33,6 @@ struct PresentInfo
// The exact emulated time of the when real hardware would have presented this frame
u64 emulated_timestamp = 0;
TimePoint intended_present_time{};
// AfterPresent only: The actual time the frame was presented
TimePoint actual_present_time{};
// Accuracy of actual_present_time
PresentTimeAccuracy present_time_accuracy = PresentTimeAccuracy::Unimplemented;
// Where the presentation of the frame was triggered from
PresentReason reason = PresentReason::Immediate;