SpaceCadetPinball/SpaceCadetPinball/winmain.cpp

1417 lines
38 KiB
C++
Raw Permalink Normal View History

2020-11-05 16:44:34 +01:00
#include "pch.h"
#include "winmain.h"
2020-11-06 14:56:32 +01:00
#include "control.h"
#include "EmbeddedData.h"
2020-11-06 14:56:32 +01:00
#include "fullscrn.h"
2020-12-02 18:12:34 +01:00
#include "midi.h"
2020-11-05 16:44:34 +01:00
#include "options.h"
2020-11-06 14:56:32 +01:00
#include "pb.h"
#include "render.h"
#include "Sound.h"
#include "translations.h"
#include "font_selection.h"
2020-11-05 16:44:34 +01:00
constexpr const char* winmain::Version;
SDL_Window* winmain::MainWindow = nullptr;
SDL_Renderer* winmain::Renderer = nullptr;
ImGuiIO* winmain::ImIO = nullptr;
int winmain::return_value = 0;
bool winmain::bQuit = false;
bool winmain::activated = false;
bool winmain::DispFrameRate = false;
bool winmain::DispGRhistory = false;
bool winmain::single_step = false;
bool winmain::has_focus = true;
int winmain::last_mouse_x;
int winmain::last_mouse_y;
int winmain::mouse_down;
bool winmain::no_time_loss = false;
bool winmain::restart = false;
std::vector<float> winmain::gfrDisplay{};
2022-12-13 13:06:27 +01:00
unsigned winmain::gfrOffset = 0;
float winmain::gfrWindow = 5.0f;
bool winmain::ShowAboutDialog = false;
bool winmain::ShowImGuiDemo = false;
bool winmain::ShowSpriteViewer = false;
bool winmain::ShowExitPopup = false;
bool winmain::LaunchBallEnabled = true;
bool winmain::HighScoresEnabled = true;
bool winmain::DemoActive = false;
2021-10-30 09:12:30 +02:00
int winmain::MainMenuHeight = 0;
std::string winmain::FpsDetails, winmain::PrevSdlError;
unsigned winmain::PrevSdlErrorCount = 0;
double winmain::UpdateToFrameRatio;
winmain::DurationMs winmain::TargetFrameTime;
optionsStruct& winmain::Options = options::Options;
2021-11-18 15:58:53 +01:00
winmain::DurationMs winmain::SpinThreshold = DurationMs(0.005);
WelfordState winmain::SleepState{};
int winmain::CursorIdleCounter = 0;
int winmain::WinMain(LPCSTR lpCmdLine)
2020-11-05 16:44:34 +01:00
{
std::set_new_handler(memalloc_failure);
2020-11-05 16:44:34 +01:00
printf("Game version: %s\n", Version);
printf("Command line: %s\n", lpCmdLine);
printf("Compiled with: SDL %d.%d.%d;", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
printf(" SDL_mixer %d.%d.%d;", SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL);
2022-12-11 07:32:40 +01:00
printf(" ImGui %s %s\n", IMGUI_VERSION, ImGuiRender);
// SDL init
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO |
SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
2020-11-05 16:44:34 +01:00
{
pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, "Could not initialize SDL2", SDL_GetError());
return 1;
}
2020-11-05 16:44:34 +01:00
pb::quickFlag = strstr(lpCmdLine, "-quick") != nullptr;
2020-11-05 16:44:34 +01:00
// SDL window
SDL_Window* window = SDL_CreateWindow
(
pb::get_rc_string(Msg::STRING139),
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 556,
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE
);
MainWindow = window;
if (!window)
2020-11-06 14:56:32 +01:00
{
pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, "Could not create window", SDL_GetError());
return 1;
}
2021-10-30 09:12:30 +02:00
// If HW fails, fallback to SW SDL renderer.
SDL_Renderer* renderer = nullptr;
2021-11-18 15:58:53 +01:00
auto swOffset = strstr(lpCmdLine, "-sw") != nullptr ? 1 : 0;
for (int i = swOffset; i < 2 && !renderer; i++)
2021-10-30 09:12:30 +02:00
{
Renderer = renderer = SDL_CreateRenderer
(
window,
-1,
i == 0 ? SDL_RENDERER_ACCELERATED : SDL_RENDERER_SOFTWARE
);
}
if (!renderer)
{
pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, "Could not create renderer", SDL_GetError());
return 1;
}
2021-10-30 09:12:30 +02:00
SDL_RendererInfo rendererInfo{};
if (!SDL_GetRendererInfo(renderer, &rendererInfo))
printf("Using SDL renderer: %s\n", rendererInfo.name);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
auto prefPath = SDL_GetPrefPath("", "SpaceCadetPinball");
auto basePath = SDL_GetBasePath();
// SDL mixer init
bool mixOpened = false, noAudio = strstr(lpCmdLine, "-noaudio") != nullptr;
if (!noAudio)
{
if ((Mix_Init(MIX_INIT_MID_Proxy) & MIX_INIT_MID_Proxy) == 0)
{
printf("Could not initialize SDL MIDI, music might not work.\nSDL Error: %s\n", SDL_GetError());
SDL_ClearError();
}
if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) != 0)
{
printf("Could not open audio device, continuing without audio.\nSDL Error: %s\n", SDL_GetError());
SDL_ClearError();
}
else
mixOpened = true;
}
{
// Load SDL Game Controller definitions from DB
unsigned decompressedSize{};
const auto controllerDb = ImFontAtlas::DecompressCompressedStbData(
EmbeddedData::SDL_GameControllerDB_compressed_data,
EmbeddedData::SDL_GameControllerDB_compressed_size,
decompressedSize);
auto rw = SDL_RWFromMem(controllerDb, decompressedSize);
const auto added = SDL_GameControllerAddMappingsFromRW(rw, 1);
IM_FREE(controllerDb);
if (added < 0)
{
printf("Could not load game controller DB.\nSDL Error: %s\n", SDL_GetError());
SDL_ClearError();
}
}
auto resetAllOptions = strstr(lpCmdLine, "-reset") != nullptr;
do
{
restart = false;
// ImGui init
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImIO = &io;
auto iniPath = std::string(prefPath) + "imgui_pb.ini";
io.IniFilename = iniPath.c_str();
// First option initialization step: just load settings from .ini. Needs ImGui context.
options::InitPrimary();
if (resetAllOptions)
{
resetAllOptions = false;
options::ResetAllOptions();
}
if (!Options.FontFileName.V.empty())
{
ImVector<ImWchar> ranges;
translations::GetGlyphRange(&ranges);
ImFontConfig fontConfig{};
// ToDo: further tweak font options, maybe try imgui_freetype
fontConfig.OversampleV = 2;
fontConfig.OversampleH = 4;
// ToDo: improve font file test, checking if file exists is not enough
auto fontLoaded = false;
auto fileName = Options.FontFileName.V.c_str();
auto fileHandle = fopenu(fileName, "rb");
if (fileHandle)
{
fclose(fileHandle);
// ToDo: Bind font size to UI scale
if (io.Fonts->AddFontFromFileTTF(fileName, 13.f, &fontConfig, ranges.Data))
fontLoaded = true;
}
if (!fontLoaded)
printf("Failed to load font: %s, using embedded font.\n", fileName);
io.Fonts->Build();
}
2022-12-11 07:32:40 +01:00
ImGui_Render_Init(renderer);
ImGui::StyleColorsDark();
ImGui_ImplSDL2_InitForSDLRenderer(window, Renderer);
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
// Data search order: WD, executable path, user pref path, platform specific paths.
std::vector<const char*> searchPaths
{
{
"",
basePath,
prefPath
}
};
searchPaths.insert(searchPaths.end(), std::begin(PlatformDataPaths), std::end(PlatformDataPaths));
pb::SelectDatFile(searchPaths);
// Second step: run updates that depend on .DAT file selection
options::InitSecondary();
Sound::Init(mixOpened, Options.SoundChannels, Options.Sounds, Options.SoundVolume);
if (!mixOpened)
Options.Sounds = false;
if (!midi::music_init(mixOpened, Options.MusicVolume))
Options.Music = false;
if (pb::init())
{
std::string message = "The .dat file is missing.\n"
"Make sure that the game data is present in any of the following locations:\n";
for (auto path : searchPaths)
{
if (path)
{
message = message + (path[0] ? path : "working directory") + "\n";
}
}
pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, "Could not load game data", message.c_str());
return 1;
}
2020-11-06 14:56:32 +01:00
fullscrn::init();
pb::reset_table();
pb::firsttime_setup();
2020-11-06 14:56:32 +01:00
if (strstr(lpCmdLine, "-fullscreen"))
{
Options.FullScreen = true;
}
if (!Options.FullScreen)
{
auto resInfo = &fullscrn::resolution_array[fullscrn::GetResolution()];
SDL_SetWindowSize(MainWindow, resInfo->TableWidth, resInfo->TableHeight);
}
SDL_ShowWindow(window);
fullscrn::set_screen_mode(Options.FullScreen);
if (strstr(lpCmdLine, "-demo"))
pb::toggle_demo();
else
pb::replay_level(false);
MainLoop();
options::uninit();
midi::music_shutdown();
Sound::Close();
pb::uninit();
2022-12-11 07:32:40 +01:00
ImGui_Render_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
2020-11-06 14:56:32 +01:00
}
while (restart);
2020-11-06 14:56:32 +01:00
if (!noAudio)
{
if (mixOpened)
Mix_CloseAudio();
Mix_Quit();
}
SDL_free(basePath);
SDL_free(prefPath);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
2020-11-07 16:41:14 +01:00
return return_value;
}
void winmain::MainLoop()
{
bQuit = false;
unsigned updateCounter = 0, frameCounter = 0;
auto frameStart = Clock::now();
double UpdateToFrameCounter = 0;
DurationMs sleepRemainder(0), frameDuration(TargetFrameTime);
auto prevTime = frameStart;
2020-11-06 14:56:32 +01:00
while (true)
{
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(MainWindow, buf);
FpsDetails = buf;
frameCounter = updateCounter = 0;
prevTime = curTime;
}
}
2020-11-06 14:56:32 +01:00
if (!ProcessWindowMessages() || bQuit)
break;
if (has_focus)
{
if (mouse_down)
{
2021-10-10 16:13:43 +02:00
int x, y, w, h;
SDL_GetMouseState(&x, &y);
SDL_GetWindowSize(MainWindow, &w, &h);
float dx = static_cast<float>(last_mouse_x - x) / static_cast<float>(w);
float dy = static_cast<float>(y - last_mouse_y) / static_cast<float>(h);
2021-10-10 16:13:43 +02:00
pb::ballset(dx, dy);
// Original creates continuous mouse movement with mouse capture.
// Alternative solution: mouse warp at window edges.
int xMod = 0, yMod = 0;
if (x == 0 || x >= w - 1)
xMod = w - 2;
if (y == 0 || y >= h - 1)
yMod = h - 2;
if (xMod != 0 || yMod != 0)
{
// Mouse warp does not work over remote desktop or in some VMs
x = abs(x - xMod);
y = abs(y - yMod);
SDL_WarpMouseInWindow(MainWindow, x, y);
}
2021-10-10 16:13:43 +02:00
last_mouse_x = x;
last_mouse_y = y;
}
if (!single_step && !no_time_loss)
{
auto dt = static_cast<float>(frameDuration.count());
pb::frame(dt);
if (DispGRhistory)
{
2022-12-13 13:06:27 +01:00
auto targetSize = static_cast<unsigned>(static_cast<float>(Options.UpdatesPerSecond) * gfrWindow);
if (gfrDisplay.size() != targetSize)
{
2022-12-13 13:06:27 +01:00
gfrDisplay.resize(targetSize, static_cast<float>(TargetFrameTime.count()));
gfrOffset = 0;
}
2022-12-13 13:06:27 +01:00
gfrDisplay[gfrOffset] = dt;
gfrOffset = (gfrOffset + 1) % gfrDisplay.size();
}
updateCounter++;
}
no_time_loss = false;
if (UpdateToFrameCounter >= UpdateToFrameRatio)
{
if (Options.HideCursor && CursorIdleCounter <= 0)
ImGui::SetMouseCursor(ImGuiMouseCursor_None);
ImGui_ImplSDL2_NewFrame();
2022-12-11 07:32:40 +01:00
ImGui_Render_NewFrame();
ImGui::NewFrame();
RenderUi();
SDL_RenderClear(Renderer);
2021-12-26 11:25:25 +01:00
// Alternative clear hack, clear might fail on some systems
// Todo: remove original clear, if save for all platforms
SDL_RenderFillRect(Renderer, nullptr);
render::PresentVScreen();
ImGui::Render();
2022-12-11 07:32:40 +01:00
ImGui_Render_RenderDrawData(ImGui::GetDrawData());
SDL_RenderPresent(Renderer);
frameCounter++;
UpdateToFrameCounter -= UpdateToFrameRatio;
}
auto sdlError = SDL_GetError();
if (sdlError[0] || !PrevSdlError.empty())
{
if (sdlError[0])
SDL_ClearError();
// Rate limit duplicate SDL error messages.
if (sdlError != PrevSdlError)
{
PrevSdlError = sdlError;
if (PrevSdlErrorCount > 0)
{
printf("SDL Error: ^ Previous Error Repeated %u Times\n", PrevSdlErrorCount + 1);
PrevSdlErrorCount = 0;
}
if (sdlError[0])
printf("SDL Error: %s\n", sdlError);
}
else
{
PrevSdlErrorCount++;
}
}
auto updateEnd = Clock::now();
auto targetTimeDelta = TargetFrameTime - DurationMs(updateEnd - frameStart) - sleepRemainder;
TimePoint frameEnd;
if (targetTimeDelta > DurationMs::zero() && !Options.UncappedUpdatesPerSecond)
{
2021-11-18 15:58:53 +01:00
if (Options.HybridSleep)
HybridSleep(targetTimeDelta);
else
std::this_thread::sleep_for(targetTimeDelta);
frameEnd = Clock::now();
}
else
{
frameEnd = updateEnd;
}
// Limit duration to 2 * target time
sleepRemainder = Clamp(DurationMs(frameEnd - updateEnd) - targetTimeDelta, -TargetFrameTime,
TargetFrameTime);
frameDuration = std::min<DurationMs>(DurationMs(frameEnd - frameStart), 2 * TargetFrameTime);
frameStart = frameEnd;
UpdateToFrameCounter++;
CursorIdleCounter = std::max(CursorIdleCounter - static_cast<int>(frameDuration.count()), 0);
}
2020-11-06 14:56:32 +01:00
}
if (PrevSdlErrorCount > 0)
{
printf("SDL Error: ^ Previous Error Repeated %u Times\n", PrevSdlErrorCount);
}
2020-11-06 14:56:32 +01:00
}
void winmain::RenderUi()
2020-11-06 14:56:32 +01:00
{
// Transparent menu bar with a button for preventing menu lockout.
ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4{});
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4{});
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
if (!Options.ShowMenu && ImGui::BeginMainMenuBar())
{
if (ImGui::MenuItem("Menu"))
{
options::toggle(Menu1::Show_Menu);
ImGui::FocusWindow(nullptr);
}
ImGui::EndMainMenuBar();
}
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(2);
// No demo window in release to save space
#ifndef NDEBUG
if (ShowImGuiDemo)
ImGui::ShowDemoWindow(&ShowImGuiDemo);
#endif
if (Options.ShowMenu && ImGui::BeginMainMenuBar())
2020-12-02 18:12:34 +01:00
{
int currentMenuHeight = static_cast<int>(ImGui::GetWindowSize().y);
if (MainMenuHeight != currentMenuHeight)
{
// Get the height of the main menu bar and update screen coordinates
MainMenuHeight = currentMenuHeight;
fullscrn::window_size_changed();
}
if (ImGui::BeginMenu(pb::get_rc_string(Msg::Menu1_Game)))
2020-12-02 18:12:34 +01:00
{
ImGuiMenuItemWShortcut(GameBindings::NewGame);
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_Launch_Ball), nullptr, false, LaunchBallEnabled))
{
end_pause();
pb::launch_ball();
}
ImGuiMenuItemWShortcut(GameBindings::TogglePause);
ImGui::Separator();
2020-12-02 18:12:34 +01:00
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_High_Scores), nullptr, false, HighScoresEnabled))
2020-12-02 18:12:34 +01:00
{
2022-01-05 09:38:50 +01:00
pause(false);
pb::high_scores();
2020-12-02 18:12:34 +01:00
}
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_Demo), nullptr, DemoActive))
2020-12-02 18:12:34 +01:00
{
end_pause();
pb::toggle_demo();
2020-12-02 18:12:34 +01:00
}
ImGuiMenuItemWShortcut(GameBindings::Exit);
ImGui::EndMenu();
2020-12-02 18:12:34 +01:00
}
if (ImGui::BeginMenu(pb::get_rc_string(Msg::Menu1_Options)))
2020-12-02 18:12:34 +01:00
{
ImGuiMenuItemWShortcut(GameBindings::ToggleMenuDisplay, Options.ShowMenu);
ImGuiMenuItemWShortcut(GameBindings::ToggleFullScreen, Options.FullScreen);
if (ImGui::BeginMenu(pb::get_rc_string(Msg::Menu1_Select_Players)))
{
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_1Player), nullptr, Options.Players == 1))
{
options::toggle(Menu1::OnePlayer);
new_game();
}
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_2Players), nullptr, Options.Players == 2))
{
options::toggle(Menu1::TwoPlayers);
new_game();
}
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_3Players), nullptr, Options.Players == 3))
{
options::toggle(Menu1::ThreePlayers);
new_game();
}
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_4Players), nullptr, Options.Players == 4))
{
options::toggle(Menu1::FourPlayers);
new_game();
}
ImGui::EndMenu();
}
ImGuiMenuItemWShortcut(GameBindings::ShowControlDialog);
if (ImGui::BeginMenu("Language"))
{
auto currentLanguage = translations::GetCurrentLanguage();
for (auto& item : translations::Languages)
{
if (ImGui::MenuItem(item.DisplayName, nullptr, currentLanguage->Language == item.Language))
{
if (currentLanguage->Language != item.Language)
{
translations::SetCurrentLanguage(item.ShortName);
Restart();
}
}
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::BeginMenu("Audio"))
{
ImGuiMenuItemWShortcut(GameBindings::ToggleSounds, Options.Sounds);
Implement stereo sound. (#138) * Implement stereo sound. Original Space Cadet has mono sound. To achieve stereo, the following steps were accomplished: - Add a game option to turn on/off stereo sound. Default is on. - TPinballComponent objects were extended with a method called get_coordinates() that returns a single 2D point, approximating the on-screen position of the object, re-mapped between 0 and 1 vertically and horizontally, {0, 0} being at the top-left. - For static objects like bumpers and lights, the coordinate refers to the geometric center of the corresponding graphic sprite, and is precalculated at initialization. - For ball objects, the coordinate refers to the geometric center of the ball, calculated during play when requested. - Extend all calls to sound-playing methods so that they include a TPinballComponent* argument that refers to the sound source, e.g. where the sound comes from. For instance, when a flipper is activated, its method call to emit a sound now includes a reference to the flipper object; when a ball goes under a SkillShotGate, its method call to emit a sound now includes a reference to the corresponding light; and so on. For some cases, like light rollovers, the sound source is taken from the ball that triggered the light rollover. For other cases, like holes, flags and targets, the sound source is taken from the object itself. For some special cases like ramp activation, sound source is taken from the nearest light position that makes sense. For all game-progress sounds, like mission completion sounds or ball drain sounds, the sound source is undefined (set to nullptr), and the Sound::PlaySound() method takes care of positioning them at a default location, where speakers on a pinball machine normally are. - Make the Sound::PlaySound() method accept a new argument, a TPinballComponent reference, as described above. If the stereo option is turned on, the Sound::PlaySound() method calls the get_coordinates() method of the TPinballComponent reference to get the sound position. This project uses SDL_mixer and there is a function called Mix_SetPosition() that allows placing a sound in the stereo field, by giving it a distance and an angle. We arbitrarily place the player's ears at the bottom of the table; we set the ears' height to half a table's length. Intensity of the stereo effect is directly related to this value; the farther the player's ears from the table, the narrowest the stereo picture gets, and vice-versa. From there we have all we need to calculate distance and angle; we do just that and position all the sounds. * Copy-paste typo fix.
2022-05-30 09:35:29 +02:00
if (ImGui::MenuItem("Stereo Sound Effects", nullptr, Options.SoundStereo))
{
options::toggle(Menu1::SoundStereo);
}
ImGui::TextUnformatted("Sound Volume");
if (ImGui::SliderInt("##Sound Volume", &Options.SoundVolume.V, options::MinVolume, options::MaxVolume,
"%d",
ImGuiSliderFlags_AlwaysClamp))
2020-12-02 18:12:34 +01:00
{
Sound::SetVolume(Options.SoundVolume);
}
ImGui::TextUnformatted("Sound Channels");
if (ImGui::SliderInt("##Sound Channels", &Options.SoundChannels.V, options::MinSoundChannels,
options::MaxSoundChannels, "%d", ImGuiSliderFlags_AlwaysClamp))
{
Sound::SetChannels(Options.SoundChannels);
}
ImGui::Separator();
ImGuiMenuItemWShortcut(GameBindings::ToggleMusic, Options.Music);
ImGui::TextUnformatted("Music Volume");
if (ImGui::SliderInt("##Music Volume", &Options.MusicVolume.V, options::MinVolume, options::MaxVolume,
"%d",
ImGuiSliderFlags_AlwaysClamp))
{
midi::SetVolume(Options.MusicVolume);
2020-12-02 18:12:34 +01:00
}
ImGui::EndMenu();
2020-12-02 18:12:34 +01:00
}
if (ImGui::BeginMenu("Graphics"))
{
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_WindowUniformScale), nullptr, Options.UniformScaling))
{
options::toggle(Menu1::WindowUniformScale);
}
if (ImGui::MenuItem("Linear Filtering", nullptr, Options.LinearFiltering))
{
options::toggle(Menu1::WindowLinearFilter);
}
if (ImGui::MenuItem("Integer Scaling", nullptr, Options.IntegerScaling))
{
options::toggle(Menu1::WindowIntegerScale);
}
if (ImGui::DragFloat("UI Scale", &Options.UIScale.V, 0.005f, 0.8f, 5,
"%.2f", ImGuiSliderFlags_AlwaysClamp))
{
ImIO->FontGlobalScale = Options.UIScale;
}
ImGui::Separator();
2021-10-02 06:42:08 +02:00
char buffer[80]{};
auto changed = false;
if (ImGui::MenuItem("Set Default UPS/FPS"))
{
changed = true;
Options.UpdatesPerSecond = options::DefUps;
Options.FramesPerSecond = options::DefFps;
}
if (ImGui::SliderInt("UPS", &Options.UpdatesPerSecond.V, options::MinUps, options::MaxUps, "%d",
ImGuiSliderFlags_AlwaysClamp))
{
changed = true;
Options.FramesPerSecond = std::min(Options.UpdatesPerSecond.V, Options.FramesPerSecond.V);
}
if (ImGui::SliderInt("FPS", &Options.FramesPerSecond.V, options::MinFps, options::MaxFps, "%d",
ImGuiSliderFlags_AlwaysClamp))
{
changed = true;
Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond.V, Options.FramesPerSecond.V);
}
2021-10-02 06:42:08 +02:00
snprintf(buffer, sizeof buffer - 1, "Uncapped UPS (FPS ratio %02.02f)", UpdateToFrameRatio);
if (ImGui::MenuItem(buffer, nullptr, Options.UncappedUpdatesPerSecond))
2021-10-02 06:42:08 +02:00
{
Options.UncappedUpdatesPerSecond ^= true;
2021-10-02 06:42:08 +02:00
}
2021-11-18 15:58:53 +01:00
if (ImGui::MenuItem("Precise Sleep", nullptr, Options.HybridSleep))
{
Options.HybridSleep ^= true;
SleepState = WelfordState{};
SpinThreshold = DurationMs::zero();
}
2021-10-02 06:42:08 +02:00
if (changed)
{
UpdateFrameRate();
}
ImGui::Separator();
if (ImGui::MenuItem("Hide Cursor", nullptr, Options.HideCursor))
{
Options.HideCursor ^= true;
}
if (ImGui::MenuItem("Change Font..."))
{
font_selection::ShowDialog();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu(pb::get_rc_string(Msg::Menu1_Table_Resolution)))
{
char buffer[20]{};
auto resolutionStringId = Msg::Menu1_UseMaxResolution_640x480;
switch (fullscrn::GetMaxResolution())
{
case 0: resolutionStringId = Msg::Menu1_UseMaxResolution_640x480;
break;
case 1: resolutionStringId = Msg::Menu1_UseMaxResolution_800x600;
break;
case 2: resolutionStringId = Msg::Menu1_UseMaxResolution_1024x768;
break;
}
auto maxResText = pb::get_rc_string(resolutionStringId);
if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1))
{
options::toggle(Menu1::MaximumResolution);
}
for (auto i = 0; i <= fullscrn::GetMaxResolution(); i++)
{
auto& res = fullscrn::resolution_array[i];
snprintf(buffer, sizeof buffer - 1, "%d x %d", res.ScreenWidth, res.ScreenHeight);
if (ImGui::MenuItem(buffer, nullptr, Options.Resolution == i))
{
options::toggle(static_cast<Menu1>(static_cast<int>(Menu1::R640x480) + i));
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Game Data"))
{
if (ImGui::MenuItem("Prefer 3DPB Data", nullptr, Options.Prefer3DPBGameData))
{
options::toggle(Menu1::Prefer3DPBGameData);
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::MenuItem("Reset All Options"))
{
options::ResetAllOptions();
Restart();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu(pb::get_rc_string(Msg::Menu1_Help)))
{
#ifndef NDEBUG
if (ImGui::MenuItem("ImGui Demo", nullptr, ShowImGuiDemo))
2020-12-02 18:12:34 +01:00
{
ShowImGuiDemo ^= true;
2020-12-02 18:12:34 +01:00
}
#endif
if (ImGui::MenuItem("Sprite Viewer", nullptr, ShowSpriteViewer))
{
2022-01-05 09:38:50 +01:00
if (!ShowSpriteViewer)
pause(false);
ShowSpriteViewer ^= true;
}
if (pb::cheat_mode && ImGui::MenuItem("Frame Times", nullptr, DispGRhistory))
{
DispGRhistory ^= true;
}
if (ImGui::MenuItem("Debug Overlay", nullptr, Options.DebugOverlay))
{
Options.DebugOverlay ^= true;
}
if (Options.DebugOverlay && ImGui::BeginMenu("Overlay Options"))
{
if (ImGui::MenuItem("Box Grid", nullptr, Options.DebugOverlayGrid))
Options.DebugOverlayGrid ^= true;
if (ImGui::MenuItem("Ball Depth Grid", nullptr, Options.DebugOverlayBallDepthGrid))
Options.DebugOverlayBallDepthGrid ^= true;
if (ImGui::MenuItem("Sprite Positions", nullptr, Options.DebugOverlaySprites))
Options.DebugOverlaySprites ^= true;
if (ImGui::MenuItem("All Edges", nullptr, Options.DebugOverlayAllEdges))
Options.DebugOverlayAllEdges ^= true;
2022-12-28 06:47:44 +01:00
if (ImGui::MenuItem("Component AABB", nullptr, Options.DebugOverlayAabb))
Options.DebugOverlayAabb ^= true;
if (ImGui::MenuItem("Ball Position", nullptr, Options.DebugOverlayBallPosition))
Options.DebugOverlayBallPosition ^= true;
if (ImGui::MenuItem("Ball Box Edges", nullptr, Options.DebugOverlayBallEdges))
Options.DebugOverlayBallEdges ^= true;
if (ImGui::MenuItem("Sound Positions", nullptr, Options.DebugOverlaySounds))
Options.DebugOverlaySounds ^= true;
if (ImGui::MenuItem("Apply Collision Mask", nullptr, Options.DebugOverlayCollisionMask))
Options.DebugOverlayCollisionMask ^= true;
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Cheats"))
{
if (ImGui::MenuItem("hidden test", nullptr, pb::cheat_mode))
pb::PushCheat("hidden test");
if (ImGui::MenuItem("1max"))
pb::PushCheat("1max");
if (ImGui::MenuItem("bmax", nullptr, control::table_unlimited_balls))
pb::PushCheat("bmax");
if (ImGui::MenuItem("gmax"))
pb::PushCheat("gmax");
if (ImGui::MenuItem("rmax"))
pb::PushCheat("rmax");
if (pb::FullTiltMode && ImGui::MenuItem("quote"))
pb::PushCheat("quote");
if (ImGui::MenuItem("easy mode", nullptr, control::easyMode))
pb::PushCheat("easy mode");
ImGui::EndMenu();
}
ImGui::Separator();
2021-02-09 16:09:44 +01:00
if (ImGui::MenuItem(pb::get_rc_string(Msg::Menu1_About_Pinball)))
{
2022-01-05 09:38:50 +01:00
pause(false);
ShowAboutDialog = true;
}
ImGui::EndMenu();
}
if (DispFrameRate && !FpsDetails.empty())
if (ImGui::BeginMenu(FpsDetails.c_str()))
ImGui::EndMenu();
ImGui::EndMainMenuBar();
}
a_dialog();
high_score::RenderHighScoreDialog();
font_selection::RenderDialog();
if (ShowSpriteViewer)
render::SpriteViewer(&ShowSpriteViewer);
options::RenderControlDialog();
if (DispGRhistory)
RenderFrameTimeDialog();
const auto exitText = translations::GetTranslation(Msg::Menu1_Exit);
if (ShowExitPopup)
{
ShowExitPopup = false;
pause(false);
ImGui::OpenPopup(exitText);
2023-03-05 12:16:07 +01:00
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
}
if (ImGui::BeginPopupModal(exitText, nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Exit the game?");
ImGui::Separator();
if (ImGui::Button(pb::get_rc_string(Msg::GenericOk), ImVec2(120, 0)))
{
SDL_Event event{SDL_QUIT};
SDL_PushEvent(&event);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::IsWindowAppearing())
{
ImGui::SetKeyboardFocusHere(0);
}
if (ImGui::Button(pb::get_rc_string(Msg::GenericCancel), ImVec2(120, 0)))
{
end_pause();
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::EndPopup();
}
// Print game texts on the sidebar
gdrv::grtext_draw_ttext_in_box();
}
int winmain::event_handler(const SDL_Event* event)
{
auto inputDown = false;
switch (event->type)
{
case SDL_KEYDOWN:
case SDL_MOUSEBUTTONDOWN:
case SDL_CONTROLLERBUTTONDOWN:
inputDown = true;
break;
default: break;
}
if (!options::WaitingForInput() || !inputDown)
ImGui_ImplSDL2_ProcessEvent(event);
bool mouseEvent;
switch (event->type)
{
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
CursorIdleCounter = 1000;
mouseEvent = true;
break;
default:
mouseEvent = false;
break;
}
if (ImIO->WantCaptureMouse && !options::WaitingForInput())
{
if (mouse_down)
{
mouse_down = 0;
SDL_SetWindowGrab(MainWindow, SDL_FALSE);
}
if (mouseEvent)
return 1;
}
if (ImIO->WantCaptureKeyboard && !options::WaitingForInput())
{
switch (event->type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
return 1;
default: ;
2020-12-02 18:12:34 +01:00
}
}
switch (event->type)
2020-11-06 14:56:32 +01:00
{
case SDL_QUIT:
end_pause();
bQuit = true;
fullscrn::shutdown();
return_value = 0;
return 0;
case SDL_KEYUP:
pb::InputUp({InputTypes::Keyboard, event->key.keysym.sym});
break;
case SDL_KEYDOWN:
if (event->key.repeat)
break;
pb::InputDown({InputTypes::Keyboard, event->key.keysym.sym});
if (!pb::cheat_mode)
break;
switch (event->key.keysym.sym)
{
case SDLK_g:
DispGRhistory ^= true;
break;
case SDLK_o:
{
auto plt = new ColorRgba[4 * 256];
auto pltPtr = &plt[10]; // first 10 entries are system colors hardcoded in display_palette()
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{blue, redGreen, redGreen, 0};
}
gdrv::display_palette(plt);
delete[] plt;
}
break;
case SDLK_y:
SDL_SetWindowTitle(MainWindow, "Pinball");
DispFrameRate ^= true;
break;
case SDLK_F1:
pb::frame(10);
break;
case SDLK_F10:
single_step ^= true;
if (!single_step)
no_time_loss = true;
break;
default:
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
2020-11-06 14:56:32 +01:00
{
bool noInput = false;
switch (event->button.button)
2020-11-06 14:56:32 +01:00
{
case SDL_BUTTON_LEFT:
if (pb::cheat_mode)
{
mouse_down = 1;
last_mouse_x = event->button.x;
last_mouse_y = event->button.y;
SDL_SetWindowGrab(MainWindow, SDL_TRUE);
noInput = true;
}
break;
default:
break;
2020-11-06 14:56:32 +01:00
}
if (!noInput)
pb::InputDown({InputTypes::Mouse, event->button.button});
2020-11-06 14:56:32 +01:00
}
break;
case SDL_MOUSEBUTTONUP:
{
bool noInput = false;
switch (event->button.button)
{
case SDL_BUTTON_LEFT:
if (mouse_down)
{
mouse_down = 0;
SDL_SetWindowGrab(MainWindow, SDL_FALSE);
noInput = true;
}
break;
default:
break;
}
if (!noInput)
pb::InputUp({InputTypes::Mouse, event->button.button});
}
break;
case SDL_WINDOWEVENT:
switch (event->window.event)
{
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_TAKE_FOCUS:
case SDL_WINDOWEVENT_SHOWN:
activated = true;
Sound::Activate();
if (Options.Music && !single_step)
midi::music_play();
no_time_loss = true;
has_focus = true;
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
case SDL_WINDOWEVENT_HIDDEN:
activated = false;
fullscrn::activate(0);
Options.FullScreen = false;
Sound::Deactivate();
midi::music_stop();
has_focus = false;
pb::loose_focus();
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
fullscrn::window_size_changed();
break;
default: ;
}
break;
case SDL_JOYDEVICEADDED:
if (SDL_IsGameController(event->jdevice.which))
{
SDL_GameControllerOpen(event->jdevice.which);
}
break;
case SDL_JOYDEVICEREMOVED:
{
SDL_GameController* controller = SDL_GameControllerFromInstanceID(event->jdevice.which);
if (controller)
{
SDL_GameControllerClose(controller);
}
}
break;
case SDL_CONTROLLERBUTTONDOWN:
pb::InputDown({InputTypes::GameController, event->cbutton.button});
break;
case SDL_CONTROLLERBUTTONUP:
pb::InputUp({InputTypes::GameController, event->cbutton.button});
break;
default: ;
2020-11-06 14:56:32 +01:00
}
return 1;
}
int winmain::ProcessWindowMessages()
{
static auto idleWait = 0;
SDL_Event event;
if (has_focus)
2020-11-06 14:56:32 +01:00
{
idleWait = static_cast<int>(TargetFrameTime.count());
while (SDL_PollEvent(&event))
{
if (!event_handler(&event))
return 0;
}
return 1;
2020-11-06 14:56:32 +01:00
}
// Progressively wait longer when transitioning to idle
idleWait = std::min(idleWait + static_cast<int>(TargetFrameTime.count()), 500);
if (SDL_WaitEventTimeout(&event, idleWait))
{
idleWait = static_cast<int>(TargetFrameTime.count());
return event_handler(&event);
}
return 1;
2020-11-05 16:44:34 +01:00
}
2020-11-07 16:41:14 +01:00
void winmain::memalloc_failure()
2020-11-05 16:44:34 +01:00
{
2020-12-02 18:12:34 +01:00
midi::music_stop();
Sound::Close();
const char* caption = pb::get_rc_string(Msg::STRING270);
const char* text = pb::get_rc_string(Msg::STRING279);
pb::ShowMessageBox(SDL_MESSAGEBOX_ERROR, caption, text);
std::exit(1);
2020-11-05 16:44:34 +01:00
}
2020-11-07 16:41:14 +01:00
void winmain::a_dialog()
2020-11-07 16:41:14 +01:00
{
if (ShowAboutDialog == true)
{
ShowAboutDialog = false;
ImGui::OpenPopup(pb::get_rc_string(Msg::STRING204));
}
2020-11-07 16:41:14 +01:00
bool unused_open = true;
2023-03-26 13:35:27 +02:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{ 600, 300 });
if (ImGui::BeginPopupModal(pb::get_rc_string(Msg::STRING204), &unused_open, ImGuiWindowFlags_None))
{
2023-03-26 13:35:27 +02:00
if (ImGui::BeginTabBar("AboutTabBar", ImGuiTabBarFlags_None))
{
2023-03-26 13:35:27 +02:00
if (ImGui::BeginTabItem("3DPB"))
{
ImGui::TextUnformatted(pb::get_rc_string(Msg::STRING139));
ImGui::TextUnformatted("Original game by Cinematronics, Microsoft");
ImGui::Separator();
ImGui::TextUnformatted("Decompiled -> Ported to SDL");
ImGui::Text("Version %s", Version);
if (ImGui::SmallButton("Project home: https://github.com/k4zmu2a/SpaceCadetPinball"))
{
#if SDL_VERSION_ATLEAST(2, 0, 14)
2023-03-26 13:35:27 +02:00
// Relatively new feature, skip with older SDL
SDL_OpenURL("https://github.com/k4zmu2a/SpaceCadetPinball");
#endif
2023-03-26 13:35:27 +02:00
}
ImGui::EndTabItem();
}
ImGui::PushStyleColor(ImGuiCol_Button, 0);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, 0);
if (ImGui::BeginTabItem("Full Tilt!"))
{
const ImVec2 buttonCenter = { -1, 0 };
ImGui::Button("Full Tilt! was created by Cinematronics for Maxis.", buttonCenter);
ImGui::Button("Version 1.1", buttonCenter);
auto tableRow = [](LPCSTR textA, LPCSTR textB)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(textA);
ImGui::TableNextColumn();
ImGui::TextUnformatted(textB);
};
if (ImGui::BeginTable("Full Tilt!", 2))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Button("Cinematronics", buttonCenter);
ImGui::Separator();
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("PROGRAMMING", "ART");
tableRow("Michael Sandige", "John Frantz");
tableRow("John Taylor", "Ryan Medeiros");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("DESIGN", "SOUND EFFECTS");
tableRow("Kevin Gliner", "Matt Ridgeway");
tableRow(nullptr, "Donald S. Griffin");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("DESIGN CONSULTANT", "MUSIC");
tableRow("Mark Sprenger", "Matt Ridgeway");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("PRODUCER", "VOICES");
tableRow("Kevin Gliner", "Mike McGeary");
tableRow(nullptr, "William Rice");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("GRAND POOBAH", nullptr);
tableRow("David Stafford", nullptr);
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Button("SPECIAL THANKS", buttonCenter);
if (ImGui::BeginTable("Cinematronics", 2))
{
tableRow("Paula Sandige", "Alex St. John");
tableRow("Brad Silverberg", "Jeff Camp");
tableRow("Danny Thorpe", "Greg Hospelhorn");
tableRow("Keith Johnson", "Sean Grant");
tableRow("Bob McAnn", "Michael Kelley");
tableRow("Rob Rosenhouse", "Lisa Acton");
ImGui::EndTable();
}
ImGui::TextUnformatted("Dan and Mitchell Roth");
ImGui::TableNextColumn();
ImGui::Button("Maxis", buttonCenter);
ImGui::Separator();
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("PRODUCER", "PRODUCT MANAGER");
tableRow("John Csicsery", "Larry Lee");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("LEAD TESTER", "QA MANAGER");
tableRow("Scott Shicoff", "Scott Shicoff");
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Button("ADDITIONAL TESTING", buttonCenter);
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("Cathy Castro", "Robin Hines");
tableRow("John \"Jussi\" Ylinen", "Keith Meyer");
tableRow("Marc Meyer", "Owen Nelson");
tableRow("Joe Longworth", "Peter Saylor");
tableRow("Michael Gilmartin", "Robin Hines");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("ADDITIONAL ART", "ART DIRECTOR");
tableRow("Ocean Quigley", "Sharon Barr");
tableRow("Rick Macaraeg", "INSTALL PROGRAM");
tableRow("Charlie Aquilina", "Kevin O'Hare");
ImGui::EndTable();
}
ImGui::Separator();
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("INTRO MUSIC", "DOCUMENTATION");
tableRow("Brian Conrad", "David Caggiano");
tableRow("John Csicsery", "Michael Bremer");
tableRow(nullptr, "Bob Sombrio");
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Button("SPECIAL THANKS", buttonCenter);
if (ImGui::BeginTable("Maxis", 2))
{
tableRow("Sam Poole", "Joe Scirica");
tableRow("Jeff Braun", "Bob Derber");
tableRow("Ashley Csicsery", "Tom Forge");
ImGui::EndTable();
}
ImGui::Button("Will \"Burr\" Wright", buttonCenter);
ImGui::EndTable();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
ImGui::PopStyleColor(3);
}
2020-11-07 16:41:14 +01:00
2023-03-26 13:35:27 +02:00
ImGui::Separator();
if (ImGui::Button("Ok"))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
2023-03-26 13:35:27 +02:00
ImGui::PopStyleVar();
2020-11-07 16:41:14 +01:00
}
2020-12-02 18:12:34 +01:00
void winmain::end_pause()
{
if (single_step)
{
pb::pause_continue();
no_time_loss = true;
2020-12-02 18:12:34 +01:00
}
}
void winmain::new_game()
{
end_pause();
2022-01-05 09:38:50 +01:00
pb::replay_level(false);
2020-12-02 18:12:34 +01:00
}
2022-01-05 09:38:50 +01:00
void winmain::pause(bool toggle)
2020-12-02 18:12:34 +01:00
{
2022-01-05 09:38:50 +01:00
if (toggle || !single_step)
{
pb::pause_continue();
no_time_loss = true;
}
2020-12-02 18:12:34 +01:00
}
void winmain::Restart()
{
restart = true;
SDL_Event event{SDL_QUIT};
SDL_PushEvent(&event);
}
void winmain::UpdateFrameRate()
{
// UPS >= FPS
auto fps = Options.FramesPerSecond.V, ups = Options.UpdatesPerSecond.V;
UpdateToFrameRatio = static_cast<double>(ups) / fps;
TargetFrameTime = DurationMs(1000.0 / ups);
}
void winmain::HandleGameBinding(GameBindings binding, bool shortcut)
{
switch (binding)
{
case GameBindings::TogglePause:
pause();
break;
case GameBindings::NewGame:
new_game();
break;
case GameBindings::ToggleFullScreen:
options::toggle(Menu1::Full_Screen);
break;
case GameBindings::ToggleSounds:
options::toggle(Menu1::Sounds);
break;
case GameBindings::ToggleMusic:
options::toggle(Menu1::Music);
break;
case GameBindings::ShowControlDialog:
pause(false);
options::ShowControlDialog();
break;
case GameBindings::ToggleMenuDisplay:
options::toggle(Menu1::Show_Menu);
break;
case GameBindings::Exit:
if (!shortcut)
{
SDL_Event event{SDL_QUIT};
SDL_PushEvent(&event);
}
else
ShowExitPopup = true;
break;
default:
break;
}
}
void winmain::RenderFrameTimeDialog()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{300, 70});
if (ImGui::Begin("Frame Times", &DispGRhistory, ImGuiWindowFlags_NoScrollbar))
{
auto target = static_cast<float>(TargetFrameTime.count());
2022-12-13 13:06:27 +01:00
auto yMax = target * 2;
2021-11-18 15:58:53 +01:00
auto spin = Options.HybridSleep ? static_cast<float>(SpinThreshold.count()) : 0;
2022-12-13 13:06:27 +01:00
ImGui::Text("YMin:0ms, Target frame time:%03.04fms, YMax:%03.04fms, SpinThreshold:%03.04fms",
target, yMax, spin);
static bool scrollPlot = true;
ImGui::Checkbox("Scroll Plot", &scrollPlot);
2022-12-13 13:06:27 +01:00
ImGui::SameLine();
ImGui::SliderFloat("Window Size", &gfrWindow, 0.1f, 15, "%.3fsec", ImGuiSliderFlags_AlwaysClamp);
{
float average = 0.0f, dev = 0.0f;
for (auto n : gfrDisplay)
2022-12-13 13:06:27 +01:00
{
average += n;
dev += std::abs(target - n);
}
average /= static_cast<float>(gfrDisplay.size());
dev /= static_cast<float>(gfrDisplay.size());
char overlay[64];
sprintf(overlay, "avg %.3fms, dev %.3fms", average, dev);
auto region = ImGui::GetContentRegionAvail();
ImGui::PlotLines("Lines", gfrDisplay.data(), static_cast<int>(gfrDisplay.size()),
scrollPlot ? gfrOffset : 0, overlay, 0, yMax, region);
2022-12-13 13:06:27 +01:00
}
}
ImGui::End();
ImGui::PopStyleVar();
}
2021-11-18 15:58:53 +01:00
void winmain::HybridSleep(DurationMs sleepTarget)
{
2021-11-18 15:58:53 +01:00
static constexpr double StdDevFactor = 0.5;
// This nice concept is from https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
// Sacrifices some CPU time for smaller frame time jitter
while (sleepTarget > SpinThreshold)
{
auto start = Clock::now();
std::this_thread::sleep_for(DurationMs(1));
auto end = Clock::now();
auto actualDuration = DurationMs(end - start);
sleepTarget -= actualDuration;
// Update expected sleep duration using Welford's online algorithm
// With bad timer, this will run away to 100% spin
SleepState.Advance(actualDuration.count());
SpinThreshold = DurationMs(SleepState.mean + SleepState.GetStdDev() * StdDevFactor);
}
// spin lock
for (auto start = Clock::now(); DurationMs(Clock::now() - start) < sleepTarget;);
}
void winmain::ImGuiMenuItemWShortcut(GameBindings binding, bool selected)
{
const auto& keyDef = Options.Key[~binding];
if (ImGui::MenuItem(pb::get_rc_string(keyDef.Description), keyDef.GetShortcutDescription().c_str(), selected))
{
HandleGameBinding(binding, false);
}
}