diff --git a/SpaceCadetPinball/imgui_sdl.cpp b/SpaceCadetPinball/imgui_sdl.cpp index afb3055..789e6e0 100644 --- a/SpaceCadetPinball/imgui_sdl.cpp +++ b/SpaceCadetPinball/imgui_sdl.cpp @@ -100,6 +100,12 @@ namespace Clean(); } + + void Reset() + { + Order.clear(); + Container.clear(); + } private: void Clean() { @@ -149,6 +155,7 @@ namespace struct Device { SDL_Renderer* Renderer; + bool CacheWasInvalidated = false; struct ClipRect { @@ -522,6 +529,14 @@ namespace namespace ImGuiSDL { + static int ImGuiSDLEventWatch(void* userdata, SDL_Event* event) { + if (event->type == SDL_RENDER_TARGETS_RESET) { + // Device lost event, applies to DirectX and some mobile devices. + CurrentDevice->CacheWasInvalidated = true; + } + return 0; + } + void Initialize(SDL_Renderer* renderer, int windowWidth, int windowHeight) { ImGuiIO& io = ImGui::GetIO(); @@ -531,6 +546,12 @@ namespace ImGuiSDL ImGui::GetStyle().WindowRounding = 0.0f; ImGui::GetStyle().AntiAliasedFill = false; ImGui::GetStyle().AntiAliasedLines = false; + ImGui::GetStyle().ChildRounding = 0.0f; + ImGui::GetStyle().PopupRounding = 0.0f; + ImGui::GetStyle().FrameRounding = 0.0f; + ImGui::GetStyle().ScrollbarRounding = 0.0f; + ImGui::GetStyle().GrabRounding = 0.0f; + ImGui::GetStyle().TabRounding = 0.0f; // Loads the font texture. unsigned char* pixels; @@ -545,6 +566,7 @@ namespace ImGuiSDL io.Fonts->TexID = (void*)texture; CurrentDevice = new Device(renderer); + SDL_AddEventWatch(ImGuiSDLEventWatch, nullptr); } void Deinitialize() @@ -555,10 +577,17 @@ namespace ImGuiSDL delete texture; delete CurrentDevice; + SDL_DelEventWatch(ImGuiSDLEventWatch, nullptr); } void Render(ImDrawData* drawData) { + if (CurrentDevice->CacheWasInvalidated) { + CurrentDevice->CacheWasInvalidated = false; + CurrentDevice->GenericTriangleCache.Reset(); + CurrentDevice->UniformColorTriangleCache.Reset(); + } + SDL_BlendMode blendMode; SDL_GetRenderDrawBlendMode(CurrentDevice->Renderer, &blendMode); SDL_SetRenderDrawBlendMode(CurrentDevice->Renderer, SDL_BLENDMODE_BLEND); diff --git a/SpaceCadetPinball/midi.cpp b/SpaceCadetPinball/midi.cpp index 0aa3a43..9cb8ed0 100644 --- a/SpaceCadetPinball/midi.cpp +++ b/SpaceCadetPinball/midi.cpp @@ -59,7 +59,11 @@ int midi::music_init() return music_init_ft(); } - currentMidi = Mix_LoadMUS(pinball::get_rc_string(156, 0)); + // File name is in lower case, while game data is in upper case. + std::string fileName = pinball::get_rc_string(156, 0); + std::transform(fileName.begin(), fileName.end(), fileName.begin(), [](unsigned char c) { return std::toupper(c); }); + auto midiPath = pinball::make_path_name(fileName); + currentMidi = Mix_LoadMUS(midiPath.c_str()); return currentMidi != nullptr; } @@ -121,7 +125,7 @@ Mix_Music* midi::load_track(std::string fileName) fileName.insert(0, "SOUND"); } fileName += ".MDS"; - + auto filePath = pinball::make_path_name(fileName); auto midi = MdsToMidi(filePath); if (!midi) @@ -138,7 +142,7 @@ Mix_Music* midi::load_track(std::string fileName) delete midi; if (!audio) return nullptr; - + TrackList->Add(audio); return audio; } @@ -329,7 +333,7 @@ std::vector* midi::MdsToMidi(std::string file) midiBytes.insert(midiBytes.end(), metaEndTrack, metaEndTrack + 4); // Set final MTrk size - auto lengthBE = SwapByteOrderInt((uint32_t)midiBytes.size() - sizeof header - sizeof track); + auto lengthBE = SwapByteOrderInt(static_cast(midiBytes.size()) - sizeof header - sizeof track); auto lengthData = reinterpret_cast(&lengthBE); std::copy_n(lengthData, 4, midiBytes.begin() + lengthPos); } diff --git a/SpaceCadetPinball/options.cpp b/SpaceCadetPinball/options.cpp index d2fc6e7..658e96d 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -100,6 +100,11 @@ void options::init() ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f); Options.Resolution = get_int("Screen Resolution", -1); Options.LinearFiltering = get_int("Linear Filtering", true); + Options.FramesPerSecond = std::min(MaxFps, std::max(MinUps, get_int("Frames Per Second", DefFps))); + Options.UpdatesPerSecond = std::min(MaxUps, std::max(MinUps, get_int("Updates Per Second", DefUps))); + Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond, Options.FramesPerSecond); + + winmain::UpdateFrameRate(); Sound::Enable(0, 7, Options.Sounds); @@ -125,6 +130,8 @@ void options::uninit() set_int("Uniform scaling", Options.UniformScaling); set_float("UI Scale", ImGui::GetIO().FontGlobalScale); set_int("Linear Filtering", Options.LinearFiltering); + set_int("Frames Per Second", Options.FramesPerSecond); + set_int("Updates Per Second", Options.UpdatesPerSecond); } diff --git a/SpaceCadetPinball/options.h b/SpaceCadetPinball/options.h index 3b32e5d..accdbb9 100644 --- a/SpaceCadetPinball/options.h +++ b/SpaceCadetPinball/options.h @@ -49,12 +49,17 @@ struct optionsStruct int Resolution; bool UniformScaling; bool LinearFiltering; + int FramesPerSecond; + int UpdatesPerSecond; }; class options { public: + // Original does ~120 updates per second. + static constexpr int MaxUps = 360, MaxFps = MaxUps, MinUps = 60, MinFps = MinUps, + DefUps = 120, DefFps = 60; static optionsStruct Options; static void init(); diff --git a/SpaceCadetPinball/pb.cpp b/SpaceCadetPinball/pb.cpp index cf147dc..4b7f036 100644 --- a/SpaceCadetPinball/pb.cpp +++ b/SpaceCadetPinball/pb.cpp @@ -214,24 +214,27 @@ void pb::ballset(int x, int y) ball->Speed = maths::normalize_2d(&ball->Acceleration); } -int pb::frame(int time) +void pb::frame(int dtMilliSec) { - if (time > 100) - time = 100; - float timeMul = time * 0.001f; - if (!mode_countdown(time)) + + if (dtMilliSec > 100) + dtMilliSec = 100; + if (dtMilliSec <= 0) + return; + float dtMicroSec = dtMilliSec * 0.001f; + if (!mode_countdown(dtMilliSec)) { - time_next = time_now + timeMul; - timed_frame(time_now, timeMul, true); + time_next = time_now + dtMicroSec; + timed_frame(time_now, dtMicroSec, true); time_now = time_next; - time_ticks += time; + time_ticks += dtMilliSec; if (nudge::nudged_left || nudge::nudged_right || nudge::nudged_up) { - nudge::nudge_count = timeMul * 4.0f + nudge::nudge_count; + nudge::nudge_count = dtMicroSec * 4.0f + nudge::nudge_count; } else { - auto nudgeDec = nudge::nudge_count - timeMul; + auto nudgeDec = nudge::nudge_count - dtMicroSec; if (nudgeDec <= 0.0f) nudgeDec = 0.0; nudge::nudge_count = nudgeDec; @@ -249,7 +252,6 @@ int pb::frame(int time) MainTable->tilt(time_now); } } - return 1; } void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls) diff --git a/SpaceCadetPinball/pb.h b/SpaceCadetPinball/pb.h index 956ec71..17bd97e 100644 --- a/SpaceCadetPinball/pb.h +++ b/SpaceCadetPinball/pb.h @@ -48,7 +48,7 @@ public: static void toggle_demo(); static void replay_level(int demoMode); static void ballset(int x, int y); - static int frame(int time); + static void frame(int dtMilliSec); static void timed_frame(float timeNow, float timeDelta, bool drawBalls); static void window_size(int* width, int* height); static void pause_continue(); diff --git a/SpaceCadetPinball/pch.h b/SpaceCadetPinball/pch.h index 0e2940a..431ac1b 100644 --- a/SpaceCadetPinball/pch.h +++ b/SpaceCadetPinball/pch.h @@ -27,6 +27,7 @@ #include #include #include +#include #define SDL_MAIN_HANDLED #include "SDL.h" diff --git a/SpaceCadetPinball/winmain.cpp b/SpaceCadetPinball/winmain.cpp index 1a40297..a8bfbea 100644 --- a/SpaceCadetPinball/winmain.cpp +++ b/SpaceCadetPinball/winmain.cpp @@ -10,8 +10,6 @@ #include "render.h" #include "Sound.h" -const double TargetFps = 60, TargetFrameTime = 1000 / TargetFps; - SDL_Window* winmain::MainWindow = nullptr; SDL_Renderer* winmain::Renderer = nullptr; ImGuiIO* winmain::ImIO = nullptr; @@ -28,8 +26,6 @@ int winmain::last_mouse_y; int winmain::mouse_down; int winmain::no_time_loss; -DWORD winmain::then; -DWORD winmain::now; bool winmain::restart = false; gdrv_bitmap8 winmain::gfr_display{}; @@ -42,15 +38,8 @@ bool winmain::HighScoresEnabled = true; bool winmain::DemoActive = false; char* winmain::BasePath; std::string winmain::FpsDetails; - - -uint32_t timeGetTimeAlt() -{ - auto now = std::chrono::high_resolution_clock::now(); - auto duration = now.time_since_epoch(); - auto millis = std::chrono::duration_cast(duration).count(); - return static_cast(millis); -} +double winmain::UpdateToFrameRatio; +winmain::DurationMs winmain::TargetFrameTime; int winmain::WinMain(LPCSTR lpCmdLine) { @@ -167,60 +156,57 @@ int winmain::WinMain(LPCSTR lpCmdLine) else pb::replay_level(0); - DWORD updateCounter = 300u, frameCounter = 0, prevTime = 0u; - then = timeGetTimeAlt(); + DWORD dtHistoryCounter = 300u, updateCounter = 0, frameCounter = 0; - double sdlTimerResMs = 1000.0 / static_cast(SDL_GetPerformanceFrequency()); - auto frameStart = static_cast(SDL_GetPerformanceCounter()); + auto frameStart = Clock::now(); + double frameDuration = TargetFrameTime.count(), UpdateToFrameCounter = 0; + DurationMs sleepRemainder(0); + auto prevTime = frameStart; while (true) { - if (!updateCounter) + if (DispFrameRate) { - updateCounter = 300; - if (DispFrameRate) + auto curTime = Clock::now(); + if (curTime - prevTime > DurationMs(1000)) { - auto curTime = timeGetTimeAlt(); - if (prevTime) - { - char buf[60]; - auto elapsedSec = static_cast(curTime - prevTime) * 0.001f; - snprintf(buf, sizeof buf, "Updates/sec = %02.02f Frames/sec = %02.02f ", - 300.0f / elapsedSec, frameCounter / elapsedSec); - SDL_SetWindowTitle(window, buf); - FpsDetails = buf; - frameCounter = 0; - - if (DispGRhistory) - { - if (!gfr_display.BmpBufPtr1) - { - auto plt = static_cast(malloc(1024u)); - auto pltPtr = &plt[10]; - for (int i1 = 0, i2 = 0; i1 < 256 - 10; ++i1, i2 += 8) - { - unsigned char blue = i2, redGreen = i2; - if (i2 > 255) - { - blue = 255; - redGreen = i1; - } - - *pltPtr++ = ColorRgba{Rgba{redGreen, redGreen, blue, 0}}; - } - gdrv::display_palette(plt); - free(plt); - gdrv::create_bitmap(&gfr_display, 400, 15, 400, false); - } - - gdrv::copy_bitmap(&render::vscreen, 300, 10, 0, 30, &gfr_display, 0, 0); - gdrv::fill_bitmap(&gfr_display, 300, 10, 0, 0, 0); - } - } + char buf[60]; + auto elapsedSec = DurationMs(curTime - prevTime).count() * 0.001; + snprintf(buf, sizeof buf, "Updates/sec = %02.02f Frames/sec = %02.02f ", + updateCounter / elapsedSec, frameCounter / elapsedSec); + SDL_SetWindowTitle(window, buf); + FpsDetails = buf; + frameCounter = updateCounter = 0; prevTime = curTime; } - else + } + + if (DispGRhistory) + { + if (!gfr_display.BmpBufPtr1) { - prevTime = 0; + auto plt = static_cast(malloc(1024u)); + auto pltPtr = &plt[10]; + for (int i1 = 0, i2 = 0; i1 < 256 - 10; ++i1, i2 += 8) + { + unsigned char blue = i2, redGreen = i2; + if (i2 > 255) + { + blue = 255; + redGreen = i1; + } + + *pltPtr++ = ColorRgba{Rgba{redGreen, redGreen, blue, 0}}; + } + gdrv::display_palette(plt); + free(plt); + gdrv::create_bitmap(&gfr_display, 400, 15, 400, false); + } + + if (!dtHistoryCounter) + { + dtHistoryCounter = 300; + gdrv::copy_bitmap(&render::vscreen, 300, 10, 0, 30, &gfr_display, 0, 0); + gdrv::fill_bitmap(&gfr_display, 300, 10, 0, 0, 0); } } @@ -231,53 +217,33 @@ int winmain::WinMain(LPCSTR lpCmdLine) { if (mouse_down) { - now = timeGetTimeAlt(); - if (now - then >= 2) - { - int x, y; - SDL_GetMouseState(&x, &y); - pb::ballset(last_mouse_x - x, y - last_mouse_y); - SDL_WarpMouseInWindow(window, last_mouse_x, last_mouse_y); - } + int x, y; + SDL_GetMouseState(&x, &y); + pb::ballset(last_mouse_x - x, y - last_mouse_y); + SDL_WarpMouseInWindow(window, last_mouse_x, last_mouse_y); } if (!single_step) { - auto curTime = timeGetTimeAlt(); - now = curTime; - if (no_time_loss) + auto deltaT = static_cast(frameDuration); + frameDuration -= deltaT; + pb::frame(deltaT); + if (gfr_display.BmpBufPtr1) { - then = curTime; - no_time_loss = 0; - } - - if (curTime == then) - { - SDL_Delay(8); - } - else if (pb::frame(curTime - then)) - { - if (gfr_display.BmpBufPtr1) + auto deltaTPal = deltaT + 10; + auto fillChar = static_cast(deltaTPal); + if (deltaTPal > 236) { - auto deltaT = now - then + 10; - auto fillChar = static_cast(deltaT); - if (deltaT > 236) - { - fillChar = 1; - } - gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - updateCounter, 0, fillChar); + fillChar = 1; } - --updateCounter; - then = now; + gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - dtHistoryCounter, 0, fillChar); + --dtHistoryCounter; } + updateCounter++; } - auto frameEnd = static_cast(SDL_GetPerformanceCounter()); - auto elapsedMs = (frameEnd - frameStart) * sdlTimerResMs; - if (elapsedMs >= TargetFrameTime) + if (UpdateToFrameCounter >= UpdateToFrameRatio) { - // Keep track of remainder, limited to one frame time. - frameStart = frameEnd - std::min(elapsedMs - TargetFrameTime, TargetFrameTime) / sdlTimerResMs; - + UpdateToFrameCounter -= UpdateToFrameRatio; ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); @@ -299,6 +265,28 @@ int winmain::WinMain(LPCSTR lpCmdLine) SDL_ClearError(); printf("SDL Error: %s\n", sdlError); } + + auto updateEnd = Clock::now(); + auto targetTimeDelta = TargetFrameTime - DurationMs(updateEnd - frameStart) - sleepRemainder; + + TimePoint frameEnd; + if (targetTimeDelta > DurationMs::zero()) + { + std::this_thread::sleep_for(targetTimeDelta); + frameEnd = Clock::now(); + sleepRemainder = DurationMs(frameEnd - updateEnd) - targetTimeDelta; + } + else + { + frameEnd = updateEnd; + sleepRemainder = DurationMs(0); + } + + // Limit duration to 2 * target time + frameDuration = std::min(frameDuration + DurationMs(frameEnd - frameStart).count(), + 2 * TargetFrameTime.count()); + frameStart = frameEnd; + UpdateToFrameCounter++; } } @@ -430,7 +418,7 @@ void winmain::RenderUi() } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Window")) + if (ImGui::BeginMenu("Graphics")) { if (ImGui::MenuItem("Uniform Scaling", nullptr, options::Options.UniformScaling)) { @@ -440,8 +428,35 @@ void winmain::RenderUi() { options::toggle(Menu1::WindowLinearFilter); } - ImGui::DragFloat("", &ImIO->FontGlobalScale, 0.005f, 0.8f, 5, - "UI Scale %.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::DragFloat("UI Scale", &ImIO->FontGlobalScale, 0.005f, 0.8f, 5, + "%.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::Separator(); + + auto changed = false; + if (ImGui::MenuItem("Set Default UPS/FPS")) + { + changed = true; + options::Options.UpdatesPerSecond = options::DefUps; + options::Options.FramesPerSecond = options::DefFps; + } + if (ImGui::DragInt("UPS", &options::Options.UpdatesPerSecond, 1, options::MinUps, options::MaxUps, + "%d", ImGuiSliderFlags_AlwaysClamp)) + { + changed = true; + options::Options.FramesPerSecond = std::min(options::Options.UpdatesPerSecond, + options::Options.FramesPerSecond); + } + if (ImGui::DragInt("FPS", &options::Options.FramesPerSecond, 1, options::MinFps, options::MaxFps, + "%d", ImGuiSliderFlags_AlwaysClamp)) + { + changed = true; + options::Options.UpdatesPerSecond = std::max(options::Options.UpdatesPerSecond, + options::Options.FramesPerSecond); + } + if (changed) + { + UpdateFrameRate(); + } ImGui::EndMenu(); } @@ -760,3 +775,11 @@ void winmain::Restart() SDL_Event event{SDL_QUIT}; SDL_PushEvent(&event); } + +void winmain::UpdateFrameRate() +{ + // UPS >= FPS + auto fps = options::Options.FramesPerSecond, ups = options::Options.UpdatesPerSecond; + UpdateToFrameRatio = static_cast(ups) / fps; + TargetFrameTime = DurationMs(1000.0 / ups); +} diff --git a/SpaceCadetPinball/winmain.h b/SpaceCadetPinball/winmain.h index 82dd8f2..f40f42d 100644 --- a/SpaceCadetPinball/winmain.h +++ b/SpaceCadetPinball/winmain.h @@ -1,8 +1,44 @@ #pragma once #include "gdrv.h" +struct SdlTickClock +{ + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static constexpr bool is_steady = true; + + static time_point now() noexcept + { + return time_point{duration{SDL_GetTicks()}}; + } +}; + +struct SdlPerformanceClock +{ + using duration = std::chrono::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static constexpr bool is_steady = true; + + static time_point now() noexcept + { + const auto freq = SDL_GetPerformanceFrequency(); + const auto ctr = SDL_GetPerformanceCounter(); + const auto whole = (ctr / freq) * period::den; + const auto part = (ctr % freq) * period::den / freq; + return time_point(duration(whole + part)); + } +}; + class winmain { + using Clock = SdlPerformanceClock; // Or std::chrono::steady_clock. + using DurationMs = std::chrono::duration; + using TimePoint = std::chrono::time_point; + public: static std::string DatFileName; static int single_step; @@ -24,16 +60,18 @@ public: static void pause(); static void Restart(); static bool RestartRequested() { return restart; } + static void UpdateFrameRate(); private: static int return_value, bQuit, DispFrameRate, DispGRhistory, activated; static int has_focus, mouse_down, last_mouse_x, last_mouse_y, no_time_loss; - static DWORD then, now; static gdrv_bitmap8 gfr_display; static std::string FpsDetails; static bool restart; static bool ShowAboutDialog; static bool ShowImGuiDemo; static bool ShowSpriteViewer; + static double UpdateToFrameRatio; + static DurationMs TargetFrameTime; static void RenderUi(); };