1
0
mirror of https://github.com/k4zmu2a/SpaceCadetPinball.git synced 2024-11-22 02:32:39 +01:00

winmain: reworked main loop for smoother frame times.

imgui_sdl: added handling for device lost.
midi: load PINBALL.MID in uppercase and using absolute path.
Added UPS/FPS options, by default 120/60.
This commit is contained in:
Muzychenko Andrey 2021-09-28 08:14:18 +03:00
parent 22ce8ac538
commit b4cb827d73
9 changed files with 225 additions and 116 deletions

View File

@ -100,6 +100,12 @@ namespace
Clean(); Clean();
} }
void Reset()
{
Order.clear();
Container.clear();
}
private: private:
void Clean() void Clean()
{ {
@ -149,6 +155,7 @@ namespace
struct Device struct Device
{ {
SDL_Renderer* Renderer; SDL_Renderer* Renderer;
bool CacheWasInvalidated = false;
struct ClipRect struct ClipRect
{ {
@ -522,6 +529,14 @@ namespace
namespace ImGuiSDL 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) void Initialize(SDL_Renderer* renderer, int windowWidth, int windowHeight)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -531,6 +546,12 @@ namespace ImGuiSDL
ImGui::GetStyle().WindowRounding = 0.0f; ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().AntiAliasedFill = false; ImGui::GetStyle().AntiAliasedFill = false;
ImGui::GetStyle().AntiAliasedLines = 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. // Loads the font texture.
unsigned char* pixels; unsigned char* pixels;
@ -545,6 +566,7 @@ namespace ImGuiSDL
io.Fonts->TexID = (void*)texture; io.Fonts->TexID = (void*)texture;
CurrentDevice = new Device(renderer); CurrentDevice = new Device(renderer);
SDL_AddEventWatch(ImGuiSDLEventWatch, nullptr);
} }
void Deinitialize() void Deinitialize()
@ -555,10 +577,17 @@ namespace ImGuiSDL
delete texture; delete texture;
delete CurrentDevice; delete CurrentDevice;
SDL_DelEventWatch(ImGuiSDLEventWatch, nullptr);
} }
void Render(ImDrawData* drawData) void Render(ImDrawData* drawData)
{ {
if (CurrentDevice->CacheWasInvalidated) {
CurrentDevice->CacheWasInvalidated = false;
CurrentDevice->GenericTriangleCache.Reset();
CurrentDevice->UniformColorTriangleCache.Reset();
}
SDL_BlendMode blendMode; SDL_BlendMode blendMode;
SDL_GetRenderDrawBlendMode(CurrentDevice->Renderer, &blendMode); SDL_GetRenderDrawBlendMode(CurrentDevice->Renderer, &blendMode);
SDL_SetRenderDrawBlendMode(CurrentDevice->Renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawBlendMode(CurrentDevice->Renderer, SDL_BLENDMODE_BLEND);

View File

@ -59,7 +59,11 @@ int midi::music_init()
return music_init_ft(); 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; return currentMidi != nullptr;
} }
@ -121,7 +125,7 @@ Mix_Music* midi::load_track(std::string fileName)
fileName.insert(0, "SOUND"); fileName.insert(0, "SOUND");
} }
fileName += ".MDS"; fileName += ".MDS";
auto filePath = pinball::make_path_name(fileName); auto filePath = pinball::make_path_name(fileName);
auto midi = MdsToMidi(filePath); auto midi = MdsToMidi(filePath);
if (!midi) if (!midi)
@ -138,7 +142,7 @@ Mix_Music* midi::load_track(std::string fileName)
delete midi; delete midi;
if (!audio) if (!audio)
return nullptr; return nullptr;
TrackList->Add(audio); TrackList->Add(audio);
return audio; return audio;
} }
@ -329,7 +333,7 @@ std::vector<uint8_t>* midi::MdsToMidi(std::string file)
midiBytes.insert(midiBytes.end(), metaEndTrack, metaEndTrack + 4); midiBytes.insert(midiBytes.end(), metaEndTrack, metaEndTrack + 4);
// Set final MTrk size // Set final MTrk size
auto lengthBE = SwapByteOrderInt((uint32_t)midiBytes.size() - sizeof header - sizeof track); auto lengthBE = SwapByteOrderInt(static_cast<uint32_t>(midiBytes.size()) - sizeof header - sizeof track);
auto lengthData = reinterpret_cast<const uint8_t*>(&lengthBE); auto lengthData = reinterpret_cast<const uint8_t*>(&lengthBE);
std::copy_n(lengthData, 4, midiBytes.begin() + lengthPos); std::copy_n(lengthData, 4, midiBytes.begin() + lengthPos);
} }

View File

@ -100,6 +100,11 @@ void options::init()
ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f); ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f);
Options.Resolution = get_int("Screen Resolution", -1); Options.Resolution = get_int("Screen Resolution", -1);
Options.LinearFiltering = get_int("Linear Filtering", true); 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); Sound::Enable(0, 7, Options.Sounds);
@ -125,6 +130,8 @@ void options::uninit()
set_int("Uniform scaling", Options.UniformScaling); set_int("Uniform scaling", Options.UniformScaling);
set_float("UI Scale", ImGui::GetIO().FontGlobalScale); set_float("UI Scale", ImGui::GetIO().FontGlobalScale);
set_int("Linear Filtering", Options.LinearFiltering); set_int("Linear Filtering", Options.LinearFiltering);
set_int("Frames Per Second", Options.FramesPerSecond);
set_int("Updates Per Second", Options.UpdatesPerSecond);
} }

View File

@ -49,12 +49,17 @@ struct optionsStruct
int Resolution; int Resolution;
bool UniformScaling; bool UniformScaling;
bool LinearFiltering; bool LinearFiltering;
int FramesPerSecond;
int UpdatesPerSecond;
}; };
class options class options
{ {
public: 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 optionsStruct Options;
static void init(); static void init();

View File

@ -214,24 +214,27 @@ void pb::ballset(int x, int y)
ball->Speed = maths::normalize_2d(&ball->Acceleration); ball->Speed = maths::normalize_2d(&ball->Acceleration);
} }
int pb::frame(int time) void pb::frame(int dtMilliSec)
{ {
if (time > 100)
time = 100; if (dtMilliSec > 100)
float timeMul = time * 0.001f; dtMilliSec = 100;
if (!mode_countdown(time)) if (dtMilliSec <= 0)
return;
float dtMicroSec = dtMilliSec * 0.001f;
if (!mode_countdown(dtMilliSec))
{ {
time_next = time_now + timeMul; time_next = time_now + dtMicroSec;
timed_frame(time_now, timeMul, true); timed_frame(time_now, dtMicroSec, true);
time_now = time_next; time_now = time_next;
time_ticks += time; time_ticks += dtMilliSec;
if (nudge::nudged_left || nudge::nudged_right || nudge::nudged_up) 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 else
{ {
auto nudgeDec = nudge::nudge_count - timeMul; auto nudgeDec = nudge::nudge_count - dtMicroSec;
if (nudgeDec <= 0.0f) if (nudgeDec <= 0.0f)
nudgeDec = 0.0; nudgeDec = 0.0;
nudge::nudge_count = nudgeDec; nudge::nudge_count = nudgeDec;
@ -249,7 +252,6 @@ int pb::frame(int time)
MainTable->tilt(time_now); MainTable->tilt(time_now);
} }
} }
return 1;
} }
void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls) void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls)

View File

@ -48,7 +48,7 @@ public:
static void toggle_demo(); static void toggle_demo();
static void replay_level(int demoMode); static void replay_level(int demoMode);
static void ballset(int x, int y); 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 timed_frame(float timeNow, float timeDelta, bool drawBalls);
static void window_size(int* width, int* height); static void window_size(int* width, int* height);
static void pause_continue(); static void pause_continue();

View File

@ -27,6 +27,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <string> #include <string>
#include <thread>
#define SDL_MAIN_HANDLED #define SDL_MAIN_HANDLED
#include "SDL.h" #include "SDL.h"

View File

@ -10,8 +10,6 @@
#include "render.h" #include "render.h"
#include "Sound.h" #include "Sound.h"
const double TargetFps = 60, TargetFrameTime = 1000 / TargetFps;
SDL_Window* winmain::MainWindow = nullptr; SDL_Window* winmain::MainWindow = nullptr;
SDL_Renderer* winmain::Renderer = nullptr; SDL_Renderer* winmain::Renderer = nullptr;
ImGuiIO* winmain::ImIO = nullptr; ImGuiIO* winmain::ImIO = nullptr;
@ -28,8 +26,6 @@ int winmain::last_mouse_y;
int winmain::mouse_down; int winmain::mouse_down;
int winmain::no_time_loss; int winmain::no_time_loss;
DWORD winmain::then;
DWORD winmain::now;
bool winmain::restart = false; bool winmain::restart = false;
gdrv_bitmap8 winmain::gfr_display{}; gdrv_bitmap8 winmain::gfr_display{};
@ -42,15 +38,8 @@ bool winmain::HighScoresEnabled = true;
bool winmain::DemoActive = false; bool winmain::DemoActive = false;
char* winmain::BasePath; char* winmain::BasePath;
std::string winmain::FpsDetails; std::string winmain::FpsDetails;
double winmain::UpdateToFrameRatio;
winmain::DurationMs winmain::TargetFrameTime;
uint32_t timeGetTimeAlt()
{
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
return static_cast<uint32_t>(millis);
}
int winmain::WinMain(LPCSTR lpCmdLine) int winmain::WinMain(LPCSTR lpCmdLine)
{ {
@ -167,60 +156,57 @@ int winmain::WinMain(LPCSTR lpCmdLine)
else else
pb::replay_level(0); pb::replay_level(0);
DWORD updateCounter = 300u, frameCounter = 0, prevTime = 0u; DWORD dtHistoryCounter = 300u, updateCounter = 0, frameCounter = 0;
then = timeGetTimeAlt();
double sdlTimerResMs = 1000.0 / static_cast<double>(SDL_GetPerformanceFrequency()); auto frameStart = Clock::now();
auto frameStart = static_cast<double>(SDL_GetPerformanceCounter()); double frameDuration = TargetFrameTime.count(), UpdateToFrameCounter = 0;
DurationMs sleepRemainder(0);
auto prevTime = frameStart;
while (true) while (true)
{ {
if (!updateCounter) if (DispFrameRate)
{ {
updateCounter = 300; auto curTime = Clock::now();
if (DispFrameRate) if (curTime - prevTime > DurationMs(1000))
{ {
auto curTime = timeGetTimeAlt(); char buf[60];
if (prevTime) auto elapsedSec = DurationMs(curTime - prevTime).count() * 0.001;
{ snprintf(buf, sizeof buf, "Updates/sec = %02.02f Frames/sec = %02.02f ",
char buf[60]; updateCounter / elapsedSec, frameCounter / elapsedSec);
auto elapsedSec = static_cast<float>(curTime - prevTime) * 0.001f; SDL_SetWindowTitle(window, buf);
snprintf(buf, sizeof buf, "Updates/sec = %02.02f Frames/sec = %02.02f ", FpsDetails = buf;
300.0f / elapsedSec, frameCounter / elapsedSec); frameCounter = updateCounter = 0;
SDL_SetWindowTitle(window, buf);
FpsDetails = buf;
frameCounter = 0;
if (DispGRhistory)
{
if (!gfr_display.BmpBufPtr1)
{
auto plt = static_cast<ColorRgba*>(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);
}
}
prevTime = curTime; prevTime = curTime;
} }
else }
if (DispGRhistory)
{
if (!gfr_display.BmpBufPtr1)
{ {
prevTime = 0; auto plt = static_cast<ColorRgba*>(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) if (mouse_down)
{ {
now = timeGetTimeAlt(); int x, y;
if (now - then >= 2) SDL_GetMouseState(&x, &y);
{ pb::ballset(last_mouse_x - x, y - last_mouse_y);
int x, y; SDL_WarpMouseInWindow(window, last_mouse_x, last_mouse_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) if (!single_step)
{ {
auto curTime = timeGetTimeAlt(); auto deltaT = static_cast<int>(frameDuration);
now = curTime; frameDuration -= deltaT;
if (no_time_loss) pb::frame(deltaT);
if (gfr_display.BmpBufPtr1)
{ {
then = curTime; auto deltaTPal = deltaT + 10;
no_time_loss = 0; auto fillChar = static_cast<uint8_t>(deltaTPal);
} if (deltaTPal > 236)
if (curTime == then)
{
SDL_Delay(8);
}
else if (pb::frame(curTime - then))
{
if (gfr_display.BmpBufPtr1)
{ {
auto deltaT = now - then + 10; fillChar = 1;
auto fillChar = static_cast<uint8_t>(deltaT);
if (deltaT > 236)
{
fillChar = 1;
}
gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - updateCounter, 0, fillChar);
} }
--updateCounter; gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - dtHistoryCounter, 0, fillChar);
then = now; --dtHistoryCounter;
} }
updateCounter++;
} }
auto frameEnd = static_cast<double>(SDL_GetPerformanceCounter()); if (UpdateToFrameCounter >= UpdateToFrameRatio)
auto elapsedMs = (frameEnd - frameStart) * sdlTimerResMs;
if (elapsedMs >= TargetFrameTime)
{ {
// Keep track of remainder, limited to one frame time. UpdateToFrameCounter -= UpdateToFrameRatio;
frameStart = frameEnd - std::min(elapsedMs - TargetFrameTime, TargetFrameTime) / sdlTimerResMs;
ImGui_ImplSDL2_NewFrame(); ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
@ -299,6 +265,28 @@ int winmain::WinMain(LPCSTR lpCmdLine)
SDL_ClearError(); SDL_ClearError();
printf("SDL Error: %s\n", sdlError); 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(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Window")) if (ImGui::BeginMenu("Graphics"))
{ {
if (ImGui::MenuItem("Uniform Scaling", nullptr, options::Options.UniformScaling)) if (ImGui::MenuItem("Uniform Scaling", nullptr, options::Options.UniformScaling))
{ {
@ -440,8 +428,35 @@ void winmain::RenderUi()
{ {
options::toggle(Menu1::WindowLinearFilter); options::toggle(Menu1::WindowLinearFilter);
} }
ImGui::DragFloat("", &ImIO->FontGlobalScale, 0.005f, 0.8f, 5, ImGui::DragFloat("UI Scale", &ImIO->FontGlobalScale, 0.005f, 0.8f, 5,
"UI Scale %.2f", ImGuiSliderFlags_AlwaysClamp); "%.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(); ImGui::EndMenu();
} }
@ -760,3 +775,11 @@ void winmain::Restart()
SDL_Event event{SDL_QUIT}; SDL_Event event{SDL_QUIT};
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
void winmain::UpdateFrameRate()
{
// UPS >= FPS
auto fps = options::Options.FramesPerSecond, ups = options::Options.UpdatesPerSecond;
UpdateToFrameRatio = static_cast<double>(ups) / fps;
TargetFrameTime = DurationMs(1000.0 / ups);
}

View File

@ -1,8 +1,44 @@
#pragma once #pragma once
#include "gdrv.h" #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<SdlTickClock>;
static constexpr bool is_steady = true;
static time_point now() noexcept
{
return time_point{duration{SDL_GetTicks()}};
}
};
struct SdlPerformanceClock
{
using duration = std::chrono::duration<uint64_t, std::nano>;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<SdlPerformanceClock>;
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 class winmain
{ {
using Clock = SdlPerformanceClock; // Or std::chrono::steady_clock.
using DurationMs = std::chrono::duration<double, std::milli>;
using TimePoint = std::chrono::time_point<Clock>;
public: public:
static std::string DatFileName; static std::string DatFileName;
static int single_step; static int single_step;
@ -24,16 +60,18 @@ public:
static void pause(); static void pause();
static void Restart(); static void Restart();
static bool RestartRequested() { return restart; } static bool RestartRequested() { return restart; }
static void UpdateFrameRate();
private: private:
static int return_value, bQuit, DispFrameRate, DispGRhistory, activated; static int return_value, bQuit, DispFrameRate, DispGRhistory, activated;
static int has_focus, mouse_down, last_mouse_x, last_mouse_y, no_time_loss; 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 gdrv_bitmap8 gfr_display;
static std::string FpsDetails; static std::string FpsDetails;
static bool restart; static bool restart;
static bool ShowAboutDialog; static bool ShowAboutDialog;
static bool ShowImGuiDemo; static bool ShowImGuiDemo;
static bool ShowSpriteViewer; static bool ShowSpriteViewer;
static double UpdateToFrameRatio;
static DurationMs TargetFrameTime;
static void RenderUi(); static void RenderUi();
}; };