mirror of
https://github.com/k4zmu2a/SpaceCadetPinball.git
synced 2023-12-30 21:52:56 +00: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:
parent
22ce8ac538
commit
b4cb827d73
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include "SDL.h"
|
||||
|
@ -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,28 +156,29 @@ 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)
|
||||
{
|
||||
updateCounter = 300;
|
||||
if (DispFrameRate)
|
||||
{
|
||||
auto curTime = timeGetTimeAlt();
|
||||
if (prevTime)
|
||||
auto curTime = Clock::now();
|
||||
if (curTime - prevTime > DurationMs(1000))
|
||||
{
|
||||
char buf[60];
|
||||
auto elapsedSec = static_cast<float>(curTime - prevTime) * 0.001f;
|
||||
auto elapsedSec = DurationMs(curTime - prevTime).count() * 0.001;
|
||||
snprintf(buf, sizeof buf, "Updates/sec = %02.02f Frames/sec = %02.02f ",
|
||||
300.0f / elapsedSec, frameCounter / elapsedSec);
|
||||
updateCounter / elapsedSec, frameCounter / elapsedSec);
|
||||
SDL_SetWindowTitle(window, buf);
|
||||
FpsDetails = buf;
|
||||
frameCounter = 0;
|
||||
frameCounter = updateCounter = 0;
|
||||
prevTime = curTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (DispGRhistory)
|
||||
{
|
||||
@ -212,17 +202,13 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||
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);
|
||||
}
|
||||
}
|
||||
prevTime = curTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ProcessWindowMessages() || bQuit)
|
||||
break;
|
||||
@ -230,54 +216,34 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||
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 (curTime == then)
|
||||
{
|
||||
SDL_Delay(8);
|
||||
}
|
||||
else if (pb::frame(curTime - then))
|
||||
{
|
||||
auto deltaT = static_cast<int>(frameDuration);
|
||||
frameDuration -= deltaT;
|
||||
pb::frame(deltaT);
|
||||
if (gfr_display.BmpBufPtr1)
|
||||
{
|
||||
auto deltaT = now - then + 10;
|
||||
auto fillChar = static_cast<uint8_t>(deltaT);
|
||||
if (deltaT > 236)
|
||||
auto deltaTPal = deltaT + 10;
|
||||
auto fillChar = static_cast<uint8_t>(deltaTPal);
|
||||
if (deltaTPal > 236)
|
||||
{
|
||||
fillChar = 1;
|
||||
}
|
||||
gdrv::fill_bitmap(&gfr_display, 1, 10, 300 - updateCounter, 0, fillChar);
|
||||
}
|
||||
--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);
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user