diff --git a/CMakeLists.txt b/CMakeLists.txt index e7df74c..fca48f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(SpaceCadetPinball) set(CMAKE_CXX_STANDARD 11) +option(MUSIC_TSF "Use TinySoundFont for MIDI playback" ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bin) @@ -175,12 +176,16 @@ set(SOURCE_FILES SpaceCadetPinball/imstb_textedit.h SpaceCadetPinball/imstb_rectpack.h SpaceCadetPinball/imstb_truetype.h - SpaceCadetPinball/gm_sf2.cpp - SpaceCadetPinball/tsf.cpp - SpaceCadetPinball/tml.h - SpaceCadetPinball/tsf.h ) +if (MUSIC_TSF) + set(SOURCE_FILES ${SOURCE_FILES} + SpaceCadetPinball/gm_sf2.cpp + SpaceCadetPinball/tsf.cpp + SpaceCadetPinball/tml.h + SpaceCadetPinball/tsf.h) +endif() + add_executable(SpaceCadetPinball ${SOURCE_FILES}) # Skip pch on foreign code @@ -192,6 +197,9 @@ set_source_files_properties( SpaceCadetPinball/imgui_tables.cpp SpaceCadetPinball/imgui_demo.cpp SpaceCadetPinball/imgui_impl_sdl.cpp + SpaceCadetPinball/gm_sf2.cpp + SpaceCadetPinball/tsf.cpp + PROPERTIES SKIP_PRECOMPILE_HEADERS 1 ) target_precompile_headers(SpaceCadetPinball @@ -199,8 +207,13 @@ target_precompile_headers(SpaceCadetPinball SpaceCadetPinball/pch.h ) +if (MUSIC_TSF) + target_compile_definitions(SpaceCadetPinball PRIVATE -DMUSIC_TSF) +else() + target_compile_definitions(SpaceCadetPinball PRIVATE -DMUSIC_SDL) +endif() + target_link_libraries(SpaceCadetPinball ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY}) -target_compile_definitions(SpaceCadetPinball PRIVATE -DMUSIC_TSF) if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") target_link_libraries(SpaceCadetPinball idbfs.js) 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 e6e1623..db29d69 100644 --- a/SpaceCadetPinball/midi.cpp +++ b/SpaceCadetPinball/midi.cpp @@ -10,6 +10,8 @@ #endif midi_song midi::currentMidi = {false}; + +#ifdef MUSIC_TSF tml_message* midi::currentMessage = nullptr; static float midiTime = 0.0f; static float sampPerSec = 1000.0 / 22050.0; @@ -62,6 +64,7 @@ void midi::sdl_audio_callback(void* data, Uint8 *stream, int len) tsf_render_short(tsfSynth, (short*)stream, SampleBlock, 0); } } +#endif constexpr uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { @@ -93,7 +96,7 @@ int midi::play_pb_theme(int flag) #ifdef MUSIC_SDL int result = 0; music_stop(); - if (currentMidi) + if (currentMidi.valid) result = Mix_PlayMusic(currentMidi.handle, -1); return result; @@ -138,14 +141,23 @@ int midi::music_init() return music_init_ft(); } -#ifdef MUSIC_SDL - currentMidi = Mix_LoadMUS(pinball::get_rc_string(156, 0)); - return currentMidi != nullptr; +#if defined(MUSIC_SDL) + // 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); + auto song = Mix_LoadMUS(midiPath.c_str()); + if (song) { + currentMidi = {true, song}; + } else { + currentMidi = {false}; + } + + return currentMidi.valid; #elif defined(MUSIC_TSF) currentMessage = nullptr; currentMidi = {false}; - tsfSynth = tsf_load_memory(gm_sf2, (int)gm_sf2_len); int sampleRate; @@ -209,18 +221,27 @@ void midi::music_shutdown_ft() #ifdef MUSIC_SDL if (active_track.valid) Mix_HaltMusic(); - /*while (TrackList->GetCount()) - { - auto midi = TrackList->Get(0); - Mix_FreeMusic(midi.handle); - TrackList->Delete(midi); - }*/ + + for (auto& track : TrackList) { + if (track.valid) Mix_FreeMusic(track.handle); + } + + TrackList.clear(); active_track = {false}; - delete TrackList; #elif defined(MUSIC_TSF) + if (active_track.valid) { + tsf_note_off_all(tsfSynth); + active_track = {false, nullptr}; + currentMessage = nullptr; + midiTime = 0.0f; + } - active_track = {false}; + for (auto& track : TrackList) { + //if (track.valid) tml_free(track.handle); + } + + TrackList.clear(); #endif } @@ -237,7 +258,7 @@ midi_song 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) @@ -283,14 +304,14 @@ int midi::play_ft(midi_song* midi) } #ifdef MUSIC_SDL - if (Mix_PlayMusic(midi.handle, -1)) + if (Mix_PlayMusic(midi->handle, -1)) { - active_track = nullptr; + active_track = {false, nullptr}; result = 0; } else { - active_track = midi; + active_track = *midi; result = 1; } #elif defined(MUSIC_TSF) @@ -474,7 +495,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 cf1d06b..99704d2 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -44,6 +44,7 @@ short options::vk_list[28] std::map options::settings{}; +constexpr int options::MaxUps, options::MaxFps, options::MinUps, options::MinFps, options::DefUps, options::DefFps; void options::init() { @@ -100,6 +101,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 +131,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 bf91f36..262dd1b 100644 --- a/SpaceCadetPinball/winmain.cpp +++ b/SpaceCadetPinball/winmain.cpp @@ -17,8 +17,6 @@ #include #endif -const double TargetFps = 60, TargetFrameTime = 1000 / TargetFps; - SDL_Window *winmain::MainWindow = nullptr; SDL_Renderer *winmain::Renderer = nullptr; ImGuiIO *winmain::ImIO = nullptr; @@ -35,8 +33,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{}; @@ -49,14 +45,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; static bool loop_stop = false; @@ -201,144 +191,140 @@ 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; + run_loop([&]() { + if (DispFrameRate) + { + auto curTime = Clock::now(); + if (curTime - prevTime > DurationMs(1000)) + { + 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; + } + } - run_loop([&] - { - if (!updateCounter) - { - updateCounter = 300; - if (DispFrameRate) - { - 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; + } - 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); + } - *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); + } + } - gdrv::copy_bitmap(&render::vscreen, 300, 10, 0, 30, &gfr_display, 0, 0); - gdrv::fill_bitmap(&gfr_display, 300, 10, 0, 0, 0); - } - } - prevTime = curTime; - } - else - { - prevTime = 0; - } - } + if (!ProcessWindowMessages() || bQuit) { + loop_stop = true; + return; + } - if (!ProcessWindowMessages() || bQuit) - { - loop_stop = true; - return; - } + if (has_focus) + { + if (mouse_down) + { + 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 deltaT = static_cast(frameDuration); + frameDuration -= deltaT; + pb::frame(deltaT); + if (gfr_display.BmpBufPtr1) + { + auto deltaTPal = deltaT + 10; + auto fillChar = static_cast(deltaTPal); + if (deltaTPal > 236) + { + fillChar = 1; + } + gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - dtHistoryCounter, 0, fillChar); + --dtHistoryCounter; + } + updateCounter++; + } - if (has_focus) - { - 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); - } - } - if (!single_step) - { - auto curTime = timeGetTimeAlt(); - now = curTime; - if (no_time_loss) - { - then = curTime; - no_time_loss = 0; - } + if (UpdateToFrameCounter >= UpdateToFrameRatio) + { + UpdateToFrameCounter -= UpdateToFrameRatio; + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); - if (curTime == then) - { - SDL_Delay(8); - } - else if (pb::frame(curTime - then)) - { - if (gfr_display.BmpBufPtr1) - { - 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); - } - --updateCounter; - then = now; - } - } + RenderUi(); - auto frameEnd = static_cast(SDL_GetPerformanceCounter()); - auto elapsedMs = (frameEnd - frameStart) * sdlTimerResMs; - if (elapsedMs >= TargetFrameTime) - { - // Keep track of remainder, limited to one frame time. - frameStart = frameEnd - std::min(elapsedMs - TargetFrameTime, TargetFrameTime) / sdlTimerResMs; + SDL_RenderClear(renderer); + render::PresentVScreen(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); + ImGui::Render(); + ImGuiSDL::Render(ImGui::GetDrawData()); - RenderUi(); + SDL_RenderPresent(renderer); + frameCounter++; + } - SDL_RenderClear(renderer); - render::PresentVScreen(); + auto sdlError = SDL_GetError(); + if (sdlError[0]) + { + SDL_ClearError(); + printf("SDL Error: %s\n", sdlError); + } - ImGui::Render(); - ImGuiSDL::Render(ImGui::GetDrawData()); + auto updateEnd = Clock::now(); + auto targetTimeDelta = TargetFrameTime - DurationMs(updateEnd - frameStart) - sleepRemainder; - SDL_RenderPresent(renderer); - frameCounter++; - } + 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); + } - auto sdlError = SDL_GetError(); - if (sdlError[0]) - { - SDL_ClearError(); - printf("SDL Error: %s\n", sdlError); - } - } - }); + // Limit duration to 2 * target time + frameDuration = std::min(frameDuration + DurationMs(frameEnd - frameStart).count(), + 2 * TargetFrameTime.count()); + frameStart = frameEnd; + UpdateToFrameCounter++; + } + }); gdrv::destroy_bitmap(&gfr_display); options::uninit(); @@ -472,7 +458,7 @@ void winmain::RenderUi() } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Window")) + if (ImGui::BeginMenu("Graphics")) { if (ImGui::MenuItem("Uniform Scaling", nullptr, options::Options.UniformScaling)) { @@ -482,8 +468,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(); } @@ -814,3 +827,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(); }; diff --git a/game_resources/README.txt b/game_resources/README.txt new file mode 100644 index 0000000..9f92bc4 --- /dev/null +++ b/game_resources/README.txt @@ -0,0 +1,3 @@ +This directory is used for emscripten builds. + +Place all game files, excluding the .exe in this directory.