417 lines
11 KiB
C++
417 lines
11 KiB
C++
#include "PowderToySDL.h"
|
|
#include "SimulationConfig.h"
|
|
#include "WindowIcon.h"
|
|
#include "Config.h"
|
|
#include "gui/interface/Engine.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "common/platform/Platform.h"
|
|
#include <iostream>
|
|
|
|
int desktopWidth = 1280;
|
|
int desktopHeight = 1024;
|
|
SDL_Window *sdl_window = NULL;
|
|
SDL_Renderer *sdl_renderer = NULL;
|
|
SDL_Texture *sdl_texture = NULL;
|
|
int scale = 1;
|
|
bool fullscreen = false;
|
|
bool altFullscreen = false;
|
|
bool forceIntegerScaling = true;
|
|
bool vsyncHint = false;
|
|
bool resizable = false;
|
|
bool momentumScroll = true;
|
|
bool showAvatars = true;
|
|
uint64_t lastTick = 0;
|
|
uint64_t lastFpsUpdate = 0;
|
|
bool showLargeScreenDialog = false;
|
|
int mousex = 0;
|
|
int mousey = 0;
|
|
int mouseButton = 0;
|
|
bool mouseDown = false;
|
|
bool calculatedInitialMouse = false;
|
|
bool hasMouseMoved = false;
|
|
double correctedFrameTimeAvg = 0;
|
|
uint64_t drawingTimer = 0;
|
|
uint64_t frameStart = 0;
|
|
uint64_t oldFrameStart = 0;
|
|
|
|
void StartTextInput()
|
|
{
|
|
SDL_StartTextInput();
|
|
}
|
|
|
|
void StopTextInput()
|
|
{
|
|
SDL_StopTextInput();
|
|
}
|
|
|
|
void SetTextInputRect(int x, int y, int w, int h)
|
|
{
|
|
SDL_Rect rect;
|
|
rect.x = x;
|
|
rect.y = y;
|
|
rect.w = w;
|
|
rect.h = h;
|
|
SDL_SetTextInputRect(&rect);
|
|
}
|
|
|
|
void ClipboardPush(ByteString text)
|
|
{
|
|
SDL_SetClipboardText(text.c_str());
|
|
}
|
|
|
|
ByteString ClipboardPull()
|
|
{
|
|
return ByteString(SDL_GetClipboardText());
|
|
}
|
|
|
|
int GetModifiers()
|
|
{
|
|
return SDL_GetModState();
|
|
}
|
|
|
|
unsigned int GetTicks()
|
|
{
|
|
return SDL_GetTicks();
|
|
}
|
|
|
|
void CalculateMousePosition(int *x, int *y)
|
|
{
|
|
int globalMx, globalMy;
|
|
SDL_GetGlobalMouseState(&globalMx, &globalMy);
|
|
int windowX, windowY;
|
|
SDL_GetWindowPosition(sdl_window, &windowX, &windowY);
|
|
|
|
if (x)
|
|
*x = (globalMx - windowX) / scale;
|
|
if (y)
|
|
*y = (globalMy - windowY) / scale;
|
|
}
|
|
|
|
void blit(pixel *vid)
|
|
{
|
|
SDL_UpdateTexture(sdl_texture, NULL, vid, WINDOWW * sizeof (Uint32));
|
|
// need to clear the renderer if there are black edges (fullscreen, or resizable window)
|
|
if (fullscreen || resizable)
|
|
SDL_RenderClear(sdl_renderer);
|
|
SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
|
|
SDL_RenderPresent(sdl_renderer);
|
|
}
|
|
|
|
void SDLOpen()
|
|
{
|
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
|
|
{
|
|
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
|
|
Platform::Exit(-1);
|
|
}
|
|
|
|
if (!RecreateWindow())
|
|
{
|
|
fprintf(stderr, "Creating SDL window: %s\n", SDL_GetError());
|
|
Platform::Exit(-1);
|
|
}
|
|
|
|
int displayIndex = SDL_GetWindowDisplayIndex(sdl_window);
|
|
if (displayIndex >= 0)
|
|
{
|
|
SDL_Rect rect;
|
|
if (!SDL_GetDisplayUsableBounds(displayIndex, &rect))
|
|
{
|
|
desktopWidth = rect.w;
|
|
desktopHeight = rect.h;
|
|
}
|
|
}
|
|
|
|
if constexpr (SET_WINDOW_ICON)
|
|
{
|
|
WindowIcon(sdl_window);
|
|
}
|
|
}
|
|
|
|
void SDLClose()
|
|
{
|
|
if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL)
|
|
{
|
|
// * nvidia-460 egl registers callbacks with x11 that end up being called
|
|
// after egl is unloaded unless we grab it here and release it after
|
|
// sdl closes the display. this is an nvidia driver weirdness but
|
|
// technically an sdl bug. glfw has this fixed:
|
|
// https://github.com/glfw/glfw/commit/9e6c0c747be838d1f3dc38c2924a47a42416c081
|
|
SDL_GL_LoadLibrary(NULL);
|
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
SDL_GL_UnloadLibrary();
|
|
}
|
|
SDL_Quit();
|
|
}
|
|
|
|
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_, bool vsyncHint_)
|
|
{
|
|
// bool changingScale = scale != scale_;
|
|
bool changingFullscreen = fullscreen_ != fullscreen || (altFullscreen_ != altFullscreen && fullscreen);
|
|
bool changingResizable = resizable != resizable_;
|
|
bool changingVsync = vsyncHint != vsyncHint_;
|
|
scale = scale_;
|
|
fullscreen = fullscreen_;
|
|
altFullscreen = altFullscreen_;
|
|
resizable = resizable_;
|
|
forceIntegerScaling = forceIntegerScaling_;
|
|
vsyncHint = vsyncHint_;
|
|
// Recreate the window when toggling fullscreen, due to occasional issues
|
|
// Also recreate it when enabling resizable windows, to fix bugs on windows,
|
|
// see https://github.com/jacob1/The-Powder-Toy/issues/24
|
|
if (changingFullscreen || altFullscreen || (changingResizable && resizable && !fullscreen) || changingVsync)
|
|
{
|
|
RecreateWindow();
|
|
return;
|
|
}
|
|
if (changingResizable)
|
|
SDL_RestoreWindow(sdl_window);
|
|
|
|
SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
|
|
SDL_RenderSetIntegerScale(sdl_renderer, forceIntegerScaling && fullscreen ? SDL_TRUE : SDL_FALSE);
|
|
unsigned int flags = 0;
|
|
if (fullscreen)
|
|
flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
SDL_SetWindowFullscreen(sdl_window, flags);
|
|
if (fullscreen)
|
|
SDL_RaiseWindow(sdl_window);
|
|
SDL_SetWindowResizable(sdl_window, resizable ? SDL_TRUE : SDL_FALSE);
|
|
}
|
|
|
|
bool RecreateWindow()
|
|
{
|
|
unsigned int flags = 0;
|
|
unsigned int rendererFlags = 0;
|
|
if (fullscreen)
|
|
flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
if (resizable && !fullscreen)
|
|
flags |= SDL_WINDOW_RESIZABLE;
|
|
if (vsyncHint)
|
|
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
|
|
|
|
if (sdl_texture)
|
|
SDL_DestroyTexture(sdl_texture);
|
|
if (sdl_renderer)
|
|
SDL_DestroyRenderer(sdl_renderer);
|
|
if (sdl_window)
|
|
{
|
|
SaveWindowPosition();
|
|
SDL_DestroyWindow(sdl_window);
|
|
}
|
|
|
|
sdl_window = SDL_CreateWindow(APPNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOWW * scale, WINDOWH * scale,
|
|
flags);
|
|
if (!sdl_window)
|
|
{
|
|
return false;
|
|
}
|
|
sdl_renderer = SDL_CreateRenderer(sdl_window, -1, rendererFlags);
|
|
if (!sdl_renderer)
|
|
{
|
|
fprintf(stderr, "SDL_CreateRenderer failed; available renderers:\n");
|
|
int num = SDL_GetNumRenderDrivers();
|
|
for (int i = 0; i < num; ++i)
|
|
{
|
|
SDL_RendererInfo info;
|
|
SDL_GetRenderDriverInfo(i, &info);
|
|
fprintf(stderr, " - %s\n", info.name);
|
|
}
|
|
return false;
|
|
}
|
|
SDL_RenderSetLogicalSize(sdl_renderer, WINDOWW, WINDOWH);
|
|
if (forceIntegerScaling && fullscreen)
|
|
SDL_RenderSetIntegerScale(sdl_renderer, SDL_TRUE);
|
|
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WINDOWW, WINDOWH);
|
|
SDL_RaiseWindow(sdl_window);
|
|
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
|
//Uncomment this to enable resizing
|
|
//SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
|
//SDL_SetWindowResizable(sdl_window, SDL_TRUE);
|
|
|
|
LoadWindowPosition();
|
|
UpdateFpsLimit();
|
|
|
|
return true;
|
|
}
|
|
|
|
void EventProcess(const SDL_Event &event)
|
|
{
|
|
auto &engine = ui::Engine::Ref();
|
|
switch (event.type)
|
|
{
|
|
case SDL_QUIT:
|
|
if (ALLOW_QUIT && (engine.GetFastQuit() || engine.CloseWindow()))
|
|
{
|
|
engine.Exit();
|
|
}
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
if (SDL_GetModState() & KMOD_GUI)
|
|
{
|
|
break;
|
|
}
|
|
if (ALLOW_QUIT && !event.key.repeat && event.key.keysym.sym == 'q' && (event.key.keysym.mod&KMOD_CTRL))
|
|
engine.ConfirmExit();
|
|
else
|
|
engine.onKeyPress(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT);
|
|
break;
|
|
case SDL_KEYUP:
|
|
if (SDL_GetModState() & KMOD_GUI)
|
|
{
|
|
break;
|
|
}
|
|
engine.onKeyRelease(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT);
|
|
break;
|
|
case SDL_TEXTINPUT:
|
|
if (SDL_GetModState() & KMOD_GUI)
|
|
{
|
|
break;
|
|
}
|
|
engine.onTextInput(ByteString(event.text.text).FromUtf8());
|
|
break;
|
|
case SDL_TEXTEDITING:
|
|
if (SDL_GetModState() & KMOD_GUI)
|
|
{
|
|
break;
|
|
}
|
|
engine.onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
|
|
break;
|
|
case SDL_MOUSEWHEEL:
|
|
{
|
|
// int x = event.wheel.x;
|
|
int y = event.wheel.y;
|
|
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
|
|
{
|
|
// x *= -1;
|
|
y *= -1;
|
|
}
|
|
|
|
engine.onMouseWheel(mousex, mousey, y); // TODO: pass x?
|
|
break;
|
|
}
|
|
case SDL_MOUSEMOTION:
|
|
mousex = event.motion.x;
|
|
mousey = event.motion.y;
|
|
engine.onMouseMove(mousex, mousey);
|
|
|
|
hasMouseMoved = true;
|
|
break;
|
|
case SDL_DROPFILE:
|
|
engine.onFileDrop(event.drop.file);
|
|
SDL_free(event.drop.file);
|
|
break;
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
// if mouse hasn't moved yet, sdl will send 0,0. We don't want that
|
|
if (hasMouseMoved)
|
|
{
|
|
mousex = event.button.x;
|
|
mousey = event.button.y;
|
|
}
|
|
mouseButton = event.button.button;
|
|
engine.onMouseClick(mousex, mousey, mouseButton);
|
|
|
|
mouseDown = true;
|
|
if constexpr (!DEBUG)
|
|
{
|
|
SDL_CaptureMouse(SDL_TRUE);
|
|
}
|
|
break;
|
|
case SDL_MOUSEBUTTONUP:
|
|
// if mouse hasn't moved yet, sdl will send 0,0. We don't want that
|
|
if (hasMouseMoved)
|
|
{
|
|
mousex = event.button.x;
|
|
mousey = event.button.y;
|
|
}
|
|
mouseButton = event.button.button;
|
|
engine.onMouseUnclick(mousex, mousey, mouseButton);
|
|
|
|
mouseDown = false;
|
|
if constexpr (!DEBUG)
|
|
{
|
|
SDL_CaptureMouse(SDL_FALSE);
|
|
}
|
|
break;
|
|
case SDL_WINDOWEVENT:
|
|
{
|
|
switch (event.window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_SHOWN:
|
|
if (!calculatedInitialMouse)
|
|
{
|
|
//initial mouse coords, sdl won't tell us this if mouse hasn't moved
|
|
CalculateMousePosition(&mousex, &mousey);
|
|
engine.initialMouse(mousex, mousey);
|
|
engine.onMouseMove(mousex, mousey);
|
|
calculatedInitialMouse = true;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EngineProcess()
|
|
{
|
|
auto &engine = ui::Engine::Ref();
|
|
auto correctedFrameTime = frameStart - oldFrameStart;
|
|
drawingTimer += correctedFrameTime;
|
|
correctedFrameTimeAvg = correctedFrameTimeAvg + (correctedFrameTime - correctedFrameTimeAvg) * 0.05;
|
|
if (correctedFrameTime && frameStart - lastFpsUpdate > UINT64_C(200'000'000))
|
|
{
|
|
engine.SetFps(1e9f / correctedFrameTimeAvg);
|
|
lastFpsUpdate = frameStart;
|
|
}
|
|
if (frameStart - lastTick > UINT64_C(100'000'000))
|
|
{
|
|
lastTick = frameStart;
|
|
TickClient();
|
|
}
|
|
if (showLargeScreenDialog)
|
|
{
|
|
showLargeScreenDialog = false;
|
|
LargeScreenDialog();
|
|
}
|
|
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event))
|
|
{
|
|
EventProcess(event);
|
|
}
|
|
|
|
engine.Tick();
|
|
|
|
auto fpsLimit = ui::Engine::Ref().GetFpsLimit();
|
|
int drawcap = ui::Engine::Ref().GetDrawingFrequencyLimit();
|
|
if (!drawcap || drawingTimer > 1e9f / drawcap)
|
|
{
|
|
engine.Draw();
|
|
drawingTimer = 0;
|
|
|
|
auto wantVsync = bool(std::get_if<FpsLimitVsync>(&fpsLimit));
|
|
if (scale != engine.Scale ||
|
|
fullscreen != engine.Fullscreen ||
|
|
altFullscreen != engine.GetAltFullscreen() ||
|
|
forceIntegerScaling != engine.GetForceIntegerScaling() ||
|
|
resizable != engine.GetResizable() ||
|
|
vsyncHint != wantVsync)
|
|
{
|
|
SDLSetScreen(engine.Scale, engine.GetResizable(), engine.Fullscreen, engine.GetAltFullscreen(), engine.GetForceIntegerScaling(), wantVsync);
|
|
}
|
|
|
|
blit(engine.g->Data());
|
|
}
|
|
auto now = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000);
|
|
oldFrameStart = frameStart;
|
|
frameStart = now;
|
|
if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&fpsLimit))
|
|
{
|
|
auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimitExplicit->value);
|
|
auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration;
|
|
auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U;
|
|
frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration);
|
|
SDL_Delay((frameStart - now) / UINT64_C(1'000'000));
|
|
}
|
|
}
|