mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 09:09:29 +01:00
Merge pull request #22838 from hrydgard/lag-sync-master
Lag sync: Fix drift and pacing. Set built-in relay to pump every millisecond
This commit is contained in:
5 files changed
+57
-38
No files matched your search
@@ -257,6 +257,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(instance), CmdParamType::Int, "instance", '\0', "Run as instance N of several talking to each other over ad hoc (own 127.0.0.N address and MAC)", CmdLineMode::Headless},
|
||||
{POFF(realtime), CmdParamType::Bool, "realtime", '\0', "Run at PSP speed instead of as fast as possible", CmdLineMode::Headless},
|
||||
{POFF(adhocServer), CmdParamType::Bool, "adhoc-server", '\0', "Run the built-in ad hoc server"},
|
||||
{POFF(lagSync), CmdParamType::Bool, "lag-sync", '\0', "Keep emulated time in step with the wall clock every millisecond, not every frame (the Force real clock sync setting)"},
|
||||
{POFF(frameStats), CmdParamType::Bool, "frame-stats", '\0', "Print flips per emulated second at the end of the run, counted from --state's load", CmdLineMode::Headless},
|
||||
{POFF(maxScreenshotError), CmdParamType::Double, "max-mse", '\0', "Maximum allowed MSE error for screenshot comparison", CmdLineMode::Headless},
|
||||
{POFF(mountIso), CmdParamType::String, "mount", 'm', "Mount ISO/CSO on umd1: when booting an ELF/PBP or --vsh"},
|
||||
@@ -573,6 +574,10 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.bEnableAdhocServer = adhocServer.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bEnableAdhocServer);
|
||||
}
|
||||
if (lagSync.has_value()) {
|
||||
g_Config.bForceLagSync = lagSync.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bForceLagSync);
|
||||
}
|
||||
if (fuseId.has_value()) {
|
||||
g_Config.sFuseID = fuseId.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.sFuseID);
|
||||
|
||||
@@ -200,6 +200,7 @@ struct CommandLineOptions {
|
||||
std::optional<int> instance;
|
||||
std::optional<bool> realtime;
|
||||
std::optional<bool> adhocServer;
|
||||
std::optional<bool> lagSync;
|
||||
|
||||
std::optional<std::string> screenshotFilename;
|
||||
std::optional<std::string> screenshotFilenameSave;
|
||||
|
||||
+43
-31
@@ -100,8 +100,16 @@ static std::vector<SceUID> vblankWakePending;
|
||||
static int afterFlipEvent = -1;
|
||||
static int lagSyncEvent = -1;
|
||||
|
||||
static double lastLagSync = 0.0;
|
||||
// Lag sync keeps emulated time in step with the wall clock: lagSyncEmuBase (emulated us) was reached at
|
||||
// lagSyncRealBase, and each sync waits for the wall clock to catch up from there.
|
||||
static double lagSyncRealBase = 0.0;
|
||||
static u64 lagSyncEmuBase = 0;
|
||||
static float lagSyncScale = 1.0f;
|
||||
static bool lagSyncScheduled = false;
|
||||
static const int LAG_SYNC_INTERVAL_US = 1000;
|
||||
// Further behind than this (a pause, or loading), start over from now instead of catching up. Like the
|
||||
// frame throttle's limit, it lets the odd slow frame be made up for.
|
||||
static const double LAG_SYNC_MAX_BEHIND = 0.1;
|
||||
|
||||
static int numSkippedFrames;
|
||||
static bool hasSetMode;
|
||||
@@ -178,16 +186,18 @@ static bool UseLagSync() {
|
||||
return g_Config.bForceLagSync && !UseAutoFrameSkip();
|
||||
}
|
||||
|
||||
static void ScheduleLagSync(int over = 0) {
|
||||
static void ResetLagSyncBase() {
|
||||
lagSyncRealBase = time_now_d();
|
||||
lagSyncEmuBase = CoreTiming::GetGlobalTimeUs();
|
||||
}
|
||||
|
||||
// Starts or stops lag sync to match the setting.
|
||||
static void ScheduleLagSync() {
|
||||
CoreTiming::UnscheduleEvent(lagSyncEvent, 0);
|
||||
lagSyncScheduled = UseLagSync();
|
||||
if (lagSyncScheduled) {
|
||||
// Reset over if it became too high, such as after pausing or initial loading.
|
||||
// There's no real sense in it being more than 1/60th of a second.
|
||||
if (over > 1000000 / framerate) {
|
||||
over = 0;
|
||||
}
|
||||
CoreTiming::ScheduleEvent(usToCycles(1000 + over), lagSyncEvent, 0);
|
||||
lastLagSync = time_now_d();
|
||||
ResetLagSyncBase();
|
||||
CoreTiming::ScheduleEvent(usToCycles(LAG_SYNC_INTERVAL_US), lagSyncEvent, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,9 +304,10 @@ void __DisplayDoState(PointerWrap &p) {
|
||||
Do(p, lagSyncScheduled);
|
||||
CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);
|
||||
if (p.mode == p.MODE_READ) {
|
||||
lastLagSync = time_now_d();
|
||||
if (lagSyncScheduled != UseLagSync()) {
|
||||
ScheduleLagSync();
|
||||
} else {
|
||||
ResetLagSyncBase();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -500,7 +511,9 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep,
|
||||
}
|
||||
}
|
||||
|
||||
if (curFrameTime < nextFrameTime && throttle) {
|
||||
// Lag sync paces emulation already. A wait here too would be out of phase with it, and it would race
|
||||
// to catch up afterwards.
|
||||
if (curFrameTime < nextFrameTime && throttle && !lagSyncScheduled) {
|
||||
// If time gap is huge just jump (somebody fast-forwarded)
|
||||
if (nextFrameTime - curFrameTime > 2*scaledTimestep) {
|
||||
nextFrameTime = curFrameTime;
|
||||
@@ -523,7 +536,7 @@ static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep,
|
||||
|
||||
static void DoFrameIdleTiming() {
|
||||
PROFILE_THIS_SCOPE("timing");
|
||||
if (!FrameTimingThrottled() || !g_Config.bEnableSound || wasPaused) {
|
||||
if (!FrameTimingThrottled() || !g_Config.bEnableSound || wasPaused || lagSyncScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -835,27 +848,26 @@ void hleLagSync(u64 userdata, int cyclesLate) {
|
||||
scale = (float)framerate / fpsLimit;
|
||||
}
|
||||
|
||||
const double goal = lastLagSync + (scale / 1000.0f);
|
||||
double before = time_now_d();
|
||||
// Don't lag too long ever, if they leave it paused.
|
||||
double now = before;
|
||||
while (now < goal && goal < now + 0.01) {
|
||||
// Tight loop on win32 - intentionally, as timing is otherwise not precise enough.
|
||||
// TODO: Use the precise waits if available
|
||||
#ifndef _WIN32
|
||||
const double left = goal - now;
|
||||
if (left > 0.0f && left < 1.0f) { // Sanity check
|
||||
usleep((long)(left * 1000000.0));
|
||||
}
|
||||
#else
|
||||
yield();
|
||||
#endif
|
||||
now = time_now_d();
|
||||
if (scale != lagSyncScale) {
|
||||
lagSyncScale = scale;
|
||||
ResetLagSyncBase();
|
||||
}
|
||||
|
||||
const int emuOver = (int)cyclesToUs(cyclesLate);
|
||||
const int over = (int)((now - goal) * 1000000);
|
||||
ScheduleLagSync(over - emuOver);
|
||||
// Measured from a fixed base rather than from the previous sync, so oversleeping doesn't add up.
|
||||
const double emulated = (double)(CoreTiming::GetGlobalTimeUs() - lagSyncEmuBase) / 1000000.0;
|
||||
const double goal = lagSyncRealBase + emulated * scale;
|
||||
const double before = time_now_d();
|
||||
if (before > goal + LAG_SYNC_MAX_BEHIND || goal > before + LAG_SYNC_MAX_BEHIND) {
|
||||
ResetLagSyncBase();
|
||||
} else if (before < goal) {
|
||||
WaitUntil(before, goal, "lag-sync");
|
||||
}
|
||||
const double now = time_now_d();
|
||||
|
||||
lagSyncScheduled = UseLagSync();
|
||||
if (lagSyncScheduled) {
|
||||
CoreTiming::ScheduleEvent(usToCycles(LAG_SYNC_INTERVAL_US) - cyclesLate, lagSyncEvent, 0);
|
||||
}
|
||||
|
||||
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || g_coreCollectDebugStats) {
|
||||
DisplayNotifySleep(now - before);
|
||||
|
||||
@@ -2133,14 +2133,12 @@ void __NetAdhocInit() {
|
||||
while (adhocServerRunning) {
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto pump_status = relay_server.pump();
|
||||
auto wait_time = std::chrono::milliseconds(8);
|
||||
if (pump_status == aemu_postoffice_server::ServerPumpStatus::LISTEN_SOCK_DEAD) {
|
||||
break;
|
||||
}
|
||||
if (pump_status == aemu_postoffice_server::ServerPumpStatus::IDLE) {
|
||||
wait_time = std::chrono::milliseconds(100);
|
||||
}
|
||||
std::this_thread::sleep_until(start + wait_time);
|
||||
// Every hop through the relay waits for the next pump, so keep them close together. Idle
|
||||
// (nobody connected) too, since that's when a game's first connection comes in.
|
||||
std::this_thread::sleep_until(start + std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
INFO_LOG(Log::sceNet, "RelayServer: Shutdown complete");
|
||||
|
||||
@@ -662,10 +662,13 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar
|
||||
int blockTicks = (int)usToCycles(1000000 / (opt.presses.empty() ? 10 : 60));
|
||||
if (opt.realtime) {
|
||||
// Display flips are the only thing that paces headless otherwise, and a test that doesn't
|
||||
// draw never flips, so the emulated clock would race ahead of instances it talks to.
|
||||
// draw never flips, so the emulated clock would race ahead of instances it talks to. The JIT
|
||||
// cores only return at the end of a frame, so like the app, this paces once per frame.
|
||||
blockTicks = (int)usToCycles(1000);
|
||||
const double ahead = emulatedElapsed - (time_now_d() - startTime);
|
||||
if (ahead > 0.001) {
|
||||
// With --lag-sync, lag sync paces every emulated millisecond instead, and two pacers out of
|
||||
// phase would undo each other.
|
||||
if (ahead > 0.001 && !g_Config.bForceLagSync) {
|
||||
sleep_ms((int)(std::min(ahead, 0.05) * 1000.0), "realtime");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user