From fc68f17107920c72308e07d1007ac5f13a8bbba7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 2 Apr 2024 17:49:48 -0500 Subject: [PATCH] GUI: add option to disable VSync also add frame rate limiter --- src/gui/gui.cpp | 25 +++++++++++++-- src/gui/gui.h | 9 +++++- src/gui/render/abstract.cpp | 9 +++++- src/gui/render/renderDX11.cpp | 15 +++++++-- src/gui/render/renderDX11.h | 7 +++-- src/gui/render/renderGL.cpp | 24 ++++++++++++-- src/gui/render/renderGL.h | 9 ++++-- src/gui/render/renderSDL.cpp | 23 ++++++++++++-- src/gui/render/renderSDL.h | 8 +++-- src/gui/settings.cpp | 32 ++++++++++++++++++- src/gui/tutorial.cpp | 59 +---------------------------------- 11 files changed, 145 insertions(+), 75 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 34efe78f6..10d9b6b74 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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)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), diff --git a/src/gui/gui.h b/src/gui/gui.h index 7ac213c85..b5ebd4aea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -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; diff --git a/src/gui/render/abstract.cpp b/src/gui/render/abstract.cpp index 135cb9ee5..5d501b01c 100644 --- a/src/gui/render/abstract.cpp +++ b/src/gui/render/abstract.cpp @@ -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; } diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 71f25f42c..f103d5041 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -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; diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h index 20e598b43..eda263ed5 100644 --- a/src/gui/render/renderDX11.h +++ b/src/gui/render/renderDX11.h @@ -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), diff --git a/src/gui/render/renderGL.cpp b/src/gui/render/renderGL.cpp index 6f0a2a789..8e012c565 100644 --- a/src/gui/render/renderGL.cpp +++ b/src/gui/render/renderGL.cpp @@ -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"); diff --git a/src/gui/render/renderGL.h b/src/gui/render/renderGL.h index 57560763b..809ca8eb7 100644 --- a/src/gui/render/renderGL.h +++ b/src/gui/render/renderGL.h @@ -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)); diff --git a/src/gui/render/renderSDL.cpp b/src/gui/render/renderSDL.cpp index a291c3b09..dca54c3b6 100644 --- a/src/gui/render/renderSDL.cpp +++ b/src/gui/render/renderSDL.cpp @@ -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); } diff --git a/src/gui/render/renderSDL.h b/src/gui/render/renderSDL.h index b78770bc5..898638ebf 100644 --- a/src/gui/render/renderSDL.h +++ b/src/gui/render/renderSDL.h @@ -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) {} }; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1b503c217..3c0018fea 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -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() { diff --git a/src/gui/tutorial.cpp b/src/gui/tutorial.cpp index 816f71192..8b2e79c1a 100644 --- a/src/gui/tutorial.cpp +++ b/src/gui/tutorial.cpp @@ -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() {