diff --git a/Core/Shared/Emulator.cpp b/Core/Shared/Emulator.cpp index 99a07e88d..fd64d8aff 100644 --- a/Core/Shared/Emulator.cpp +++ b/Core/Shared/Emulator.cpp @@ -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; @@ -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(); @@ -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; diff --git a/Core/Shared/Emulator.h b/Core/Shared/Emulator.h index 5f1dac636..42bc3deda 100644 --- a/Core/Shared/Emulator.h +++ b/Core/Shared/Emulator.h @@ -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(); } diff --git a/Core/Shared/FrameLimiter.h b/Core/Shared/FrameLimiter.h index d89a07b0b..629a4787c 100644 --- a/Core/Shared/FrameLimiter.h +++ b/Core/Shared/FrameLimiter.h @@ -1,5 +1,6 @@ #pragma once #include "Utilities/Timer.h" +#include "Utilities/PlatformUtilities.h" class FrameLimiter { @@ -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(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(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(gap)); + } + return false; } }; \ No newline at end of file diff --git a/Core/Shared/Interfaces/IRenderingDevice.h b/Core/Shared/Interfaces/IRenderingDevice.h index db61909ff..89f542718 100644 --- a/Core/Shared/Interfaces/IRenderingDevice.h +++ b/Core/Shared/Interfaces/IRenderingDevice.h @@ -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: @@ -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; }; \ No newline at end of file diff --git a/Core/Shared/SettingTypes.h b/Core/Shared/SettingTypes.h index 2a89d3b48..398fa85dd 100644 --- a/Core/Shared/SettingTypes.h +++ b/Core/Shared/SettingTypes.h @@ -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 diff --git a/Core/Shared/Video/DebugStats.cpp b/Core/Shared/Video/DebugStats.cpp index dbb97a655..2e304f5a4 100644 --- a/Core/Shared/Video/DebugStats.cpp +++ b/Core/Shared/Video/DebugStats.cpp @@ -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); } } diff --git a/Core/Shared/Video/DebugStats.h b/Core/Shared/Video/DebugStats.h index d064466e9..620170f8f 100644 --- a/Core/Shared/Video/DebugStats.h +++ b/Core/Shared/Video/DebugStats.h @@ -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); }; \ No newline at end of file diff --git a/Core/Shared/Video/SoftwareRenderer.cpp b/Core/Shared/Video/SoftwareRenderer.cpp index ea0d21511..e5322c6c1 100644 --- a/Core/Shared/Video/SoftwareRenderer.cpp +++ b/Core/Shared/Video/SoftwareRenderer.cpp @@ -91,7 +91,7 @@ void SoftwareRenderer::Reset() { } -void SoftwareRenderer::SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) +void SoftwareRenderer::SetFullscreenMode(FullscreenSettings settings) { //not supported } diff --git a/Core/Shared/Video/SoftwareRenderer.h b/Core/Shared/Video/SoftwareRenderer.h index ca5c78ad8..2e833dea4 100644 --- a/Core/Shared/Video/SoftwareRenderer.h +++ b/Core/Shared/Video/SoftwareRenderer.h @@ -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; }; \ No newline at end of file diff --git a/Core/Shared/Video/SystemHud.cpp b/Core/Shared/Video/SystemHud.cpp index ccde33d60..238fb795e 100644 --- a/Core/Shared/Video/SystemHud.cpp +++ b/Core/Shared/Video/SystemHud.cpp @@ -3,9 +3,8 @@ #include "Shared/Video/DebugHud.h" #include "Shared/Movies/MovieManager.h" #include "Shared/MessageManager.h" -#include "Shared/BaseControlManager.h" #include "Shared/Video/DrawStringCommand.h" -#include "Shared/Interfaces/IMessageManager.h" +#include "Utilities/StringUtilities.h" SystemHud::SystemHud(Emulator* emu) { @@ -78,7 +77,7 @@ void SystemHud::ShowFpsCounter(DebugHud* hud, uint32_t screenWidth, int lineNumb { int yPos = 10 + 10 * lineNumber; - string fpsString = string("FPS: ") + std::to_string(_currentFPS); // +" / " + std::to_string(_currentRenderedFPS); + string fpsString = string("FPS: ") + StringUtilities::ToString(_currentFPS, 2); uint32_t length = DrawStringCommand::MeasureString(fpsString).X; DrawString(hud, screenWidth, fpsString, screenWidth - 8 - length, yPos); } @@ -287,21 +286,14 @@ void SystemHud::UpdateHud() if(_lastFrameCount > frameCount) { _currentFPS = 0; } else { - _currentFPS = (int)(std::round((double)(frameCount - _lastFrameCount) / (_fpsTimer.GetElapsedMS() / 1000))); - _currentRenderedFPS = (int)(std::round((double)(_renderedFrameCount - _lastRenderedFrameCount) / (_fpsTimer.GetElapsedMS() / 1000))); + _currentFPS = (std::round((double)(frameCount - _lastFrameCount) / (_fpsTimer.GetElapsedMS() / 1000) * 100)) / 100; } _lastFrameCount = frameCount; - _lastRenderedFrameCount = _renderedFrameCount; _fpsTimer.Reset(); } if(_currentFPS > 5000) { _currentFPS = 0; } - if(_currentRenderedFPS > 5000) { - _currentRenderedFPS = 0; - } - - _renderedFrameCount++; } } \ No newline at end of file diff --git a/Core/Shared/Video/SystemHud.h b/Core/Shared/Video/SystemHud.h index f2ed88652..60c084661 100644 --- a/Core/Shared/Video/SystemHud.h +++ b/Core/Shared/Video/SystemHud.h @@ -21,10 +21,7 @@ class SystemHud final : public IMessageManager Timer _fpsTimer; Timer _animationTimer; uint32_t _lastFrameCount = 0; - uint32_t _lastRenderedFrameCount = 0; - uint32_t _currentFPS = 0; - uint32_t _currentRenderedFPS = 0; - uint32_t _renderedFrameCount = 0; + double _currentFPS = 0; void DrawMessages(DebugHud* hud, uint32_t screenWidth, uint32_t screenHeight) const; void DrawBar(DebugHud* hud, int x, int y, int width, int height) const; diff --git a/Core/Shared/Video/VideoDecoder.cpp b/Core/Shared/Video/VideoDecoder.cpp index 71a2fb491..d907c4e00 100644 --- a/Core/Shared/Video/VideoDecoder.cpp +++ b/Core/Shared/Video/VideoDecoder.cpp @@ -1,5 +1,4 @@ #include "pch.h" -#include "Shared/Interfaces/IRenderingDevice.h" #include "Shared/Video/VideoDecoder.h" #include "Shared/Video/VideoRenderer.h" #include "Shared/Video/BaseVideoFilter.h" @@ -12,10 +11,7 @@ #include "Shared/Video/RotateFilter.h" #include "Shared/Video/ScanlineFilter.h" #include "Shared/Video/DebugHud.h" -#include "Shared/InputHud.h" #include "Shared/RenderedFrame.h" -#include "Shared/Video/SystemHud.h" -#include "SNES/CartTypes.h" VideoDecoder::VideoDecoder(Emulator* emu) { diff --git a/Core/Shared/Video/VideoRenderer.cpp b/Core/Shared/Video/VideoRenderer.cpp index 35116ae7f..4a6a2b49b 100644 --- a/Core/Shared/Video/VideoRenderer.cpp +++ b/Core/Shared/Video/VideoRenderer.cpp @@ -6,6 +6,7 @@ #include "Shared/EmuSettings.h" #include "Shared/Video/DebugHud.h" #include "Shared/Video/SystemHud.h" +#include "Shared/Video/DebugStats.h" #include "Shared/InputHud.h" #include "Shared/MessageManager.h" #include "Utilities/Video/IVideoRecorder.h" @@ -73,6 +74,8 @@ void VideoRenderer::RenderThread() _renderer->OnRendererThreadStarted(); } + Timer lastFrameTimer; + bool needClearHud = false; while(!_stopFlag.load()) { //Wait until a frame is ready, or until 32ms have passed (to allow HUD to update at ~30fps when paused) bool forceRender = !_waitForRender.Wait(32); @@ -91,15 +94,32 @@ void VideoRenderer::RenderThread() frame = _lastFrame; } + if(needClearHud) { + _emuHudSurface.Clear(); + _rendererHud->ClearScreen(); + } + _inputHud->DrawControllers(size, frame.InputData); + { auto lock = _hudLock.AcquireSafe(); _systemHud->Draw(_rendererHud.get(), size.Width, size.Height); } - _emuHudSurface.IsDirty = _rendererHud->Draw(_emuHudSurface.Buffer, size, {}, 0, {}, true); + bool showDebugInfo = _emu->GetSettings()->GetPreferences().ShowDebugInfo; + if(showDebugInfo) { + double lastFrameTime = lastFrameTimer.GetElapsedMS(); + lastFrameTimer.Reset(); + _emu->GetDebugStats()->UpdateStats(_emu, true, lastFrameTime); + _emu->GetDebugStats()->DisplayStats(_emu, _rendererHud.get()); + needClearHud = true; + } + + _emuHudSurface.IsDirty = _rendererHud->Draw(_emuHudSurface.Buffer, size, {}, 0, {}, !needClearHud); _scriptHudSurface.IsDirty = DrawScriptHud(frame); + needClearHud = showDebugInfo; + if(forceRender || _needRedraw || _emuHudSurface.IsDirty || _scriptHudSurface.IsDirty) { _needRedraw = false; _renderer->Render(_emuHudSurface, _scriptHudSurface); diff --git a/InteropDLL/EmuApiWrapper.cpp b/InteropDLL/EmuApiWrapper.cpp index 1417fc6a6..c68518773 100644 --- a/InteropDLL/EmuApiWrapper.cpp +++ b/InteropDLL/EmuApiWrapper.cpp @@ -133,10 +133,10 @@ extern "C" } } - DllExport void __stdcall SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) + DllExport void __stdcall SetFullscreenMode(FullscreenSettings settings) { if(_renderer) { - _renderer->SetExclusiveFullscreenMode(fullscreen, windowHandle); + _renderer->SetFullscreenMode(settings); } } diff --git a/Sdl/SdlRenderer.cpp b/Sdl/SdlRenderer.cpp index cf8f3f18f..22b9d8f41 100644 --- a/Sdl/SdlRenderer.cpp +++ b/Sdl/SdlRenderer.cpp @@ -33,7 +33,7 @@ void SdlRenderer::LogSdlError(const char* msg) MessageManager::Log(SDL_GetError()); } -void SdlRenderer::SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) +void SdlRenderer::SetFullscreenMode(FullscreenSettings settings) { //TODO: Implement exclusive fullscreen for Linux } diff --git a/Sdl/SdlRenderer.h b/Sdl/SdlRenderer.h index b8a3e9e6d..66553a85c 100644 --- a/Sdl/SdlRenderer.h +++ b/Sdl/SdlRenderer.h @@ -68,5 +68,5 @@ class SdlRenderer : public IRenderingDevice void Reset() override; void OnRendererThreadStarted() override; - void SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) override; + void SetFullscreenMode(FullscreenSettings settings) override; }; diff --git a/UI/Config/VideoConfig.cs b/UI/Config/VideoConfig.cs index d8326afbf..1153fa9e8 100644 --- a/UI/Config/VideoConfig.cs +++ b/UI/Config/VideoConfig.cs @@ -47,6 +47,7 @@ public class VideoConfig : BaseConfig [Reactive][MinMax(0, 400)] public Int32 NtscIFilterLength { get; set; } = 50; [Reactive][MinMax(0, 400)] public Int32 NtscQFilterLength { get; set; } = 50; + [Reactive] public bool EnableVariableRefreshRate { get; set; } = false; [Reactive] public bool FullscreenForceIntegerScale { get; set; } = false; [Reactive] public bool UseExclusiveFullscreen { get; set; } = false; [Reactive] public UInt32 ExclusiveFullscreenRefreshRateNtsc { get; set; } = 60; @@ -54,6 +55,7 @@ public class VideoConfig : BaseConfig [Reactive] public FullscreenResolution ExclusiveFullscreenResolution { get; set; } = 0; [Reactive] public ScreenRotation ScreenRotation { get; set; } = ScreenRotation.None; + [Reactive] public bool DisableHighPrecisionFramePacing { get; set; } = false; public VideoConfig() { @@ -81,7 +83,7 @@ public void ApplyConfig() AspectRatio = aspectRatio, UseBilinearInterpolation = this.UseBilinearInterpolation, - UseSrgbTextureFormat = this.UseSrgbTextureFormat, + UseSrgbTextureFormat = this.UseSrgbTextureFormat && this.UseBilinearInterpolation, VerticalSync = this.VerticalSync, IntegerFpsMode = this.IntegerFpsMode, @@ -109,16 +111,37 @@ public void ApplyConfig() NtscIFilterLength = this.NtscIFilterLength / 100.0, NtscQFilterLength = this.NtscQFilterLength / 100.0, + EnableVariableRefreshRate = this.EnableVariableRefreshRate, + FullscreenForceIntegerScale = this.FullscreenForceIntegerScale, UseExclusiveFullscreen = this.UseExclusiveFullscreen, ExclusiveFullscreenRefreshRateNtsc = this.ExclusiveFullscreenRefreshRateNtsc, ExclusiveFullscreenRefreshRatePal = this.ExclusiveFullscreenRefreshRatePal, - FullscreenResWidth = (uint)(ExclusiveFullscreenResolution == FullscreenResolution.Default ? (ApplicationHelper.GetMainWindow()?.Screens.Primary?.Bounds.Width ?? 1920) : ExclusiveFullscreenResolution.GetWidth()), - FullscreenResHeight = (uint)(ExclusiveFullscreenResolution == FullscreenResolution.Default ? (ApplicationHelper.GetMainWindow()?.Screens.Primary?.Bounds.Height ?? 1080) : ExclusiveFullscreenResolution.GetHeight()), - ScreenRotation = (uint)ScreenRotation + ScreenRotation = (uint)ScreenRotation, + DisableHighPrecisionFramePacing = this.DisableHighPrecisionFramePacing }); } + + public UInt32 GetFullscreenWidth() + { + uint monitorWidth = (uint)(ApplicationHelper.GetMainWindow()?.Screens.Primary?.Bounds.Width ?? 1920); + if(UseExclusiveFullscreen) { + return ExclusiveFullscreenResolution == FullscreenResolution.Default ? monitorWidth : (uint)ExclusiveFullscreenResolution.GetWidth(); + } else { + return monitorWidth; + } + } + + public UInt32 GetFullscreenHeight() + { + uint monitorHeight = (uint)(ApplicationHelper.GetMainWindow()?.Screens.Primary?.Bounds.Height ?? 1080); + if(UseExclusiveFullscreen) { + return ExclusiveFullscreenResolution == FullscreenResolution.Default ? monitorHeight : (uint)ExclusiveFullscreenResolution.GetHeight(); + } else { + return monitorHeight; + } + } } [StructLayout(LayoutKind.Sequential)] @@ -157,14 +180,15 @@ public struct InteropVideoConfig public double NtscIFilterLength; public double NtscQFilterLength; + [MarshalAs(UnmanagedType.I1)] public bool EnableVariableRefreshRate; [MarshalAs(UnmanagedType.I1)] public bool FullscreenForceIntegerScale; [MarshalAs(UnmanagedType.I1)] public bool UseExclusiveFullscreen; public UInt32 ExclusiveFullscreenRefreshRateNtsc; public UInt32 ExclusiveFullscreenRefreshRatePal; - public UInt32 FullscreenResWidth; - public UInt32 FullscreenResHeight; public UInt32 ScreenRotation; + + [MarshalAs(UnmanagedType.I1)] public bool DisableHighPrecisionFramePacing; } public enum VideoFilterType diff --git a/UI/Controls/HelpTooltip.axaml b/UI/Controls/HelpTooltip.axaml new file mode 100644 index 000000000..8f4416dd6 --- /dev/null +++ b/UI/Controls/HelpTooltip.axaml @@ -0,0 +1,24 @@ + + + + 400 + + + + \ No newline at end of file diff --git a/UI/Controls/HelpTooltip.axaml.cs b/UI/Controls/HelpTooltip.axaml.cs new file mode 100644 index 000000000..83d1d373e --- /dev/null +++ b/UI/Controls/HelpTooltip.axaml.cs @@ -0,0 +1,27 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using System; + +namespace Mesen.Controls; + +public class HelpTooltip : UserControl +{ + public static readonly StyledProperty TextProperty = AvaloniaProperty.Register(nameof(Text)); + + public string Text + { + get { return GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public HelpTooltip() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index 229a705e0..2f025d31d 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -75,7 +75,7 @@ public static RomInfo GetRomInfo() [DllImport(DllPath)] public static extern void AddKnownGameFolder([MarshalAs(UnmanagedType.LPUTF8Str)] string folder); - [DllImport(DllPath)] public static extern void SetExclusiveFullscreenMode([MarshalAs(UnmanagedType.I1)] bool fullscreen, IntPtr windowHandle); + [DllImport(DllPath)] public static extern void SetFullscreenMode(FullscreenSettings settings); [DllImport(DllPath)] public static extern TimingInfo GetTimingInfo(CpuType cpuType); @@ -398,4 +398,19 @@ public struct SoftwareRendererFrame public SoftwareRendererSurface EmuHud; public SoftwareRendererSurface ScriptHud; } + + public enum FullscreenMode + { + Disabled, + Borderless, + Exclusive + } + + public struct FullscreenSettings + { + public IntPtr WindowHandle; + public UInt32 Width; + public UInt32 Height; + public FullscreenMode Mode; + }; } diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index 4c03d96c0..4d533a67b 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -140,6 +140,8 @@ Custom Ratio: Use bilinear interpolation when scaling Use sRGB when applying interpolation + Enable variable refresh rate (fullscreen only) + When using a monitor that supports VRR (FreeSync, G-Sync), VRR will be enabled when running in borderless (not exclusive) fullscreen mode. This improves frame pacing and is recommended when supported. Warning: When enabled on a display that does not support VRR at all (or using a core that runs at an FPS outside of the monitor's supported VRR range), enabling this option will cause screen tearing when in fullscreen mode. Fullscreen Resolution: Use integer scale values when entering fullscreen mode Use exclusive fullscreen mode @@ -193,6 +195,7 @@ Advanced Screen Rotation: Use software renderer (requires restart) + Disable high precision frame pacing (may improve battery life)
General diff --git a/UI/UI.csproj b/UI/UI.csproj index 515184d1d..d591679ae 100644 --- a/UI/UI.csproj +++ b/UI/UI.csproj @@ -139,6 +139,9 @@ Code FirmwareSelect.axaml + + HelpTooltip.axaml + SoftwareRendererView.axaml diff --git a/UI/Utilities/MouseManager.cs b/UI/Utilities/MouseManager.cs index 83b2de484..bf56d6651 100644 --- a/UI/Utilities/MouseManager.cs +++ b/UI/Utilities/MouseManager.cs @@ -27,7 +27,6 @@ public class MouseManager : IDisposable private DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.Normal); - private Control _renderer; private bool _usesSoftwareRenderer; private MainMenuView _mainMenu; private MainWindow _wnd; @@ -38,10 +37,9 @@ public class MouseManager : IDisposable private bool _closeMenuPending = false; private DateTime _lastMouseMove = DateTime.Now; - public MouseManager(MainWindow wnd, Control renderer, MainMenuView mainMenu, bool usesSoftwareRenderer) + public MouseManager(MainWindow wnd, MainMenuView mainMenu, bool usesSoftwareRenderer) { _wnd = wnd; - _renderer = renderer; _mainMenu = mainMenu; _usesSoftwareRenderer = usesSoftwareRenderer; @@ -72,7 +70,7 @@ private void TmrProcessMouse(object? sender, EventArgs e) if(MainWindowViewModel.Instance.AudioPlayer == null) { //Only give renderer focus when the audio player isn't active //Otherwise clicking on the audio player's buttons does nothing - _renderer.Focus(); + _wnd.Renderer.Focus(); } _closeMenuPending = false; } else { @@ -82,8 +80,8 @@ private void TmrProcessMouse(object? sender, EventArgs e) _closeMenuPending = false; } - PixelPoint rendererTopLeft = _renderer.PointToScreen(new Point()); - PixelRect rendererScreenRect = new PixelRect(rendererTopLeft, PixelSize.FromSize(_renderer.Bounds.Size, LayoutHelper.GetLayoutScale(_wnd) / InputApi.GetPixelScale())); + PixelPoint rendererTopLeft = _wnd.Renderer.PointToScreen(new Point()); + PixelRect rendererScreenRect = new PixelRect(rendererTopLeft, PixelSize.FromSize(_wnd.Renderer.Bounds.Size, LayoutHelper.GetLayoutScale(_wnd) / InputApi.GetPixelScale())); if(_prevPositionX != mouseState.XPosition || _prevPositionY != mouseState.YPosition) { //Send mouse movement x/y values to core @@ -108,7 +106,7 @@ private void TmrProcessMouse(object? sender, EventArgs e) if(rendererScreenRect.Contains(mousePos)) { //Send mouse state to emulation core - Point rendererPos = _renderer.PointToClient(mousePos) * LayoutHelper.GetLayoutScale(_wnd) / InputApi.GetPixelScale(); + Point rendererPos = _wnd.Renderer.PointToClient(mousePos) * LayoutHelper.GetLayoutScale(_wnd) / InputApi.GetPixelScale(); InputApi.SetMousePosition(rendererPos.X / rendererScreenRect.Width, rendererPos.Y / rendererScreenRect.Height); bool buttonPressed = (mouseState.LeftButton || mouseState.RightButton || mouseState.MiddleButton || mouseState.Button4 || mouseState.Button5); @@ -149,7 +147,7 @@ private void SetMouseCursor(CursorImage icon) InputApi.SetCursorImage(icon); if(_usesSoftwareRenderer && !OperatingSystem.IsMacOS()) { //On MacOS, also setting the cursor on the renderer causes the cursor visibility to act oddly - _renderer.Cursor = new Cursor(icon.ToStandardCursorType()); + _wnd.Renderer.Cursor = new Cursor(icon.ToStandardCursorType()); } } @@ -252,8 +250,8 @@ private CursorImage MouseIcon private void CaptureMouse() { if(!_mouseCaptured && AllowMouseCapture) { - PixelPoint topLeft = _renderer.PointToScreen(new Point()); - PixelRect rendererScreenRect = new PixelRect(topLeft, PixelSize.FromSize(_renderer.Bounds.Size, LayoutHelper.GetLayoutScale(_wnd))); + PixelPoint topLeft = _wnd.Renderer.PointToScreen(new Point()); + PixelRect rendererScreenRect = new PixelRect(topLeft, PixelSize.FromSize(_wnd.Renderer.Bounds.Size, LayoutHelper.GetLayoutScale(_wnd))); if(InputApi.CaptureMouse(topLeft.X, topLeft.Y, rendererScreenRect.Width, rendererScreenRect.Height, GetRendererHandle())) { DisplayMessageHelper.DisplayMessage("Input", ResourceHelper.GetMessage("MouseModeEnabled")); @@ -272,7 +270,7 @@ private void ReleaseMouse() private IntPtr GetRendererHandle() { - return _usesSoftwareRenderer ? IntPtr.Zero : (_renderer as NativeRenderer)!.Handle; + return _usesSoftwareRenderer ? IntPtr.Zero : (_wnd.Renderer as NativeRenderer)!.Handle; } public void Dispose() diff --git a/UI/ViewModels/MainWindowViewModel.cs b/UI/ViewModels/MainWindowViewModel.cs index ced83a4c2..828374fba 100644 --- a/UI/ViewModels/MainWindowViewModel.cs +++ b/UI/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Threading; using Mesen.Config; using Mesen.Controls; using Mesen.Interop; @@ -38,6 +39,7 @@ public class MainWindowViewModel : ViewModelBase public SoftwareRendererViewModel SoftwareRenderer { get; } = new(); public Configuration Config { get; } + public NativeRenderer? Renderer { get; internal set; } public MainWindowViewModel() { @@ -59,6 +61,12 @@ public void Init(MainWindow wnd) this.WhenAnyValue(x => x.RecentGames.Visible, x => x.SoftwareRenderer.FrameSurface).Subscribe(x => { IsNativeRendererVisible = !RecentGames.Visible && SoftwareRenderer.FrameSurface == null; IsSoftwareRendererVisible = !RecentGames.Visible && SoftwareRenderer.FrameSurface != null; + + if(Renderer != null) { + Dispatcher.UIThread.Post(() => { + Renderer.IsVisible = IsNativeRendererVisible; + }); + } }); this.WhenAnyValue(x => x.RomInfo).Subscribe(x => { diff --git a/UI/Views/VideoConfigView.axaml b/UI/Views/VideoConfigView.axaml index 6a1b5f377..430564333 100644 --- a/UI/Views/VideoConfigView.axaml +++ b/UI/Views/VideoConfigView.axaml @@ -51,6 +51,15 @@ /> + + + + + + diff --git a/UI/Windows/MainWindow.axaml b/UI/Windows/MainWindow.axaml index b5da39a56..bd9fca976 100644 --- a/UI/Windows/MainWindow.axaml +++ b/UI/Windows/MainWindow.axaml @@ -42,11 +42,10 @@ - diff --git a/UI/Windows/MainWindow.axaml.cs b/UI/Windows/MainWindow.axaml.cs index 4f0728ea2..07033aa0c 100644 --- a/UI/Windows/MainWindow.axaml.cs +++ b/UI/Windows/MainWindow.axaml.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -67,6 +68,9 @@ public class MainWindow : MesenWindow private Stopwatch _stopWatch = Stopwatch.StartNew(); private Dictionary _keyPressedStamp = new(); private bool _focusInMenu; + private bool _needRendererReset; + + public Control Renderer => _usesSoftwareRenderer ? _softwareRenderer : _renderer; static MainWindow() { @@ -102,7 +106,8 @@ public MainWindow() _rendererPanel = this.GetControl("RendererPanel"); _rendererPanel.LayoutUpdated += RendererPanel_LayoutUpdated; - _renderer = this.GetControl("Renderer"); + ResetRenderer(); + _softwareRenderer = this.GetControl("SoftwareRenderer"); _audioPlayer = this.GetControl("AudioPlayer"); _mainMenu = this.GetControl("MainMenu"); @@ -115,6 +120,23 @@ public MainWindow() #endif } + [MemberNotNull(nameof(_renderer))] + private void ResetRenderer() + { + if(_renderer != null && !_needRendererReset) { + //Renderer needs to be reset when VRR mode is enabled, because DX11 does not allow switching + //back to the non-flip swapchain model on a window that had the flip model enabled once. + return; + } + + ContentControl container = this.GetControl("RendererContainer"); + _renderer = new NativeRenderer(); + _renderer.IsVisible = _model.IsNativeRendererVisible; + _model.Renderer = _renderer; + container.Content = _renderer; + _needRendererReset = false; + } + private static void InitGlobalShortcuts() { if(Application.Current?.PlatformSettings == null) { @@ -215,7 +237,7 @@ protected override void OnOpened(EventArgs e) return; } - _mouseManager = new MouseManager(this, _usesSoftwareRenderer ? _softwareRenderer : _renderer, _mainMenu, _usesSoftwareRenderer); + _mouseManager = new MouseManager(this, _mainMenu, _usesSoftwareRenderer); ConfigManager.Config.InitializeFontDefaults(); ConfigManager.Config.Preferences.ApplyFontOptions(); @@ -554,8 +576,21 @@ private void RendererPanel_LayoutUpdated(object? sender, EventArgs e) EmuApi.SetRendererSize(realWidth, realHeight); _model.RendererSize = new Size(realWidth, realHeight); - _renderer.Width = width; - _renderer.Height = height; + if(WindowState == WindowState.FullScreen && !ConfigManager.Config.Video.UseExclusiveFullscreen && ConfigManager.Config.Video.EnableVariableRefreshRate) { + //When VRR is enabled, set the renderer to the same size as the monitor when in fullscreen mode + PixelRect bounds = ApplicationHelper.GetMainWindow()?.Screens.Primary?.Bounds ?? default; + if(bounds != default) { + _rendererSize = bounds.Size.ToSize(LayoutHelper.GetLayoutScale(this)); + if(_model.IsMenuVisible) { + _rendererSize = _rendererSize.WithHeight(_rendererSize.Height - _mainMenu.Bounds.Height); + } + _renderer.Width = _rendererSize.Width; + _renderer.Height = _rendererSize.Height; + } + } else { + _renderer.Width = width; + _renderer.Height = height; + } _model.SoftwareRenderer.Width = width; _model.SoftwareRenderer.Height = height; } @@ -566,6 +601,16 @@ private void OnWindowStateChanged() ResizeRenderer(); } + private void SetFullscreenMode(FullscreenMode mode, IntPtr windowHandle) + { + EmuApi.SetFullscreenMode(new FullscreenSettings() { + Mode = mode, + WindowHandle = windowHandle, + Width = ConfigManager.Config.Video.GetFullscreenWidth(), + Height = ConfigManager.Config.Video.GetFullscreenHeight() + }); + } + public void ToggleFullscreen() { if(_preventFullscreenToggle) { @@ -574,10 +619,13 @@ public void ToggleFullscreen() _preventFullscreenToggle = true; if(WindowState == WindowState.FullScreen) { + if(ConfigManager.Config.Video.EnableVariableRefreshRate && !ConfigManager.Config.Video.UseExclusiveFullscreen && OperatingSystem.IsWindows()) { + _needRendererReset = true; + } + ResetRenderer(); + Task.Run(() => { - if(ConfigManager.Config.Video.UseExclusiveFullscreen) { - EmuApi.SetExclusiveFullscreenMode(false, _renderer.Handle); - } + SetFullscreenMode(FullscreenMode.Disabled, _renderer.Handle); Dispatcher.UIThread.Post(() => { WindowState = _prevWindowState; @@ -602,7 +650,7 @@ public void ToggleFullscreen() } Task.Run(() => { - EmuApi.SetExclusiveFullscreenMode(true, TryGetPlatformHandle()?.Handle ?? IntPtr.Zero); + SetFullscreenMode(FullscreenMode.Exclusive, TryGetPlatformHandle()?.Handle ?? IntPtr.Zero); _preventFullscreenToggle = false; Dispatcher.UIThread.Post(() => { @@ -610,8 +658,14 @@ public void ToggleFullscreen() }); }); } else { - WindowState = WindowState.FullScreen; - _preventFullscreenToggle = false; + Dispatcher.UIThread.Post(() => { + if(ConfigManager.Config.Video.EnableVariableRefreshRate) { + _needRendererReset = OperatingSystem.IsWindows(); + SetFullscreenMode(FullscreenMode.Borderless, _renderer.Handle); + } + WindowState = WindowState.FullScreen; + _preventFullscreenToggle = false; + }); } } } diff --git a/Utilities/PlatformUtilities.cpp b/Utilities/PlatformUtilities.cpp index 34e63bb8e..14676d392 100644 --- a/Utilities/PlatformUtilities.cpp +++ b/Utilities/PlatformUtilities.cpp @@ -3,8 +3,27 @@ #ifdef _WIN32 #include +#else + #if defined(__x86_64__) || defined(_M_X64) + #include + #elif __aarch64__ + #include + #endif #endif +void PlatformUtilities::IdleLoop() +{ +#if _WIN32 + YieldProcessor(); +#elif defined(__x86_64__) || defined(_M_X64) + _mm_pause(); +#elif __aarch64__ + #ifdef __clang__ + __isb(0xF); + #endif +#endif +} + void PlatformUtilities::DisableScreensaver() { //Prevent screensaver/etc from starting while using the emulator diff --git a/Utilities/PlatformUtilities.h b/Utilities/PlatformUtilities.h index c8dd99ccb..6c041c88f 100644 --- a/Utilities/PlatformUtilities.h +++ b/Utilities/PlatformUtilities.h @@ -4,6 +4,8 @@ class PlatformUtilities { public: + static void IdleLoop(); + static void DisableScreensaver(); static void EnableScreensaver(); diff --git a/Utilities/StringUtilities.h b/Utilities/StringUtilities.h index 992966bca..7c24cd8f9 100644 --- a/Utilities/StringUtilities.h +++ b/Utilities/StringUtilities.h @@ -1,5 +1,7 @@ #pragma once #include "pch.h" +#include +#include class StringUtilities { @@ -125,4 +127,11 @@ class StringUtilities return std::to_string(size) + " bytes"; } } + + static string ToString(double value, uint32_t precision) + { + std::ostringstream stream; + stream << std::fixed << std::setprecision(precision) << value; + return stream.str(); + } }; diff --git a/Utilities/Timer.cpp b/Utilities/Timer.cpp index bf6fb41b8..bcb71b3bc 100644 --- a/Utilities/Timer.cpp +++ b/Utilities/Timer.cpp @@ -22,13 +22,3 @@ double Timer::GetElapsedMS() const duration span = duration_cast>(end - _start); return span.count() * 1000.0; } - -void Timer::WaitUntil(double targetMillisecond) const -{ - if(targetMillisecond > 0) { - double elapsedTime = GetElapsedMS(); - if(targetMillisecond - elapsedTime > 1) { - std::this_thread::sleep_for(std::chrono::duration((int)(targetMillisecond - elapsedTime))); - } - } -} diff --git a/Utilities/Timer.h b/Utilities/Timer.h index a0e92135a..760bec281 100644 --- a/Utilities/Timer.h +++ b/Utilities/Timer.h @@ -12,5 +12,4 @@ class Timer Timer(); void Reset(); double GetElapsedMS() const; - void WaitUntil(double targetMillisecond) const; }; \ No newline at end of file diff --git a/Windows/Common.h b/Windows/Common.h index cb49d1676..e40516469 100644 --- a/Windows/Common.h +++ b/Windows/Common.h @@ -13,6 +13,7 @@ #pragma comment(lib, "dsound.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "winmm.lib") +#pragma comment(lib, "dxgi.lib") // C RunTime Header Files #include diff --git a/Windows/Renderer.cpp b/Windows/Renderer.cpp index 77b2a18e3..0346d4e82 100644 --- a/Windows/Renderer.cpp +++ b/Windows/Renderer.cpp @@ -7,9 +7,11 @@ #include "Core/Shared/MessageManager.h" #include "Core/Shared/SettingTypes.h" #include "Core/Shared/EmuSettings.h" -#include "Utilities/UTF8Util.h" +#include +#include using namespace DirectX; +using Microsoft::WRL::ComPtr; Renderer::Renderer(Emulator* emu, HWND hWnd) { @@ -28,16 +30,16 @@ Renderer::~Renderer() CleanupDevice(); } -void Renderer::SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) +void Renderer::SetFullscreenMode(FullscreenSettings settings) { - if(fullscreen != _fullscreen || _hWnd != (HWND)windowHandle) { + if(settings.Mode != _fullscreen || _hWnd != (HWND)settings.WindowHandle) { int counter = _resetCounter; - _hWnd = (HWND)windowHandle; - _monitorWidth = _emu->GetSettings()->GetVideoConfig().FullscreenResWidth; - _monitorHeight = _emu->GetSettings()->GetVideoConfig().FullscreenResHeight; + _hWnd = (HWND)settings.WindowHandle; + _monitorWidth = settings.Width; + _monitorHeight = settings.Height; - _newFullscreen = fullscreen; + _newFullscreen = settings.Mode; while(_resetCounter <= counter) { std::this_thread::sleep_for(std::chrono::duration(10)); @@ -52,7 +54,7 @@ DXGI_FORMAT Renderer::GetTextureFormat() void Renderer::SetScreenSize(uint32_t width, uint32_t height) { - VideoConfig cfg = _emu->GetSettings()->GetVideoConfig(); + VideoConfig& cfg = _emu->GetSettings()->GetVideoConfig(); FrameInfo rendererSize = _emu->GetVideoRenderer()->GetRendererSize(); uint32_t refreshRate = _emu->GetFps() < 55 ? cfg.ExclusiveFullscreenRefreshRatePal : cfg.ExclusiveFullscreenRefreshRateNtsc; @@ -64,8 +66,8 @@ void Renderer::SetScreenSize(uint32_t width, uint32_t height) _screenWidth != rendererSize.Width || _newFullscreen != _fullscreen || _useSrgbTextureFormat != cfg.UseSrgbTextureFormat || - (_fullscreen && _fullscreenRefreshRate != refreshRate) || - (_fullscreen && (_realScreenHeight != _monitorHeight || _realScreenWidth != _monitorWidth))); + (_fullscreen == FullscreenMode::Exclusive && _fullscreenRefreshRate != refreshRate) || + (_fullscreen != FullscreenMode::Disabled && (_realScreenHeight != _monitorHeight || _realScreenWidth != _monitorWidth))); }; if(needUpdate()) { @@ -76,9 +78,9 @@ void Renderer::SetScreenSize(uint32_t width, uint32_t height) _emuFrameWidth = width; bool needReset = _fullscreen != _newFullscreen; - bool fullscreenResizeMode = _fullscreen && _newFullscreen; + bool fullscreenResizeMode = _fullscreen != FullscreenMode::Disabled && _newFullscreen != FullscreenMode::Disabled; - if(_pSwapChain && _fullscreen && !_newFullscreen) { + if(_pSwapChain && _fullscreen == FullscreenMode::Exclusive && _newFullscreen != FullscreenMode::Exclusive) { HRESULT hr = _pSwapChain->SetFullscreenState(FALSE, NULL); if(FAILED(hr)) { MessageManager::Log("SetFullscreenState(FALSE) failed - Error:" + std::to_string(hr)); @@ -99,7 +101,7 @@ void Renderer::SetScreenSize(uint32_t width, uint32_t height) _screenHeight = rendererSize.Height; _screenWidth = rendererSize.Width; - if(_fullscreen) { + if(_fullscreen != FullscreenMode::Disabled) { if(_realScreenHeight != _monitorHeight) { _realScreenHeight = _monitorHeight; needReset = true; @@ -139,7 +141,7 @@ void Renderer::SetScreenSize(uint32_t width, uint32_t height) } else { ResetTextureBuffers(); ReleaseRenderTargetView(); - _pSwapChain->ResizeBuffers(1, _realScreenWidth, _realScreenHeight, GetTextureFormat(), 0); + _pSwapChain->ResizeBuffers(_bufferCount, _realScreenWidth, _realScreenHeight, DXGI_FORMAT_B8G8R8A8_UNORM, 0); CreateRenderTargetView(); CreateEmuTextureBuffers(); } @@ -171,6 +173,8 @@ void Renderer::CleanupDevice() _pSwapChain = nullptr; } if(_pDeviceContext) { + _pDeviceContext->ClearState(); + _pDeviceContext->Flush(); _pDeviceContext->Release(); _pDeviceContext = nullptr; } @@ -231,7 +235,12 @@ HRESULT Renderer::CreateRenderTargetView() return hr; } - hr = _pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &_pRenderTargetView); + D3D11_RENDER_TARGET_VIEW_DESC desc = {}; + desc.Format = GetTextureFormat(); + desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + desc.Texture2D.MipSlice = 0; + + hr = _pd3dDevice->CreateRenderTargetView(pBackBuffer, &desc, &_pRenderTargetView); pBackBuffer->Release(); if(FAILED(hr)) { MessageManager::Log("D3DDevice::CreateRenderTargetView() failed - Error:" + std::to_string(hr)); @@ -302,49 +311,89 @@ HRESULT Renderer::InitDevice() }; UINT numFeatureLevels = ARRAYSIZE(featureLevels); - DXGI_SWAP_CHAIN_DESC sd; - ZeroMemory(&sd, sizeof(sd)); - sd.BufferCount = 1; - sd.BufferDesc.Width = _realScreenWidth; - sd.BufferDesc.Height = _realScreenHeight; - sd.BufferDesc.Format = GetTextureFormat(); - sd.BufferDesc.RefreshRate.Numerator = _fullscreenRefreshRate; - sd.BufferDesc.RefreshRate.Denominator = 1; + D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_NULL; + D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_1; + + ComPtr factory; + CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + + VideoConfig& cfg = _emu->GetSettings()->GetVideoConfig(); + bool enableFlipSwap = false; + bool allowTearing = false; + + if(cfg.EnableVariableRefreshRate && _fullscreen == FullscreenMode::Borderless) { + ComPtr factory5; + if(SUCCEEDED(factory->QueryInterface(IID_PPV_ARGS(&factory5)))) { + enableFlipSwap = true; + + BOOL tearingSupported = FALSE; + hr = factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &tearingSupported, sizeof(tearingSupported)); + allowTearing = tearingSupported; + if(FAILED(hr)) { + MessageManager::Log("CheckFeatureSupport() failed - Error:" + std::to_string(hr)); + } + } + } + + _allowTearing = allowTearing; + _bufferCount = enableFlipSwap ? 2 : 1; + + DXGI_SWAP_CHAIN_DESC1 sd = {}; + sd.BufferCount = _bufferCount; + sd.Width = _realScreenWidth; + sd.Height = _realScreenHeight; + sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - sd.Flags = _fullscreen ? DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH : 0; - sd.OutputWindow = _hWnd; + sd.SwapEffect = enableFlipSwap ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; - sd.Windowed = TRUE; + sd.Flags = + (_fullscreen == FullscreenMode::Exclusive ? DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH : 0) | + (_allowTearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC sdFullscreen = {}; + sdFullscreen.RefreshRate.Numerator = _fullscreenRefreshRate; + sdFullscreen.RefreshRate.Denominator = 1; + sdFullscreen.Windowed = TRUE; - D3D_DRIVER_TYPE driverType = D3D_DRIVER_TYPE_NULL; - D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_1; for(UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) { driverType = driverTypes[driverTypeIndex]; featureLevel = D3D_FEATURE_LEVEL_11_1; - hr = D3D11CreateDeviceAndSwapChain(nullptr, driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &sd, &_pSwapChain, &_pd3dDevice, &featureLevel, &_pDeviceContext); - - /*if(FAILED(hr)) { - MessageManager::Log("D3D11CreateDeviceAndSwapChain() failed - Error:" + std::to_string(hr)); - }*/ + hr = D3D11CreateDevice(nullptr, driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &_pd3dDevice, &featureLevel, &_pDeviceContext); if(hr == E_INVALIDARG) { // DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it featureLevel = D3D_FEATURE_LEVEL_11_0; - hr = D3D11CreateDeviceAndSwapChain(nullptr, driverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1, D3D11_SDK_VERSION, &sd, &_pSwapChain, &_pd3dDevice, &featureLevel, &_pDeviceContext); + hr = D3D11CreateDevice(nullptr, driverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1, D3D11_SDK_VERSION, &_pd3dDevice, &featureLevel, &_pDeviceContext); } - if(SUCCEEDED(hr)) { + if(FAILED(hr)) { + MessageManager::Log("D3D11CreateDevice() failed - Error:" + std::to_string(hr)); + continue; + } else { break; } } if(FAILED(hr)) { - MessageManager::Log("D3D11CreateDeviceAndSwapChain() failed - Error:" + std::to_string(hr)); return hr; } - if(_fullscreen) { + hr = factory->CreateSwapChainForHwnd(_pd3dDevice, _hWnd, &sd, _fullscreen == FullscreenMode::Exclusive ? &sdFullscreen : nullptr, nullptr, &_pSwapChain); + if(hr == DXGI_ERROR_INVALID_CALL) { + //Retry with legacy swap effect + _allowTearing = false; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + sd.Flags &= ~DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + hr = factory->CreateSwapChainForHwnd(_pd3dDevice, _hWnd, &sd, _fullscreen == FullscreenMode::Exclusive ? &sdFullscreen : nullptr, nullptr, &_pSwapChain); + } + + if(FAILED(hr)) { + MessageManager::Log("CreateSwapChainForHwnd() failed - Error:" + std::to_string(hr)); + return hr; + } + + if(_fullscreen == FullscreenMode::Exclusive) { hr = _pSwapChain->SetFullscreenState(TRUE, NULL); if(FAILED(hr)) { MessageManager::Log("SetFullscreenState(true) failed - Error:" + std::to_string(hr)); @@ -578,6 +627,7 @@ void Renderer::DrawHud(HudRenderInfo& hud, RenderSurfaceInfo& hudSurface) void Renderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) { auto lock = _frameLock.AcquireSafe(); + if(_newFullscreen != _fullscreen) { SetScreenSize(_emuFrameWidth, _emuFrameHeight); } @@ -591,7 +641,9 @@ void Renderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) } } - VideoConfig cfg = _emu->GetSettings()->GetVideoConfig(); + _pDeviceContext->OMSetRenderTargets(1, &_pRenderTargetView, nullptr); + + VideoConfig& cfg = _emu->GetSettings()->GetVideoConfig(); // Clear the back buffer _pDeviceContext->ClearRenderTargetView(_pRenderTargetView, Colors::Black); @@ -608,7 +660,7 @@ void Renderer::Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) _spriteBatch->End(); // Present the information rendered to the back buffer to the front buffer (the screen) - HRESULT hr = _pSwapChain->Present(cfg.VerticalSync ? 1 : 0, 0); + HRESULT hr = _pSwapChain->Present((cfg.VerticalSync && !_allowTearing) ? 1 : 0, _allowTearing ? DXGI_PRESENT_ALLOW_TEARING : 0); if(FAILED(hr)) { MessageManager::Log("SwapChain::Present() failed - Error:" + std::to_string(hr)); if(hr == DXGI_ERROR_DEVICE_REMOVED) { diff --git a/Windows/Renderer.h b/Windows/Renderer.h index ab68e1917..aaeb57b45 100644 --- a/Windows/Renderer.h +++ b/Windows/Renderer.h @@ -33,7 +33,7 @@ class Renderer final : public IRenderingDevice ID3D11Device* _pd3dDevice = nullptr; ID3D11DeviceContext* _pDeviceContext = nullptr; - IDXGISwapChain* _pSwapChain = nullptr; + IDXGISwapChain1* _pSwapChain = nullptr; ID3D11RenderTargetView* _pRenderTargetView = nullptr; atomic _needFlip = false; @@ -53,10 +53,12 @@ class Renderer final : public IRenderingDevice const uint32_t _bytesPerPixel = 4; uint32_t _screenBufferSize = 0; - bool _newFullscreen = false; - bool _fullscreen = false; + FullscreenMode _newFullscreen = FullscreenMode::Disabled; + FullscreenMode _fullscreen = FullscreenMode::Disabled; uint32_t _fullscreenRefreshRate = 60; bool _useSrgbTextureFormat = false; + bool _allowTearing = false; + uint32_t _bufferCount = 1; uint32_t _screenWidth = 0; uint32_t _screenHeight = 0; @@ -96,7 +98,7 @@ class Renderer final : public IRenderingDevice Renderer(Emulator* emu, HWND hWnd); ~Renderer(); - void SetExclusiveFullscreenMode(bool fullscreen, void* windowHandle) override; + void SetFullscreenMode(FullscreenSettings settings) override; void Reset() override; void Render(RenderSurfaceInfo& emuHud, RenderSurfaceInfo& scriptHud) override; diff --git a/Windows/Windows.vcxproj.filters b/Windows/Windows.vcxproj.filters index c7c312793..bad0d15d0 100644 --- a/Windows/Windows.vcxproj.filters +++ b/Windows/Windows.vcxproj.filters @@ -1,106 +1,75 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd + + {904b6202-95e0-49c4-9530-dc6ffd391fcf} - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files + + DirectXTK - - Header Files + + DirectXTK - - Header Files + + DirectXTK - Header Files + DirectXTK - - Header Files - - - Header Files + + DirectXTK - Header Files + DirectXTK - - Header Files - - - Header Files + + DirectXTK - Header Files + DirectXTK - Header Files + DirectXTK + + DirectXTK + + + + + + + + - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files + + DirectXTK - Source Files + DirectXTK - - Source Files + + DirectXTK - Source Files - - - Source Files + DirectXTK + + + + + + - Header Files + DirectXTK - Header Files + DirectXTK \ No newline at end of file