Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions Core/Shared/Emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ Emulator::Emulator()
_historyViewer(new HistoryViewer(this)),
_gameServer(new GameServer(this)),
_gameClient(new GameClient(this)),
_rewindManager(new RewindManager(this))
_rewindManager(new RewindManager(this)),
_stats(new DebugStats())
{
_paused = false;
_pauseOnNextFrame = false;
Expand Down Expand Up @@ -127,8 +128,8 @@ void Emulator::Run()

_emulationThreadId = std::this_thread::get_id();

_stats->ResetStats();
_frameDelay = GetFrameDelay();
_stats.reset(new DebugStats());
_frameLimiter.reset(new FrameLimiter(_frameDelay));
_lastFrameTimer.Reset();

Expand Down Expand Up @@ -239,19 +240,21 @@ void Emulator::OnBeforeSendFrame()
_audioPlayerHud->Draw(GetFrameCount(), GetFps());
}

if(_stats && _settings->GetPreferences().ShowDebugInfo) {
if(_settings->GetPreferences().ShowDebugInfo) {
double lastFrameTime = _lastFrameTimer.GetElapsedMS();
_lastFrameTimer.Reset();
_stats->DisplayStats(this, lastFrameTime);
_stats->UpdateStats(this, false, lastFrameTime);
}
}
}

void Emulator::ProcessEndOfFrame()
{
bool useSpinWait = !_settings->GetVideoConfig().DisableHighPrecisionFramePacing && (_settings->GetEmulationSpeed() >= 50 && _settings->GetEmulationSpeed() <= 200);

if(!_isRunAheadFrame) {
_frameLimiter->ProcessFrame();
while(_frameLimiter->WaitForNextFrame()) {
while(_frameLimiter->WaitForNextFrame(useSpinWait)) {
if(_stopFlag || _frameDelay != GetFrameDelay() || _paused || _pauseOnNextFrame || _lockCounter > 0) {
//Need to process another event, stop sleeping
break;
Expand Down
1 change: 1 addition & 0 deletions Core/Shared/Emulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class Emulator
RewindManager* GetRewindManager() { return _rewindManager.get(); }
DebugHud* GetDebugHud() { return _debugHud.get(); }
DebugHud* GetScriptHud() { return _scriptHud.get(); }
DebugStats* GetDebugStats() { return _stats.get(); }
BatteryManager* GetBatteryManager() { return _batteryManager.get(); }
CheatManager* GetCheatManager() { return _cheatManager.get(); }
MovieManager* GetMovieManager() { return _movieManager.get(); }
Expand Down
23 changes: 19 additions & 4 deletions Core/Shared/FrameLimiter.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "Utilities/Timer.h"
#include "Utilities/PlatformUtilities.h"

class FrameLimiter
{
Expand Down Expand Up @@ -39,15 +40,29 @@ class FrameLimiter
_targetTime += _delay;
}

bool WaitForNextFrame()
bool WaitForNextFrame(bool useSpinWait)
{
if(_targetTime - _clockTimer.GetElapsedMS() > 50) {
int gap = (int)(_targetTime - _clockTimer.GetElapsedMS());
if(gap > 50) {
//When sleeping for a long time (e.g <= 25% speed), sleep in small chunks and check to see if we need to stop sleeping between each sleep call
_clockTimer.WaitUntil(_clockTimer.GetElapsedMS() + 40);
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(40));
return true;
}

_clockTimer.WaitUntil(_targetTime);
if(useSpinWait) {
if(gap >= 2) {
//2+ms left to wait, sleep until 1ms before the thread is meant to run
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(gap - 1));
}

while(_clockTimer.GetElapsedMS() < _targetTime) {
//Spin wait until the exact time, to improve frame pacing
PlatformUtilities::IdleLoop();
}
} else {
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(gap));
}

return false;
}
};
17 changes: 16 additions & 1 deletion Core/Shared/Interfaces/IRenderingDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ struct RenderSurfaceInfo
}
};

enum class FullscreenMode
{
Disabled,
Borderless,
Exclusive
};

struct FullscreenSettings
{
void* WindowHandle;
uint32_t Width;
uint32_t Height;
FullscreenMode Mode;
};

class IRenderingDevice
{
public:
Expand All @@ -46,5 +61,5 @@ class IRenderingDevice
virtual void Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) = 0;
virtual void Reset() = 0;
virtual void OnRendererThreadStarted() {}
virtual void SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) = 0;
virtual void SetFullscreenMode(FullscreenSettings settings) = 0;
};
4 changes: 2 additions & 2 deletions Core/Shared/SettingTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ struct VideoConfig
double NtscIFilterLength = 1.0;
double NtscQFilterLength = 1.0;

bool EnableVariableRefreshRate = false;
bool FullscreenForceIntegerScale = false;
bool UseExclusiveFullscreen = false;
uint32_t ExclusiveFullscreenRefreshRateNtsc = 60;
uint32_t ExclusiveFullscreenRefreshRatePal = 50;
uint32_t FullscreenResWidth = 0;
uint32_t FullscreenResHeight = 0;

uint32_t ScreenRotation = 0;
bool DisableHighPrecisionFramePacing = false;
};

struct AudioConfig
Expand Down
155 changes: 86 additions & 69 deletions Core/Shared/Video/DebugStats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,101 +6,118 @@
#include "Shared/Emulator.h"
#include "Shared/RewindManager.h"
#include "Shared/EmuSettings.h"
#include "Utilities/StringUtilities.h"

void DebugStats::DisplayStats(Emulator* emu, double lastFrameTime)
void DebugStats::ResetStats()
{
AudioStatistics stats = emu->GetSoundMixer()->GetStatistics();
AudioConfig audioCfg = emu->GetSettings()->GetAudioConfig();
DebugHud* hud = emu->GetDebugHud();
_core = {};
_render = {};
_skipFrames = 60;
}

_frameDurations[_frameDurationIndex] = lastFrameTime;
_frameDurationIndex = (_frameDurationIndex + 1) % 60;
void DebugStats::UpdateStats(Emulator* emu, bool forRender, double frameTime)
{
VideoStats& stats = forRender ? _render : _core;
uint32_t speed = emu->GetSettings()->GetEmulationSpeed();
if(frameTime > 100 && speed >= 25) {
//Ignore anything over 100ms, this is probably bad data caused by e.g the emulator being paused
//or the debug information being turned off and then back on.
return;
}

int startFrame = emu->GetFrameCount();
stats.LastFrameTime = frameTime;
stats.FrameDurations[stats.FrameDurationIndex] = frameTime;
stats.FrameDurationIndex = (stats.FrameDurationIndex + 1) % 60;

hud->DrawRectangle(8, 8, 115, 49, 0x40000000, true, 1, startFrame);
hud->DrawRectangle(8, 8, 115, 49, 0xFFFFFF, false, 1, startFrame);
if(speed > 0 && emu->GetFrameCount() > 1) {
if(_skipFrames && --_skipFrames) {
return;
}

hud->DrawString(10, 10, "Audio Stats", 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawString(10, 21, "Latency: ", 0xFFFFFF, 0xFF000000, 1, startFrame);
double expectedFps = emu->GetFps() * speed / 100;
double expectedFrameDelay = 1000 / expectedFps;
stats.MaxGap = std::max(stats.MaxGap, std::abs(frameTime - expectedFrameDelay));
} else {
stats.MaxGap = 0;
_skipFrames = 60;
}
}

void DebugStats::DisplayStats(Emulator* emu, DebugHud* hud)
{
AudioStatistics stats = emu->GetSoundMixer()->GetStatistics();
AudioConfig audioCfg = emu->GetSettings()->GetAudioConfig();

hud->DrawRectangle(8, 8, 115, 49, 0x40000000, true, 1);
hud->DrawRectangle(8, 8, 115, 49, 0xFFFFFF, false, 1);

hud->DrawString(10, 10, "Audio Stats", 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(10, 21, "Latency: ", 0xFFFFFF, 0xFF000000, 1);

int color = (stats.AverageLatency > 0 && std::abs(stats.AverageLatency - audioCfg.AudioLatency) > 3) ? 0xFF0000 : 0xFFFFFF;
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << stats.AverageLatency << " ms";
hud->DrawString(54, 21, ss.str(), color, 0xFF000000, 1, startFrame);
hud->DrawString(54, 21, StringUtilities::ToString(stats.AverageLatency, 2), color, 0xFF000000, 1);

hud->DrawString(10, 30, "Underruns: " + std::to_string(stats.BufferUnderrunEventCount), 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawString(10, 39, "Buffer Size: " + std::to_string(stats.BufferSize / 1024) + "kb", 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawString(10, 48, "Rate: " + std::to_string((uint32_t)(audioCfg.SampleRate * emu->GetSoundMixer()->GetRateAdjustment())) + "Hz", 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawString(10, 30, "Underruns: " + std::to_string(stats.BufferUnderrunEventCount), 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(10, 39, "Buffer Size: " + std::to_string(stats.BufferSize / 1024) + "kb", 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(10, 48, "Rate: " + std::to_string((uint32_t)(audioCfg.SampleRate * emu->GetSoundMixer()->GetRateAdjustment())) + "Hz", 0xFFFFFF, 0xFF000000, 1);

hud->DrawRectangle(132, 8, 115, 49, 0x40000000, true, 1, startFrame);
hud->DrawRectangle(132, 8, 115, 49, 0xFFFFFF, false, 1, startFrame);
hud->DrawString(134, 10, "Video Stats", 0xFFFFFF, 0xFF000000, 1, startFrame);
double expectedFps = emu->GetFps() * emu->GetSettings()->GetEmulationSpeed() / 100;
DrawVideoStats(130, 8, hud, false, expectedFps);
DrawVideoStats(256, 8, hud, true, expectedFps);

double totalDuration = 0;
for(int i = 0; i < 60; i++) {
totalDuration += _frameDurations[i];
}
hud->DrawRectangle(8, 59, 115, 33, 0x40000000, true, 1);
hud->DrawRectangle(8, 59, 115, 33, 0xFFFFFF, false, 1);

ss = std::stringstream();
ss << "FPS: " << std::fixed << std::setprecision(4) << (1000 / (totalDuration / 60));
hud->DrawString(134, 21, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawString(10, 62, "Misc. Stats", 0xFFFFFF, 0xFF000000, 1);

ss = std::stringstream();
ss << "Last Frame: " << std::fixed << std::setprecision(2) << lastFrameTime << " ms";
hud->DrawString(134, 30, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);
RewindStats rewindStats = emu->GetRewindManager()->GetStats();
double memUsage = (double)rewindStats.MemoryUsage / (1024 * 1024);
hud->DrawString(10, 73, "Rewind mem.: " + StringUtilities::ToString(memUsage, 2), 0xFFFFFF, 0xFF000000, 1);

if(emu->GetFrameCount() > 60) {
_lastFrameMin = std::min(lastFrameTime, _lastFrameMin);
_lastFrameMax = std::max(lastFrameTime, _lastFrameMax);
} else {
_lastFrameMin = 9999;
_lastFrameMax = 0;
if(rewindStats.HistoryDuration > 0) {
hud->DrawString(9, 82, " Per min.: " + StringUtilities::ToString(memUsage * 60 * 60 / rewindStats.HistoryDuration, 2), 0xFFFFFF, 0xFF000000, 1);
}
}

ss = std::stringstream();
ss << "Min Delay: " << std::fixed << std::setprecision(2) << ((_lastFrameMin < 9999) ? _lastFrameMin : 0.0) << " ms";
hud->DrawString(134, 39, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);
void DebugStats::DrawVideoStats(int x, int y, DebugHud* hud, bool forRender, double fps)
{
VideoStats& stats = forRender ? _render : _core;
hud->DrawRectangle(x, y, 120, 41, 0x40000000, true, 1);
hud->DrawRectangle(x, y, 120, 41, 0xFFFFFF, false, 1);
hud->DrawString(x + 2, y + 2, forRender ? "Renderer Stats" : "Core Stats", 0xFFFFFF, 0xFF000000, 1);

ss = std::stringstream();
ss << "Max Delay: " << std::fixed << std::setprecision(2) << _lastFrameMax << " ms";
hud->DrawString(134, 48, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);
double totalDuration = 0;
for(int i = 0; i < 60; i++) {
totalDuration += stats.FrameDurations[i];
}

hud->DrawRectangle(129, 59, 122, 32, 0xFFFFFF, false, 1, startFrame);
hud->DrawRectangle(130, 60, 120, 30, 0x000000, true, 1, startFrame);
hud->DrawString(x + 2, y + 13, "FPS: " + StringUtilities::ToString(1000 / (totalDuration / 60), 4), 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(x + 2, y + 22, "Last Frame: " + StringUtilities::ToString(stats.LastFrameTime, 2) + " ms", 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(x + 2, y + 31, "Max Gap: " + StringUtilities::ToString(stats.MaxGap, 2) + " ms", 0xFFFFFF, 0xFF000000, 1);
hud->DrawRectangle(x, y + 43, 120, 35, 0xFFFFFF, false, 1);
hud->DrawRectangle(x + 1, y + 44, 118, 33, 0x40000000, true, 1);

double expectedFrameDelay = 1000 / emu->GetFps();
double expectedFrameDelay = 1000 / fps;

for(int i = 0; i < 59; i++) {
double duration = _frameDurations[(_frameDurationIndex + i) % 60];
double nextDuration = _frameDurations[(_frameDurationIndex + i + 1) % 60];
double duration = stats.FrameDurations[(stats.FrameDurationIndex + i) % 60];
double nextDuration = stats.FrameDurations[(stats.FrameDurationIndex + i + 1) % 60];

duration = std::min(25.0, std::max(10.0, duration));
nextDuration = std::min(25.0, std::max(10.0, nextDuration));
double gap = std::max(-2.0, std::min(2.0, duration - expectedFrameDelay));
double nextGap = std::max(-2.0, std::min(2.0, nextDuration - expectedFrameDelay));

double maxGap = std::max(std::abs(gap), std::abs(nextGap));
int lineColor = 0x00FF00;
if(std::abs(duration - expectedFrameDelay) > 2) {
if(maxGap >= 2) {
lineColor = 0xFF0000;
} else if(std::abs(duration - expectedFrameDelay) > 1) {
lineColor = 0xFFA500;
} else if(maxGap >= 1) {
lineColor = 0xFF8500;
} else if(maxGap >= 0.5) {
lineColor = 0xFFC500;
} else if(maxGap >= 0.25) {
lineColor = 0xFFFF00;
}
hud->DrawLine(130 + i * 2, 60 + 50 - duration * 2, 130 + i * 2 + 2, 60 + 50 - nextDuration * 2, lineColor, 1, startFrame);
}

hud->DrawRectangle(8, 60, 115, 34, 0x40000000, true, 1, startFrame);
hud->DrawRectangle(8, 60, 115, 34, 0xFFFFFF, false, 1, startFrame);

hud->DrawString(10, 62, "Misc. Stats", 0xFFFFFF, 0xFF000000, 1, startFrame);

RewindStats rewindStats = emu->GetRewindManager()->GetStats();
double memUsage = (double)rewindStats.MemoryUsage / (1024 * 1024);
ss = std::stringstream();
ss << "Rewind mem.: " << std::fixed << std::setprecision(2) << memUsage << " MB";
hud->DrawString(10, 73, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);

if(rewindStats.HistoryDuration > 0) {
ss = std::stringstream();
ss << " Per min.: " << std::fixed << std::setprecision(2) << (memUsage * 60 * 60 / rewindStats.HistoryDuration) << " MB";
hud->DrawString(9, 82, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame);
hud->DrawLine(x + i * 2, y + 60 + std::round(gap * 8), x + i * 2 + 2, y + 60 + std::round(nextGap * 8), lineColor, 1);
}
}
22 changes: 17 additions & 5 deletions Core/Shared/Video/DebugStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@
#include "pch.h"

class Emulator;
class DebugHud;

struct VideoStats
{
double LastFrameTime = 0;
double FrameDurations[60] = {};
uint32_t FrameDurationIndex = 0;
double MaxGap = 0;
};

class DebugStats
{
private:
double _frameDurations[60] = {};
uint32_t _frameDurationIndex = 0;
double _lastFrameMin = 9999;
double _lastFrameMax = 0;
VideoStats _core;
VideoStats _render;
uint32_t _skipFrames = 0;

void DrawVideoStats(int x, int y, DebugHud* hud, bool forRender, double fps);

public:
void DisplayStats(Emulator* emu, double lastFrameTime);
void ResetStats();
void UpdateStats(Emulator* emu, bool forRender, double frameTime);
void DisplayStats(Emulator* emu, DebugHud* hud);
};
2 changes: 1 addition & 1 deletion Core/Shared/Video/SoftwareRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void SoftwareRenderer::Reset()
{
}

void SoftwareRenderer::SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle)
void SoftwareRenderer::SetFullscreenMode(FullscreenSettings settings)
{
//not supported
}
2 changes: 1 addition & 1 deletion Core/Shared/Video/SoftwareRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ class SoftwareRenderer : public IRenderingDevice
void ClearFrame() override;
void Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) override;
void Reset() override;
void SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) override;
void SetFullscreenMode(FullscreenSettings settings) override;
};
Loading
Loading