Emscripten: Add "vsync" FPS limit mode

Would be really useful for native versions too, but it's more complicated to pull off. For now, vsync on native is the same as tpt.setfpscap(2).
This commit is contained in:
Tamás Bálint Misius 2023-02-12 13:58:54 +01:00
parent 9f71eb9d77
commit c725894abd
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
10 changed files with 100 additions and 26 deletions

View File

@ -17,6 +17,7 @@ constexpr bool IGNORE_UPDATES = @IGNORE_UPDATES@;
constexpr bool ENFORCE_HTTPS = @ENFORCE_HTTPS@; constexpr bool ENFORCE_HTTPS = @ENFORCE_HTTPS@;
constexpr bool SECURE_CIPHERS_ONLY = @SECURE_CIPHERS_ONLY@; constexpr bool SECURE_CIPHERS_ONLY = @SECURE_CIPHERS_ONLY@;
constexpr bool FFTW_PLAN_MEASURE = @FFTW_PLAN_MEASURE@; constexpr bool FFTW_PLAN_MEASURE = @FFTW_PLAN_MEASURE@;
constexpr bool DEFAULT_VSYNC = @DEFAULT_VSYNC@;
constexpr char PATH_SEP_CHAR = '@PATH_SEP_CHAR@'; constexpr char PATH_SEP_CHAR = '@PATH_SEP_CHAR@';
constexpr char SERVER[] = "@SERVER@"; constexpr char SERVER[] = "@SERVER@";

14
src/FpsLimit.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <variant>
struct FpsLimitVsync
{
};
struct FpsLimitNone
{
};
struct FpsLimitExplicit
{
float value;
};
using FpsLimit = std::variant<FpsLimitVsync, FpsLimitNone, FpsLimitExplicit>;

View File

@ -390,16 +390,17 @@ void EngineProcess()
blit(engine.g->Data()); blit(engine.g->Data());
} }
auto fpsLimit = ui::Engine::Ref().FpsLimit; auto fpsLimit = ui::Engine::Ref().GetFpsLimit();
auto now = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000); auto now = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000);
oldFrameStart = frameStart; oldFrameStart = frameStart;
frameStart = now; frameStart = now;
if (fpsLimit > 2) if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&fpsLimit))
{ {
auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimit); auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimitExplicit->value);
auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration; auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration;
auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U; auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U;
frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration); frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration);
SDL_Delay((frameStart - now) / UINT64_C(1'000'000)); SDL_Delay((frameStart - now) / UINT64_C(1'000'000));
} }
// TODO: else if (auto *fpsLimitExplicit = std::get_if<FpsLimitVsync>(&fpsLimit))
} }

View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include "graphics/Pixel.h" #include "graphics/Pixel.h"
#include "FpsLimit.h"
#include <cstdint> #include <cstdint>
#include <SDL.h> #include <SDL.h>
#include <variant>
extern int desktopWidth; extern int desktopWidth;
extern int desktopHeight; extern int desktopHeight;
@ -40,6 +42,7 @@ void blit(pixel *vid);
void SDLOpen(); void SDLOpen();
void SDLClose(); void SDLClose();
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_); void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_);
void SetFpsLimit(FpsLimit newFpsLimit);
bool RecreateWindow(); bool RecreateWindow();
void LoadWindowPosition(); void LoadWindowPosition();
void SaveWindowPosition(); void SaveWindowPosition();

View File

@ -8,3 +8,7 @@ void MainLoop()
EngineProcess(); EngineProcess();
} }
} }
void SetFpsLimit(FpsLimit newFpsLimit)
{
}

View File

@ -3,35 +3,33 @@
#include <emscripten.h> #include <emscripten.h>
#include <iostream> #include <iostream>
static float lastFpsLimit; void SetFpsLimit(FpsLimit newFpsLimit)
static void updateFpsLimit()
{ {
lastFpsLimit = ui::Engine::Ref().FpsLimit; static bool mainLoopSet = false;
if (lastFpsLimit == 60.0f) // TODO: rework FPS cap so 60.0 is not the default if (!mainLoopSet)
{
emscripten_set_main_loop(EngineProcess, 0, 0);
mainLoopSet = true;
}
if (auto *fpsLimitVsync = std::get_if<FpsLimitVsync>(&newFpsLimit))
{ {
emscripten_set_main_loop_timing(EM_TIMING_RAF, 1); emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
std::cerr << "implicit fps limit via vsync" << std::endl; std::cerr << "implicit fps limit via vsync" << std::endl;
} }
else else
{ {
auto delay = int(1000.f / lastFpsLimit); auto delay = 0;
if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&newFpsLimit))
{
delay = int(1000.f / fpsLimitExplicit->value);
}
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, delay); emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, delay);
std::cerr << "explicit fps limit: " << delay << "ms delays" << std::endl; std::cerr << "explicit fps limit: " << delay << "ms delays" << std::endl;
} }
} }
static void mainLoopBody()
{
EngineProcess();
if (lastFpsLimit != ui::Engine::Ref().FpsLimit)
{
updateFpsLimit();
}
}
void MainLoop() void MainLoop()
{ {
emscripten_set_main_loop(mainLoopBody, 0, 0); SetFpsLimit(ui::Engine::Ref().GetFpsLimit());
updateFpsLimit(); EngineProcess();
mainLoopBody();
} }

View File

@ -1,4 +1,5 @@
#include "Engine.h" #include "Engine.h"
#include "Config.h"
#include "PowderToySDL.h" #include "PowderToySDL.h"
#include "Window.h" #include "Window.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
@ -10,7 +11,6 @@
using namespace ui; using namespace ui;
Engine::Engine(): Engine::Engine():
FpsLimit(60.0f),
drawingFrequencyLimit(0), drawingFrequencyLimit(0),
Scale(1), Scale(1),
Fullscreen(false), Fullscreen(false),
@ -27,7 +27,15 @@ Engine::Engine():
mousexp_(0), mousexp_(0),
mouseyp_(0) mouseyp_(0)
{ {
SetFps(FpsLimit); // populate dt with whatever that makes any sort of sense SetFpsLimit(fpsLimit); // populate dt with whatever that makes any sort of sense
if constexpr (DEFAULT_VSYNC)
{
SetFpsLimit(FpsLimitVsync{});
}
else
{
SetFpsLimit(FpsLimitExplicit{ 60.0f });
}
} }
Engine::~Engine() Engine::~Engine()
@ -41,6 +49,12 @@ Engine::~Engine()
} }
} }
void Engine::SetFpsLimit(FpsLimit newFpsLimit)
{
fpsLimit = newFpsLimit;
::SetFpsLimit(fpsLimit);
}
void Engine::Begin() void Engine::Begin()
{ {
//engine is now ready //engine is now ready
@ -194,10 +208,14 @@ void Engine::Draw()
void Engine::SetFps(float fps) void Engine::SetFps(float fps)
{ {
this->fps = fps; this->fps = fps;
if(FpsLimit > 2.0f) if (std::holds_alternative<FpsLimitExplicit>(fpsLimit))
{
this->dt = 60/fps; this->dt = 60/fps;
}
else else
{
this->dt = 1.0f; this->dt = 1.0f;
}
} }
void Engine::onKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) void Engine::onKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt)

View File

@ -6,6 +6,7 @@
#include "graphics/Pixel.h" #include "graphics/Pixel.h"
#include "gui/interface/Point.h" #include "gui/interface/Point.h"
#include <climits> #include <climits>
#include "FpsLimit.h"
class Graphics; class Graphics;
namespace ui namespace ui
@ -76,7 +77,13 @@ namespace ui
//void SetState(Window* state); //void SetState(Window* state);
//inline State* GetState() { return state_; } //inline State* GetState() { return state_; }
inline Window* GetWindow() { return state_; } inline Window* GetWindow() { return state_; }
float FpsLimit;
void SetFpsLimit(FpsLimit newFpsLimit);
FpsLimit GetFpsLimit() const
{
return fpsLimit;
}
int drawingFrequencyLimit; int drawingFrequencyLimit;
Graphics * g; Graphics * g;
int Scale; int Scale;
@ -85,6 +92,7 @@ namespace ui
unsigned int FrameIndex; unsigned int FrameIndex;
private: private:
FpsLimit fpsLimit;
bool altFullscreen; bool altFullscreen;
bool forceIntegerScaling = true; bool forceIntegerScaling = true;
bool resizable; bool resizable;

View File

@ -1222,13 +1222,37 @@ int luatpt_setfpscap(lua_State* l)
int acount = lua_gettop(l); int acount = lua_gettop(l);
if (acount == 0) if (acount == 0)
{ {
lua_pushnumber(l, ui::Engine::Ref().FpsLimit); auto fpsLimit = ui::Engine::Ref().GetFpsLimit();
if (std::holds_alternative<FpsLimitVsync>(fpsLimit))
{
lua_pushliteral(l, "vsync");
}
else if (std::holds_alternative<FpsLimitNone>(fpsLimit))
{
lua_pushnumber(l, 2);
}
else
{
lua_pushnumber(l, std::get<FpsLimitExplicit>(fpsLimit).value);
}
return 1; return 1;
} }
if (lua_isstring(l, 1) && byteStringEqualsLiteral(tpt_lua_toByteString(l, 1), "vsync"))
{
ui::Engine::Ref().SetFpsLimit(FpsLimitVsync{});
return 0;
}
float fpscap = luaL_checknumber(l, 1); float fpscap = luaL_checknumber(l, 1);
if (fpscap < 2) if (fpscap < 2)
{
return luaL_error(l, "fps cap too small"); return luaL_error(l, "fps cap too small");
ui::Engine::Ref().FpsLimit = fpscap; }
if (fpscap == 2)
{
ui::Engine::Ref().SetFpsLimit(FpsLimitNone{});
return 0;
}
ui::Engine::Ref().SetFpsLimit(FpsLimitExplicit{ fpscap });
return 0; return 0;
} }

View File

@ -50,10 +50,12 @@ if host_platform == 'emscripten'
powder_files += files( powder_files += files(
'PowderToySDLEmscripten.cpp', 'PowderToySDLEmscripten.cpp',
) )
conf_data.set('DEFAULT_VSYNC', 'true')
else else
powder_files += files( powder_files += files(
'PowderToySDLCommon.cpp', 'PowderToySDLCommon.cpp',
) )
conf_data.set('DEFAULT_VSYNC', 'false')
endif endif
if is_x86 if is_x86
powder_files += files('X86KillDenormals.cpp') powder_files += files('X86KillDenormals.cpp')
@ -66,6 +68,7 @@ render_files = files(
font_files = files( font_files = files(
'PowderToyFontEditor.cpp', 'PowderToyFontEditor.cpp',
'PowderToySDL.cpp', 'PowderToySDL.cpp',
'PowderToySDLCommon.cpp',
) )
common_files = files( common_files = files(