diff --git a/CMakeLists.txt b/CMakeLists.txt index 8090bdc..3eda6f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ if(EMSCRIPTEN) FetchContent_Declare( SDL3 GIT_REPOSITORY https://github.com/libsdl-org/SDL.git - GIT_TAG release-3.2.12 + GIT_TAG release-3.4.4 GIT_SHALLOW TRUE ) set(SDL_SHARED OFF CACHE BOOL "" FORCE) diff --git a/Makefile b/Makefile index d4d76f7..df7899e 100644 --- a/Makefile +++ b/Makefile @@ -424,6 +424,7 @@ raspi_release: wasm: @echo "Compilando para WebAssembly - Version: $(VERSION) ($(GIT_HASH))" docker run --rm \ + --user $(shell id -u):$(shell id -g) \ -v $(DIR_ROOT):/src \ -w /src \ emscripten/emsdk:latest \ diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 711da15..bc87805 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -1,6 +1,10 @@ #include "core/rendering/screen.hpp" -#include // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags +#include // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_SyncWindow, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags +#ifdef __EMSCRIPTEN__ +#include +#include +#endif #include // Para min, max #include // Para memcpy @@ -27,6 +31,43 @@ // Singleton Screen* Screen::instance = nullptr; +#ifdef __EMSCRIPTEN__ +// ============================================================================ +// Restauración del canvas en wasm/Emscripten +// ============================================================================ +// SDL3 + Emscripten no notifica de forma fiable los cambios de estado del +// canvas HTML (fullscreen exit vía Esc, rotación del dispositivo, etc.). +// Registramos callbacks nativos de Emscripten que delegan en +// Screen::handleCanvasResized(), el cual re-aplica el modo de fullscreen y +// reajusta la ventana para que SDL salga de su estado interno de fullscreen. +// +// Los callbacks difieren el trabajo con emscripten_async_call(0ms) porque el +// navegador todavía no ha acabado de redimensionar el canvas cuando el evento +// se dispara; posponer al siguiente tick garantiza valores estables. +// +// Referencias: libsdl-org/SDL#13300, libsdl-org/SDL#11389. +// ============================================================================ +namespace { + void deferredCanvasResize(void* /*user_data*/) { + if (Screen::get() != nullptr) { Screen::get()->handleCanvasResized(); } + } + + auto onEmFullscreenChange(int /*event_type*/, const EmscriptenFullscreenChangeEvent* event, void* /*user_data*/) -> EM_BOOL { + // Sincronizamos Options::video.fullscreen con el estado real del navegador + // antes de diferir la restauración: cuando el usuario sale con Esc no pasa + // por toggleFullscreen() y el estado interno quedaría desincronizado. + Options::video.fullscreen = (event != nullptr && event->isFullscreen != 0); + emscripten_async_call(deferredCanvasResize, nullptr, 0); + return EM_FALSE; + } + + auto onEmOrientationChange(int /*event_type*/, const EmscriptenOrientationChangeEvent* /*event*/, void* /*user_data*/) -> EM_BOOL { + emscripten_async_call(deferredCanvasResize, nullptr, 0); + return EM_FALSE; + } +} // namespace +#endif + // Inicializa la instancia única del singleton void Screen::init() { Screen::instance = new Screen(); @@ -69,6 +110,14 @@ Screen::Screen() // Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra) SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr); + // Aplicar la configuración inicial completa (vsync + logical presentation + + // fullscreen + tamaño de ventana). En Emscripten es necesario porque el + // canvas HTML tiene un tamaño propio y SDL_CreateWindow solo no basta para + // que SDL sincronice su viewport interno con el canvas real: sin este + // applySettings el canvas inicial sale descolocado con barras negras a los + // lados y el juego pequeño en el centro hasta el primer toggle de fullscreen. + applySettings(); + // Limpiar renderer SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); SDL_RenderClear(renderer_); @@ -145,10 +194,39 @@ void Screen::setFullscreenMode() { SDL_SetWindowFullscreen(window_, Options::video.fullscreen); } -// Camibia entre pantalla completa y ventana +// Cambia entre pantalla completa y ventana. Usamos applySettings en vez de +// setFullscreenMode porque applySettings también re-aplica la logical +// presentation — sin eso, al entrar en fullscreen SDL no recalcula el viewport +// y el juego se ve pequeño (especialmente en Android). void Screen::toggleFullscreen() { Options::video.fullscreen = !Options::video.fullscreen; - setFullscreenMode(); + applySettings(); +} + +// Re-sincroniza SDL con el estado real del canvas del navegador. Lo invocan los +// callbacks nativos de Emscripten (vegeu el bloc al principi del fitxer) cuando +// se detecta un fullscreenchange o un orientationchange. Re-aplica fullscreen y +// reajusta la ventana porque SDL no emite SDL_EVENT_WINDOW_LEAVE_FULLSCREEN en +// Emscripten y su estado interno queda desincronizado al salir con Esc. +// Fuera de Emscripten es un no-op (desktop sí emite los events correctamente). +void Screen::handleCanvasResized() { +#ifdef __EMSCRIPTEN__ + // SDL_SetWindowFullscreen es imprescindible para sacar a SDL de su estado + // interno de fullscreen cuando el usuario ha salido sin pasar por + // toggleFullscreen (onEmFullscreenChange ya ha actualizado Options). + SDL_SetWindowFullscreen(window_, Options::video.fullscreen); + SDL_SyncWindow(window_); + adjustWindowSize(); +#endif +} + +// Registra los callbacks nativos de Emscripten que restauran el canvas cuando +// SDL3 no emite los events equivalentes. Fuera de Emscripten es un no-op. +void Screen::registerEmscriptenEventCallbacks() { // NOLINT(readability-convert-member-functions-to-static) +#ifdef __EMSCRIPTEN__ + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange); + emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange); +#endif } // Cambia el tamaño de la ventana @@ -424,6 +502,8 @@ auto Screen::initSDLVideo() -> bool { SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND); SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED); + registerEmscriptenEventCallbacks(); + return true; } @@ -611,12 +691,17 @@ void Screen::getSingletons() { #endif } -// Aplica los valores de las opciones +// Aplica los valores de las opciones. +// IMPORTANTE: el orden importa. SDL_SetRenderLogicalPresentation calcula el +// viewport en función del tamaño actual de la ventana SDL, así que DEBE llamarse +// DESPUÉS de setFullscreenMode/adjustWindowSize — si no, al entrar en fullscreen +// el viewport queda cacheado al tamaño de la ventana pequeña previa y el juego +// se ve pequeño y centrado con barras negras alrededor. void Screen::applySettings() { SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED); - SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); setFullscreenMode(); adjustWindowSize(); + SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); } // Crea el objeto de texto diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 20ffad3..bd18126 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -34,6 +34,7 @@ class Screen { // --- Configuración de ventana y render --- void setFullscreenMode(); // Establece el modo de pantalla completa void toggleFullscreen(); // Cambia entre pantalla completa y ventana + void handleCanvasResized(); // Restaura el canvas cuando SDL3 no reporta el cambio (emscripten only: salida de fullscreen con Esc, rotación); no-op fuera de emscripten void setWindowZoom(int zoom); // Cambia el tamaño de la ventana auto decWindowSize() -> bool; // Reduce el tamaño de la ventana auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana @@ -232,7 +233,8 @@ class Screen { #endif // --- Métodos internos --- - auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana + auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana + void registerEmscriptenEventCallbacks(); // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten) void renderFlash(); // Dibuja el efecto de flash en la pantalla void renderShake(); // Aplica el efecto de agitar la pantalla void renderInfo() const; // Muestra información por pantalla