GUI: add option to disable VSync

also add frame rate limiter
This commit is contained in:
tildearrow 2024-04-02 17:49:48 -05:00
parent 9dbda09cd0
commit fc68f17107
11 changed files with 145 additions and 75 deletions

View file

@ -3769,6 +3769,10 @@ bool FurnaceGUI::loop() {
scrConfW=scrW;
scrConfH=scrH;
}
if (rend!=NULL) {
logV("restoring swap interval...");
rend->setSwapInterval(settings.vsync);
}
}
// update canvas size as well
if (!rend->getOutputSize(canvasW,canvasH)) {
@ -4037,7 +4041,7 @@ bool FurnaceGUI::loop() {
logD("starting render backend...");
while (++initAttempts<=5) {
if (rend->init(sdlWin)) {
if (rend->init(sdlWin,settings.vsync)) {
break;
}
SDL_Delay(1000);
@ -6534,6 +6538,22 @@ bool FurnaceGUI::loop() {
}
drawTimeEnd=SDL_GetPerformanceCounter();
swapTimeBegin=SDL_GetPerformanceCounter();
if (!settings.vsync || !rend->canVSync()) {
unsigned int presentDelay=SDL_GetPerformanceFrequency()/settings.frameRateLimit;
if ((nextPresentTime-swapTimeBegin)<presentDelay) {
#ifdef _WIN32
unsigned int mDivider=SDL_GetPerformanceFrequency()/1000;
Sleep((unsigned int)(nextPresentTime-swapTimeBegin)/mDivider);
#else
unsigned int mDivider=SDL_GetPerformanceFrequency()/1000000;
usleep((unsigned int)(nextPresentTime-swapTimeBegin)/mDivider);
#endif
nextPresentTime+=presentDelay;
} else {
nextPresentTime=swapTimeBegin+presentDelay;
}
}
rend->present();
if (settings.renderClearPos) {
rend->clear(uiColors[GUI_COLOR_BACKGROUND]);
@ -7036,7 +7056,7 @@ bool FurnaceGUI::init() {
}
logD("starting render backend...");
if (!rend->init(sdlWin)) {
if (!rend->init(sdlWin,settings.vsync)) {
logE("it failed...");
if (settings.renderBackend!="SDL") {
settings.renderBackend="SDL";
@ -7784,6 +7804,7 @@ FurnaceGUI::FurnaceGUI():
eventTimeBegin(0),
eventTimeEnd(0),
eventTimeDelta(0),
nextPresentTime(0),
perfMetricsLen(0),
chanToMove(-1),
sysToMove(-1),

View file

@ -1433,6 +1433,7 @@ class FurnaceGUIRender {
virtual void resized(const SDL_Event& ev);
virtual void clear(ImVec4 color);
virtual bool newFrame();
virtual bool canVSync();
virtual void createFontsTexture();
virtual void destroyFontsTexture();
virtual void renderGUI();
@ -1444,8 +1445,9 @@ class FurnaceGUIRender {
virtual bool regenOscShader(const char* fragment);
virtual bool getOutputSize(int& w, int& h);
virtual int getWindowFlags();
virtual void setSwapInterval(int swapInterval);
virtual void preInit();
virtual bool init(SDL_Window* win);
virtual bool init(SDL_Window* win, int swapInterval);
virtual void initGUI(SDL_Window* win);
virtual void quitGUI();
virtual bool quit();
@ -1796,6 +1798,8 @@ class FurnaceGUI {
int playbackTime;
int shaderOsc;
int cursorWheelStep;
int vsync;
int frameRateLimit;
unsigned int maxUndoSteps;
String mainFontPath;
String headFontPath;
@ -2001,6 +2005,8 @@ class FurnaceGUI {
playbackTime(1),
shaderOsc(1),
cursorWheelStep(0),
vsync(1),
frameRateLimit(60),
maxUndoSteps(100),
mainFontPath(""),
headFontPath(""),
@ -2227,6 +2233,7 @@ class FurnaceGUI {
uint64_t drawTimeBegin, drawTimeEnd, drawTimeDelta;
uint64_t swapTimeBegin, swapTimeEnd, swapTimeDelta;
uint64_t eventTimeBegin, eventTimeEnd, eventTimeDelta;
uint64_t nextPresentTime;
FurnaceGUIPerfMetric perfMetrics[64];
int perfMetricsLen;

View file

@ -59,6 +59,10 @@ bool FurnaceGUIRender::newFrame() {
return true;
}
bool FurnaceGUIRender::canVSync() {
return true;
}
void FurnaceGUIRender::createFontsTexture() {
}
@ -89,10 +93,13 @@ int FurnaceGUIRender::getWindowFlags() {
return 0;
}
void FurnaceGUIRender::setSwapInterval(int swapInterval) {
}
void FurnaceGUIRender::preInit() {
}
bool FurnaceGUIRender::init(SDL_Window* win) {
bool FurnaceGUIRender::init(SDL_Window* win, int swapInterval) {
return false;
}

View file

@ -293,6 +293,11 @@ bool FurnaceGUIRenderDX11::newFrame() {
return ImGui_ImplDX11_NewFrame();
}
bool FurnaceGUIRenderDX11::canVSync() {
// TODO: find out how to retrieve VSync status
return true;
}
void FurnaceGUIRenderDX11::createFontsTexture() {
ImGui_ImplDX11_CreateDeviceObjects();
}
@ -345,7 +350,7 @@ void FurnaceGUIRenderDX11::wipe(float alpha) {
}
void FurnaceGUIRenderDX11::present() {
HRESULT result=swapchain->Present(1,0);
HRESULT result=swapchain->Present(swapInterval,0);
if (result==DXGI_ERROR_DEVICE_REMOVED || result==DXGI_ERROR_DEVICE_RESET) {
dead=true;
} else if (result!=S_OK && result!=DXGI_STATUS_OCCLUDED) {
@ -363,6 +368,10 @@ int FurnaceGUIRenderDX11::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderDX11::setSwapInterval(int swapInt) {
swapInterval=swapInt;
}
void FurnaceGUIRenderDX11::preInit() {
}
@ -373,7 +382,7 @@ const float wipeVertices[4][4]={
{ 1.0, 1.0, 0.0, 1.0}
};
bool FurnaceGUIRenderDX11::init(SDL_Window* win) {
bool FurnaceGUIRenderDX11::init(SDL_Window* win, int swapInt) {
SDL_SysWMinfo sysWindow;
D3D_FEATURE_LEVEL featureLevel;
@ -384,6 +393,8 @@ bool FurnaceGUIRenderDX11::init(SDL_Window* win) {
}
HWND window=(HWND)sysWindow.info.win.window;
swapInterval=swapInt;
DXGI_SWAP_CHAIN_DESC chainDesc;
memset(&chainDesc,0,sizeof(chainDesc));
chainDesc.BufferDesc.Width=0;

View file

@ -41,7 +41,7 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender {
ID3D11BlendState* omBlendState;
ID3D11Buffer* quadVertex;
int outW, outH;
int outW, outH, swapInterval;
bool dead;
@ -71,6 +71,7 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender {
void resized(const SDL_Event& ev);
void clear(ImVec4 color);
bool newFrame();
bool canVSync();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
@ -78,8 +79,9 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender {
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void setSwapInterval(int swapInterval);
void preInit();
bool init(SDL_Window* win);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
@ -94,6 +96,7 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender {
quadVertex(NULL),
outW(0),
outH(0),
swapInterval(1),
dead(false),
sh_wipe_vertex(NULL),
sh_wipe_fragment(NULL),

View file

@ -360,6 +360,10 @@ bool FurnaceGUIRenderGL::newFrame() {
return ImGui_ImplOpenGL3_NewFrame();
}
bool FurnaceGUIRenderGL::canVSync() {
return swapIntervalSet;
}
void FurnaceGUIRenderGL::createFontsTexture() {
ImGui_ImplOpenGL3_CreateFontsTexture();
}
@ -527,6 +531,16 @@ int FurnaceGUIRenderGL::getWindowFlags() {
return SDL_WINDOW_OPENGL;
}
void FurnaceGUIRenderGL::setSwapInterval(int swapInterval) {
SDL_GL_SetSwapInterval(swapInterval);
if (swapInterval>0 && SDL_GL_GetSwapInterval()==0) {
swapIntervalSet=false;
logW("tried to enable VSync but couldn't!");
} else {
swapIntervalSet=true;
}
}
void FurnaceGUIRenderGL::preInit() {
#if defined(USE_GLES)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
@ -567,14 +581,20 @@ void FurnaceGUIRenderGL::preInit() {
logW(_s " not found"); \
}
bool FurnaceGUIRenderGL::init(SDL_Window* win) {
bool FurnaceGUIRenderGL::init(SDL_Window* win, int swapInterval) {
sdlWin=win;
context=SDL_GL_CreateContext(win);
if (context==NULL) {
return false;
}
SDL_GL_MakeCurrent(win,context);
SDL_GL_SetSwapInterval(1);
SDL_GL_SetSwapInterval(swapInterval);
if (swapInterval>0 && SDL_GL_GetSwapInterval()==0) {
swapIntervalSet=false;
logW("tried to enable VSync but couldn't!");
} else {
swapIntervalSet=true;
}
LOAD_PROC_MANDATORY(furGenBuffers,PFNGLGENBUFFERSPROC,"glGenBuffers");
LOAD_PROC_MANDATORY(furBindBuffer,PFNGLBINDBUFFERPROC,"glBindBuffer");

View file

@ -46,6 +46,8 @@ class FurnaceGUIRenderGL: public FurnaceGUIRender {
int sh_oscRender_oscVal;
bool sh_oscRender_have;
bool swapIntervalSet;
bool createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program, const char** attribNames);
public:
@ -61,6 +63,7 @@ class FurnaceGUIRenderGL: public FurnaceGUIRender {
bool regenOscShader(const char* fragment);
void clear(ImVec4 color);
bool newFrame();
bool canVSync();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
@ -70,15 +73,17 @@ class FurnaceGUIRenderGL: public FurnaceGUIRender {
bool getOutputSize(int& w, int& h);
bool supportsDrawOsc();
int getWindowFlags();
void setSwapInterval(int swapInterval);
void preInit();
bool init(SDL_Window* win);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
bool isDead();
FurnaceGUIRenderGL():
context(NULL),
sdlWin(NULL) {
sdlWin(NULL),
swapIntervalSet(true) {
memset(quadVertex,0,4*3*sizeof(float));
memset(oscVertex,0,4*5*sizeof(float));
memset(oscData,0,2048*sizeof(float));

View file

@ -112,6 +112,10 @@ bool FurnaceGUIRenderSDL::newFrame() {
return ImGui_ImplSDLRenderer2_NewFrame();
}
bool FurnaceGUIRenderSDL::canVSync() {
return swapIntervalSet;
}
void FurnaceGUIRenderSDL::createFontsTexture() {
ImGui_ImplSDLRenderer2_CreateFontsTexture();
}
@ -142,12 +146,27 @@ int FurnaceGUIRenderSDL::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderSDL::setSwapInterval(int swapInterval) {
if (SDL_RenderSetVSync(sdlRend,(swapInterval>=0)?1:0)!=0) {
swapIntervalSet=false;
logW("tried to enable VSync but couldn't!");
} else {
swapIntervalSet=true;
}
}
void FurnaceGUIRenderSDL::preInit() {
}
bool FurnaceGUIRenderSDL::init(SDL_Window* win) {
bool FurnaceGUIRenderSDL::init(SDL_Window* win, int swapInterval) {
logV("creating SDL renderer...");
sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|((swapInterval>0)?SDL_RENDERER_PRESENTVSYNC:0)|SDL_RENDERER_TARGETTEXTURE);
if (SDL_RenderSetVSync(sdlRend,(swapInterval>=0)?1:0)!=0) {
swapIntervalSet=false;
logW("tried to enable VSync but couldn't!");
} else {
swapIntervalSet=true;
}
logV("(post creation)");
return (sdlRend!=NULL);
}

View file

@ -21,6 +21,7 @@
class FurnaceGUIRenderSDL: public FurnaceGUIRender {
SDL_Renderer* sdlRend;
bool swapIntervalSet;
public:
ImTextureID getTextureID(FurnaceGUITexture* which);
bool lockTexture(FurnaceGUITexture* which, void** data, int* pitch);
@ -32,6 +33,7 @@ class FurnaceGUIRenderSDL: public FurnaceGUIRender {
void setBlendMode(FurnaceGUIBlendMode mode);
void clear(ImVec4 color);
bool newFrame();
bool canVSync();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
@ -39,11 +41,13 @@ class FurnaceGUIRenderSDL: public FurnaceGUIRender {
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void setSwapInterval(int swapInterval);
void preInit();
bool init(SDL_Window* win);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderSDL():
sdlRend(NULL) {}
sdlRend(NULL),
swapIntervalSet(true) {}
};

View file

@ -409,6 +409,24 @@ void FurnaceGUI::drawSettings() {
}
}
bool vsyncB=settings.vsync;
if (ImGui::Checkbox("VSync",&vsyncB)) {
settings.vsync=vsyncB;
settingsChanged=true;
if (rend!=NULL) {
rend->setSwapInterval(settings.vsync);
}
}
if (ImGui::SliderInt("Frame rate limit",&settings.frameRateLimit,0,250,settings.frameRateLimit==0?"Unlimited":"%d")) {
settingsChanged=true;
}
if (settings.frameRateLimit<0) settings.frameRateLimit=0;
if (settings.frameRateLimit>1000) settings.frameRateLimit=1000;
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("only applies when VSync is disabled.");
}
bool renderClearPosB=settings.renderClearPos;
if (ImGui::Checkbox("Late render clear",&renderClearPosB)) {
settings.renderClearPos=renderClearPosB;
@ -3885,6 +3903,9 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.renderBackend=conf.getString("renderBackend",GUI_BACKEND_DEFAULT_NAME);
settings.renderClearPos=conf.getInt("renderClearPos",0);
settings.vsync=conf.getInt("vsync",1);
settings.frameRateLimit=conf.getInt("frameRateLimit",100);
settings.chanOscThreads=conf.getInt("chanOscThreads",0);
settings.renderPoolThreads=conf.getInt("renderPoolThreads",0);
settings.shaderOsc=conf.getInt("shaderOsc",0);
@ -4337,6 +4358,8 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
clampSetting(settings.shaderOsc,0,1);
clampSetting(settings.oscLineSize,0.25f,16.0f);
clampSetting(settings.cursorWheelStep,0,1);
clampSetting(settings.vsync,0,4);
clampSetting(settings.frameRateLimit,0,1000);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -4359,7 +4382,10 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
conf.set("renderBackend",settings.renderBackend);
conf.set("renderClearPos",settings.renderClearPos);
conf.set("vsync",settings.vsync);
conf.set("frameRateLimit",settings.frameRateLimit);
conf.set("chanOscThreads",settings.chanOscThreads);
conf.set("renderPoolThreads",settings.renderPoolThreads);
conf.set("shaderOsc",settings.shaderOsc);
@ -4654,6 +4680,10 @@ void FurnaceGUI::syncSettings() {
e->setMidiVolExp(midiMap.volExp);
e->setMetronomeVol(((float)settings.metroVol)/100.0f);
e->setSamplePreviewVol(((float)settings.sampleVol)/100.0f);
if (rend!=NULL) {
rend->setSwapInterval(settings.vsync);
}
}
void FurnaceGUI::commitSettings() {

View file

@ -493,64 +493,7 @@ static const char* cvText[]={
"GAME OVER",
"Conglaturation!\n"
"\n"
"With high score beat, it\n"
"enables Serious Mode.\n"
"The User is now peace.",
"Restart Furnace to apply\n"
"changes.",
"Restart Furnace to apply\n"
"changes.",
"Restart Furnace to apply\n"
"changes.",
"Restart Furnace to apply\n"
"changes.",
"Restart Furnace to apply\n"
"changes.",
"Never gonna give STOP posting\n"
"about Rick Astley",
"I'M TIRED OF SEEING IT",
"My friends on TikTok rickroll\n"
"me",
"On Discord's fucking rickroll",
"I was in a server. Right?",
"And AAAAALLL of the channels\n"
"are just Never Gonna Give You Up.",
"I've searched for Half-Life 3\n"
"and the video I watched it and\n"
"I said: Hey Babe, Never Gonna\n"
"Give You Up HAHA",
"Diiiiiiiing Diiiiiiiiiiiing\n"
"Diiing Diiiiiiing Diiiiiiiiiing\n"
"Didididiiiiiiiing",
"I fucking looked at the DefleMask\n"
"1.2 teaser trailer and I said\n"
"That's a rickroll",
"I've looked at my [REDACTED],\n"
"I think of the scientist and I go\n"
"[REDACTED], more like never gonna\n"
"[REDACTED] up\n",
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
// this is totally intentional. do not attempt to fix it.
(const char*)1
"High Score!"
};
void FurnaceGUI::syncTutorial() {