scenes: infraestructura de la capa scenes:: (scene, timeline, sprite mover, frame animator, palette fade, surface handle, registry)
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
#include "game/modulegame.hpp"
|
||||
#include "game/modulesequence.hpp"
|
||||
#include "game/options.hpp"
|
||||
#include "scenes/scene.hpp"
|
||||
#include "scenes/scene_registry.hpp"
|
||||
|
||||
// Cheats del joc original — declarats a jinput.cpp
|
||||
extern void JI_moveCheats(Uint8 new_key);
|
||||
@@ -50,6 +52,26 @@ void gameFiberEntry() {
|
||||
|
||||
int gameState = 1;
|
||||
while (gameState != -1 && !JG_Quitting()) {
|
||||
// Mode "Scene nova": si el state actual és de seqüència i el
|
||||
// registry té una escena migrada per al num_piramide, l'executem
|
||||
// amb un mini-loop tick-based. El codi de la Scene no toca
|
||||
// fibers; el JD8_Flip() entre ticks és el que cedeix al Director.
|
||||
if (gameState == 1) {
|
||||
if (auto scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide)) {
|
||||
scene->onEnter();
|
||||
Uint32 last = SDL_GetTicks();
|
||||
while (!scene->done() && !JG_Quitting()) {
|
||||
const Uint32 now = SDL_GetTicks();
|
||||
scene->tick(static_cast<int>(now - last));
|
||||
last = now;
|
||||
JD8_Flip(); // presenta i cedix al Director
|
||||
}
|
||||
gameState = scene->nextState();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback al codi legacy (encara no migrat a Scene).
|
||||
switch (gameState) {
|
||||
case 0: {
|
||||
auto* moduleGame = new ModuleGame();
|
||||
@@ -95,198 +117,225 @@ void Director::togglePause() {
|
||||
}
|
||||
}
|
||||
|
||||
void Director::run() {
|
||||
// Doble buffer: game_frame és el frame net del joc, presentation_buffer
|
||||
// és el frame + overlay (es regenera cada iteració des de game_frame).
|
||||
// El doble buffer encara té sentit perquè el Director pot presentar
|
||||
// més frames que els que genera el joc (p.ex. durant pauses o mentre
|
||||
// el joc està en un "wait" intern que triga milisegons).
|
||||
Uint32 game_frame[320 * 200]{};
|
||||
Uint32 presentation_buffer[320 * 200]{};
|
||||
bool has_frame = false;
|
||||
void Director::setup() {
|
||||
// Els buffers són membres (director.hpp); només els inicialitzem.
|
||||
std::memset(game_frame_, 0, sizeof(game_frame_));
|
||||
std::memset(presentation_buffer_, 0, sizeof(presentation_buffer_));
|
||||
has_frame_ = false;
|
||||
}
|
||||
|
||||
bool Director::iterate() {
|
||||
if (GameFiber::is_done() || quit_requested_) {
|
||||
// Si el joc encara no ha acabat (p.ex. eixida per ESC doble-press),
|
||||
// li donem l'oportunitat de tornar net: marquem quit i reprenem el
|
||||
// fiber fins que detecte JG_Quitting() i retorne de forma natural.
|
||||
JG_QuitSignal();
|
||||
while (!GameFiber::is_done()) {
|
||||
GameFiber::resume();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
||||
|
||||
while (!GameFiber::is_done() && !quit_requested_) {
|
||||
Uint32 frame_start = SDL_GetTicks();
|
||||
const Uint32 frame_start = SDL_GetTicks();
|
||||
|
||||
handleEvents();
|
||||
Gamepad::update();
|
||||
KeyRemap::update();
|
||||
GlobalInputs::handle();
|
||||
Mouse::updateCursorVisibility();
|
||||
Gamepad::update();
|
||||
KeyRemap::update();
|
||||
GlobalInputs::handle();
|
||||
Mouse::updateCursorVisibility();
|
||||
|
||||
// Bombeig de l'àudio: reomple l'stream de música i para els canals
|
||||
// drenats. Substituïx el callback de SDL_AddTimer de la versió
|
||||
// antiga — imprescindible per al futur port a emscripten.
|
||||
JA_Update();
|
||||
// Bombeig de l'àudio: reomple l'stream de música i para els canals
|
||||
// drenats. Substituïx el callback de SDL_AddTimer de la versió
|
||||
// antiga — imprescindible per al port a emscripten.
|
||||
JA_Update();
|
||||
|
||||
// Dispara els crèdits cinematogràfics la primera vegada que el joc
|
||||
// arriba al menú del títol (info::ctx.num_piramide == 0).
|
||||
static bool credits_triggered = false;
|
||||
if (!credits_triggered && info::ctx.num_piramide == 0) {
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered = true;
|
||||
}
|
||||
|
||||
// Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
|
||||
if (esc_blocked_ && !Overlay::isEscConsumed()) {
|
||||
esc_blocked_ = false;
|
||||
}
|
||||
|
||||
// Cedeix el control al fiber del joc. Quan retorne (per un
|
||||
// JD8_Flip() dins del joc) tindrem un nou frame a pixel_data.
|
||||
// Si estem en pausa, no executem el fiber: es queda congelat al
|
||||
// seu últim yield i continuem presentant l'últim frame conegut.
|
||||
if (!paused_) {
|
||||
GameFiber::resume();
|
||||
if (GameFiber::is_done()) break;
|
||||
memcpy(game_frame, JD8_GetFramebuffer(), sizeof(game_frame));
|
||||
has_frame = true;
|
||||
}
|
||||
|
||||
// Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
|
||||
if (has_frame) {
|
||||
memcpy(presentation_buffer, game_frame, sizeof(presentation_buffer));
|
||||
Screen::get()->present(presentation_buffer);
|
||||
}
|
||||
|
||||
// Límit de framerate segons VSync
|
||||
Uint32 target_ms = Options::video.vsync ? FRAME_MS_VSYNC : FRAME_MS_NO_VSYNC;
|
||||
Uint32 elapsed = SDL_GetTicks() - frame_start;
|
||||
if (elapsed < target_ms) {
|
||||
SDL_Delay(target_ms - elapsed);
|
||||
// Dispara els crèdits cinematogràfics la primera vegada que el joc
|
||||
// arriba al menú del títol (info::ctx.num_piramide == 0).
|
||||
static bool credits_triggered = false;
|
||||
if (!credits_triggered && info::ctx.num_piramide == 0) {
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered = true;
|
||||
}
|
||||
|
||||
// Si el joc encara no ha acabat (p.ex. eixida per ESC doble-press),
|
||||
// li donem l'oportunitat de tornar net: marquem quit i reprenem el
|
||||
// fiber fins que detecte JG_Quitting() i retorne de forma natural.
|
||||
// Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
|
||||
if (esc_blocked_ && !Overlay::isEscConsumed()) {
|
||||
esc_blocked_ = false;
|
||||
}
|
||||
|
||||
// Cedeix el control al fiber del joc. Quan retorne (per un JD8_Flip()
|
||||
// dins del joc) tindrem un nou frame a pixel_data. Si estem en pausa,
|
||||
// no executem el fiber: es queda congelat al seu últim yield i
|
||||
// continuem presentant l'últim frame conegut.
|
||||
if (!paused_) {
|
||||
GameFiber::resume();
|
||||
if (GameFiber::is_done()) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(game_frame_, JD8_GetFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
}
|
||||
|
||||
// Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
|
||||
if (has_frame_) {
|
||||
std::memcpy(presentation_buffer_, game_frame_, sizeof(presentation_buffer_));
|
||||
Screen::get()->present(presentation_buffer_);
|
||||
}
|
||||
|
||||
// Límit de framerate segons VSync.
|
||||
// Nota: quan el runtime posseïx el main loop (SDL_AppIterate /
|
||||
// emscripten), aquest SDL_Delay no és ideal. Fase 7 afegirà un mode
|
||||
// que es basa en el timing intern de SDL en lloc del delay explícit.
|
||||
const Uint32 target_ms = Options::video.vsync ? FRAME_MS_VSYNC : FRAME_MS_NO_VSYNC;
|
||||
const Uint32 elapsed = SDL_GetTicks() - frame_start;
|
||||
if (elapsed < target_ms) {
|
||||
SDL_Delay(target_ms - elapsed);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Director::teardown() {
|
||||
// Si el joc encara no ha acabat (p.ex. eixida per SDL_QUIT des del
|
||||
// sistema), li donem l'oportunitat de tornar net.
|
||||
JG_QuitSignal();
|
||||
while (!GameFiber::is_done()) {
|
||||
GameFiber::resume();
|
||||
}
|
||||
}
|
||||
|
||||
void Director::handleEvents() {
|
||||
void Director::run() {
|
||||
setup();
|
||||
while (true) {
|
||||
pollAllEvents();
|
||||
if (!iterate()) break;
|
||||
}
|
||||
teardown();
|
||||
}
|
||||
|
||||
void Director::pollAllEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
JG_QuitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
// Hot-plug de gamepad
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
continue;
|
||||
}
|
||||
// Salta els crèdits amb qualsevol tecla; no deixem que arribi al joc
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && Overlay::creditsActive()) {
|
||||
Overlay::cancelCredits();
|
||||
continue;
|
||||
}
|
||||
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 &&
|
||||
event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) {
|
||||
menu_keys_held_[event.key.scancode] = false;
|
||||
continue;
|
||||
}
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
|
||||
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
Menu::captureKey(event.key.scancode);
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
continue;
|
||||
}
|
||||
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == Options::keys_gui.pause_toggle) {
|
||||
togglePause();
|
||||
Overlay::showNotification(paused_ ? Locale::get("notifications.pause") : Locale::get("notifications.resume"));
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
continue;
|
||||
}
|
||||
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == Options::keys_gui.menu_toggle) {
|
||||
Menu::toggle();
|
||||
JI_SetInputBlocked(Menu::isOpen());
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
continue;
|
||||
}
|
||||
// Si el menú està obert, consumeix tot l'input de teclat
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
Menu::close();
|
||||
JI_SetInputBlocked(false);
|
||||
// Empassa l'ESC fins al release perquè el joc no la veja per polling
|
||||
esc_swallow_until_release_ = true;
|
||||
} else {
|
||||
Menu::handleKey(event.key.scancode);
|
||||
// El menú pot haver-se tancat (p.ex. Backspace al nivell arrel)
|
||||
if (!Menu::isOpen()) {
|
||||
JI_SetInputBlocked(false);
|
||||
}
|
||||
}
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
continue;
|
||||
}
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
||||
continue; // no deixem passar KEY_UP al joc tampoc
|
||||
}
|
||||
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) {
|
||||
esc_swallow_until_release_ = false;
|
||||
continue;
|
||||
}
|
||||
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
|
||||
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
|
||||
if (!Overlay::isEscConsumed()) {
|
||||
// Primera pulsació: mostra notificació
|
||||
Overlay::handleEscape();
|
||||
} else {
|
||||
// Segona pulsació: senyal d'eixida al joc
|
||||
esc_blocked_ = false;
|
||||
key_pressed_ = true;
|
||||
JG_QuitSignal();
|
||||
// Si estem en pausa, la desactivem: el fiber del joc està
|
||||
// congelat i necessita ser reprès per veure la senyal de
|
||||
// quit i poder tornar de forma natural.
|
||||
paused_ = false;
|
||||
}
|
||||
continue; // no processa més aquest event
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
// Ja processat a KEY_DOWN, només deixem netejar el bloqueig
|
||||
// quan l'overlay faça timeout
|
||||
continue;
|
||||
} else {
|
||||
// Comprova si és una tecla GUI (no passa al joc)
|
||||
const auto sc = event.key.scancode;
|
||||
const bool is_gui_key = (sc == Options::keys_gui.dec_zoom ||
|
||||
sc == Options::keys_gui.inc_zoom ||
|
||||
sc == Options::keys_gui.fullscreen ||
|
||||
sc == Options::keys_gui.toggle_shader ||
|
||||
sc == Options::keys_gui.toggle_aspect_ratio ||
|
||||
sc == Options::keys_gui.toggle_supersampling ||
|
||||
sc == Options::keys_gui.next_shader ||
|
||||
sc == Options::keys_gui.next_shader_preset ||
|
||||
sc == Options::keys_gui.toggle_stretch_filter ||
|
||||
sc == Options::keys_gui.toggle_render_info);
|
||||
if (!is_gui_key) {
|
||||
key_pressed_ = true;
|
||||
JI_moveCheats(sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Mouse::handleEvent(event);
|
||||
handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void Director::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
JG_QuitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
// Hot-plug de gamepad
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
return;
|
||||
}
|
||||
// Salta els crèdits amb qualsevol tecla; no deixem que arribi al joc
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && Overlay::creditsActive()) {
|
||||
Overlay::cancelCredits();
|
||||
return;
|
||||
}
|
||||
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 &&
|
||||
event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) {
|
||||
menu_keys_held_[event.key.scancode] = false;
|
||||
return;
|
||||
}
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
|
||||
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
Menu::captureKey(event.key.scancode);
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
}
|
||||
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == Options::keys_gui.pause_toggle) {
|
||||
togglePause();
|
||||
Overlay::showNotification(paused_ ? Locale::get("notifications.pause") : Locale::get("notifications.resume"));
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
}
|
||||
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == Options::keys_gui.menu_toggle) {
|
||||
Menu::toggle();
|
||||
JI_SetInputBlocked(Menu::isOpen());
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
}
|
||||
// Si el menú està obert, consumeix tot l'input de teclat
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
Menu::close();
|
||||
JI_SetInputBlocked(false);
|
||||
// Empassa l'ESC fins al release perquè el joc no la veja per polling
|
||||
esc_swallow_until_release_ = true;
|
||||
} else {
|
||||
Menu::handleKey(event.key.scancode);
|
||||
// El menú pot haver-se tancat (p.ex. Backspace al nivell arrel)
|
||||
if (!Menu::isOpen()) {
|
||||
JI_SetInputBlocked(false);
|
||||
}
|
||||
}
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
}
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
||||
return; // no deixem passar KEY_UP al joc tampoc
|
||||
}
|
||||
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) {
|
||||
esc_swallow_until_release_ = false;
|
||||
return;
|
||||
}
|
||||
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
|
||||
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
|
||||
if (!Overlay::isEscConsumed()) {
|
||||
// Primera pulsació: mostra notificació
|
||||
Overlay::handleEscape();
|
||||
} else {
|
||||
// Segona pulsació: senyal d'eixida al joc
|
||||
esc_blocked_ = false;
|
||||
key_pressed_ = true;
|
||||
JG_QuitSignal();
|
||||
// Si estem en pausa, la desactivem: el fiber del joc està
|
||||
// congelat i necessita ser reprès per veure la senyal de
|
||||
// quit i poder tornar de forma natural.
|
||||
paused_ = false;
|
||||
}
|
||||
return; // no processa més aquest event
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
// Ja processat a KEY_DOWN, només deixem netejar el bloqueig
|
||||
// quan l'overlay faça timeout
|
||||
return;
|
||||
} else {
|
||||
// Comprova si és una tecla GUI (no passa al joc)
|
||||
const auto sc = event.key.scancode;
|
||||
const bool is_gui_key = (sc == Options::keys_gui.dec_zoom ||
|
||||
sc == Options::keys_gui.inc_zoom ||
|
||||
sc == Options::keys_gui.fullscreen ||
|
||||
sc == Options::keys_gui.toggle_shader ||
|
||||
sc == Options::keys_gui.toggle_aspect_ratio ||
|
||||
sc == Options::keys_gui.toggle_supersampling ||
|
||||
sc == Options::keys_gui.next_shader ||
|
||||
sc == Options::keys_gui.next_shader_preset ||
|
||||
sc == Options::keys_gui.toggle_stretch_filter ||
|
||||
sc == Options::keys_gui.toggle_render_info);
|
||||
if (!is_gui_key) {
|
||||
key_pressed_ = true;
|
||||
JI_moveCheats(sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Mouse::handleEvent(event);
|
||||
}
|
||||
|
||||
void Director::requestQuit() {
|
||||
quit_requested_ = true;
|
||||
JG_QuitSignal();
|
||||
|
||||
@@ -17,11 +17,22 @@ class Director {
|
||||
static void destroy();
|
||||
static auto get() -> Director*;
|
||||
|
||||
// Bucle principal del director. Crida des de main().
|
||||
// Bucle principal clàssic (build natiu sense SDL_MAIN_USE_CALLBACKS).
|
||||
// Internament crida setup() + bucle d'iterate() + teardown(). Crida des de main().
|
||||
void run();
|
||||
|
||||
// Punts d'entrada compatibles amb SDL_AppInit / SDL_AppIterate /
|
||||
// SDL_AppEvent / SDL_AppQuit. Permeten que el Director siga driven
|
||||
// per l'event loop de SDL3 en lloc d'un bucle propi — imprescindible
|
||||
// per al port a emscripten, on el runtime posseïx el main loop.
|
||||
void setup();
|
||||
bool iterate(); // torna false quan el joc vol eixir
|
||||
void teardown();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
|
||||
// Demana l'eixida (ex: segona pulsació d'ESC o SDL_QUIT)
|
||||
void requestQuit();
|
||||
auto isQuitRequested() const -> bool { return quit_requested_; }
|
||||
|
||||
// Consumeix el flag de "tecla polsada" (com l'antic JI_AnyKey)
|
||||
auto consumeKeyPressed() -> bool;
|
||||
@@ -40,7 +51,14 @@ class Director {
|
||||
|
||||
static Director* instance_;
|
||||
|
||||
void handleEvents();
|
||||
void pollAllEvents(); // drenatge amb SDL_PollEvent, només per al bucle natiu
|
||||
|
||||
// Buffers persistents entre iteracions. Abans eren locals a run(),
|
||||
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
||||
// a reservar en cada crida del callback.
|
||||
Uint32 game_frame_[320 * 200]{};
|
||||
Uint32 presentation_buffer_[320 * 200]{};
|
||||
bool has_frame_{false};
|
||||
|
||||
std::atomic<bool> quit_requested_{false};
|
||||
std::atomic<bool> key_pressed_{false};
|
||||
|
||||
Reference in New Issue
Block a user