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();
}
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);

View File

@ -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<uint8_t>* 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<uint32_t>(midiBytes.size()) - sizeof header - sizeof track);
auto lengthData = reinterpret_cast<const uint8_t*>(&lengthBE);
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);
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);
}

View File

@ -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();

View File

@ -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)

View File

@ -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();

View File

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

View File

@ -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<std::chrono::milliseconds>(duration).count();
return static_cast<uint32_t>(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<double>(SDL_GetPerformanceFrequency());
auto frameStart = static_cast<double>(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<float>(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<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);
}
}
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<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)
{
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<int>(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<uint8_t>(deltaTPal);
if (deltaTPal > 236)
{
auto deltaT = now - then + 10;
auto fillChar = static_cast<uint8_t>(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<double>(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<double>(ups) / fps;
TargetFrameTime = DurationMs(1000.0 / ups);
}

View File

@ -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<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
{
using Clock = SdlPerformanceClock; // Or std::chrono::steady_clock.
using DurationMs = std::chrono::duration<double, std::milli>;
using TimePoint = std::chrono::time_point<Clock>;
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();
};