diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 269032db1..d33c39286 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -33,22 +33,60 @@ Screen* Screen::screen = nullptr; #ifdef __EMSCRIPTEN__ -// Callbacks d'Emscripten per detectar canvis de mida que SDL3 no reporta -// de manera fiable al wasm: sortida de fullscreen (Esc/F11), rotació del -// dispositiu i resize de la finestra del navegador. Tots reenvien al mètode -// Screen::handleCanvasResized() que re-sincronitza la finestra SDL amb la -// mida real del canvas. Veure issues libsdl-org/SDL #13300 i #11389. +// ============================================================================ +// Restauració del canvas en wasm/Emscripten +// ============================================================================ +// +// Problema: SDL3 + Emscripten no notifica de manera fiable els canvis de mida +// del canvas HTML. En concret: +// - SDL_EVENT_WINDOW_LEAVE_FULLSCREEN no s'emet quan l'usuari surt de +// fullscreen amb Esc/F11 (libsdl-org/SDL#13300). +// - SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED i SDL_EVENT_DISPLAY_ORIENTATION +// tampoc es disparen de manera fiable (libsdl-org/SDL#11389). +// - Resultat: en sortir de fullscreen el canvas queda a la mida correcta +// però SDL encara creu que està en fullscreen amb la resolució anterior, +// i el joc es veu minúscul fins que l'usuari força un refresh manual +// (p. ex., canviant el mode d'escalat amb F7). +// +// Solució: registrem callbacks natius d'Emscripten (fullscreenchange, resize, +// orientationchange) que re-sincronitzen SDL amb l'estat real del navegador. +// Tots delegen en Screen::handleCanvasResized(), que crida setVideoMode() +// amb l'estat de fullscreen actualitzat — això és el que realment restaura +// la finestra SDL perquè dins setVideoMode es crida SDL_SetWindowFullscreen, +// que és imprescindible per treure SDL del seu estat intern de fullscreen. +// +// Els callbacks diferixen la feina amb emscripten_async_call(0ms) perquè +// quan l'event es dispara el navegador encara no ha acabat de redimensionar +// el canvas: llegir la mida en aquest instant donaria un valor obsolet. +// Posposar al següent tick del event loop garanteix que el canvas ja està +// estable quan actuem. +// +// Referències: +// - https://github.com/libsdl-org/SDL/issues/13300 +// - https://github.com/libsdl-org/SDL/issues/11389 +// - https://discourse.libsdl.org/t/sdl-emscripten-allow-resize-events-on-fullscreen-windows/66279 +// ============================================================================ namespace { - auto onEmFullscreenChange(int /*event_type*/, const EmscriptenFullscreenChangeEvent* /*event*/, void* /*user_data*/) -> EM_BOOL { + 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 { + // Actualitzem Options::video.fullscreen amb l'estat real del navegador + // abans de diferir la restauració: quan l'usuari surt amb Esc no passem + // per setVideoMode() i l'estat intern quedaria desincronitzat. + Options::video.fullscreen = (event != nullptr && event->isFullscreen != 0); + emscripten_async_call(deferredCanvasResize, nullptr, 0); return EM_FALSE; } + auto onEmResize(int /*event_type*/, const EmscriptenUiEvent* /*event*/, void* /*user_data*/) -> EM_BOOL { - if (Screen::get() != nullptr) { Screen::get()->handleCanvasResized(); } + emscripten_async_call(deferredCanvasResize, nullptr, 0); return EM_FALSE; } + auto onEmOrientationChange(int /*event_type*/, const EmscriptenOrientationChangeEvent* /*event*/, void* /*user_data*/) -> EM_BOOL { - if (Screen::get() != nullptr) { Screen::get()->handleCanvasResized(); } + emscripten_async_call(deferredCanvasResize, nullptr, 0); return EM_FALSE; } } // namespace @@ -194,23 +232,18 @@ void Screen::toggleVideoMode() { setVideoMode(Options::video.fullscreen); } -// Re-sincronitza la finestra SDL amb la mida real del canvas HTML. Només cal en -// emscripten: quan l'usuari surt de fullscreen amb Esc, rota el dispositiu o -// canvia la mida del canvas, SDL3 no emet SDL_EVENT_WINDOW_LEAVE_FULLSCREEN ni -// SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED de manera fiable (issues libsdl-org/SDL -// #13300 i #11389), així que els callbacks d'Emscripten criden aquest mètode -// directament. Llegim la mida real del canvas per CSS i l'apliquem a la finestra -// SDL perquè el logical presentation s'escali al nou viewport. +// Re-sincronitza SDL amb l'estat real del canvas del navegador. L'invoquen els +// callbacks natius d'Emscripten definits a dalt (vegeu el bloc de documentació +// just després dels includes) quan es detecta un fullscreenchange, resize o +// orientationchange. Delegar a setVideoMode() és el que realment restaura la +// finestra: per sota crida SDL_SetWindowFullscreen, imprescindible perquè +// SDL tregui el seu estat intern de fullscreen quan l'usuari ha sortit amb +// Esc sense passar pel nostre toggleVideoMode(). No fem res fora d'emscripten +// perquè en desktop SDL ja emet SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED correctament. void Screen::handleCanvasResized() { #ifdef __EMSCRIPTEN__ - double css_w = 0.0; - double css_h = 0.0; - if (emscripten_get_element_css_size("#canvas", &css_w, &css_h) == EMSCRIPTEN_RESULT_SUCCESS && css_w > 0.0 && css_h > 0.0) { - SDL_SetWindowSize(window_, static_cast(css_w), static_cast(css_h)); - } + setVideoMode(Options::video.fullscreen); #endif - adjustRenderLogicalSize(); - updateZoomFactor(); } // Reduce el tamaño de la ventana @@ -788,17 +821,21 @@ auto Screen::initSDLVideo() -> bool { SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND); SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED); + registerEmscriptenEventCallbacks(); + + std::cout << "Video system initialized successfully\n"; + return true; +} + +// Registra els callbacks natius d'Emscripten que restauren el canvas quan +// SDL3 no emet els events equivalents. Fora d'Emscripten és un no-op. +// Vegeu el bloc de documentació a dalt del fitxer per al context complet. +void Screen::registerEmscriptenEventCallbacks() { // NOLINT(readability-convert-member-functions-to-static) #ifdef __EMSCRIPTEN__ - // Registrem callbacks d'Emscripten per reaccionar a canvis de mida del - // canvas que SDL3 no notifica: sortida de fullscreen amb Esc, rotació - // del dispositiu i resize de la finestra del navegador. emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange); emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, onEmResize); emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange); #endif - - std::cout << "Video system initialized successfully\n"; - return true; } // Crea el objeto de texto diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 61a474f38..5e71b48cf 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -38,7 +38,7 @@ class Screen { // Video y ventana void setVideoMode(bool mode); // Establece el modo de video void toggleVideoMode(); // Cambia entre pantalla completa y ventana - void handleCanvasResized(); // Re-sincronitza SDL amb la mida real del canvas (emscripten: sortida de fullscreen, rotació, resize) + void handleCanvasResized(); // Restaura el canvas quan SDL3 no reporta el canvi (emscripten only: sortida de fullscreen amb Esc, rotació, resize); no-op fora d'emscripten void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero void toggleVSync(); // Alterna entre activar y desactivar el V-Sync auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana @@ -126,18 +126,19 @@ class Screen { static Screen* screen; // Métodos privados - void renderNotifications() const; // Dibuja las notificaciones - void adjustWindowSize(); // Calcula el tamaño de la ventana - void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador - void surfaceToTexture(); // Copia la surface a la textura - void textureToRenderer(); // Copia la textura al renderizador - void renderOverlays(); // Renderiza todos los overlays - void initShaders(); // Inicializa los shaders - void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend - void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend - void getDisplayInfo(); // Obtiene información sobre la pantalla - auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana - void createText(); // Crea el objeto de texto + void renderNotifications() const; // Dibuja las notificaciones + void adjustWindowSize(); // Calcula el tamaño de la ventana + void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador + void surfaceToTexture(); // Copia la surface a la textura + void textureToRenderer(); // Copia la textura al renderizador + void renderOverlays(); // Renderiza todos los overlays + void initShaders(); // Inicializa los shaders + void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend + void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend + void getDisplayInfo(); // Obtiene información sobre la pantalla + auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana + void registerEmscriptenEventCallbacks(); // Registra els callbacks natius per restaurar el canvas en wasm (no-op fora d'emscripten) + void createText(); // Crea el objeto de texto // Constructor y destructor Screen();