diff --git a/src/azimuth/control/util.c b/src/azimuth/control/util.c index f0c2650..c09b59a 100644 --- a/src/azimuth/control/util.c +++ b/src/azimuth/control/util.c @@ -25,7 +25,7 @@ #include #include -#if !defined(__WINDOWS__) && !defined(__APPLE__) +#if !defined(__WINDOWS__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__) #include #include #include @@ -48,7 +48,10 @@ // If anything fails, this will return a NULL pointer. // Free the returned string with SDL_free(). static char *az_get_app_data_directory(void) { -#if !defined(__WINDOWS__) && !defined(__APPLE__) +#if defined(__EMSCRIPTEN__) + // The fredygames.com page mounts this folder from IndexedDB before the game starts. + return SDL_strdup("/home/web_user/azimuth"); +#elif !defined(__WINDOWS__) && !defined(__APPLE__) // First, do tilde-expansion so we get a path in the user's homedir. wordexp_t words; wordexp("~/.azimuth-game", &words, 0); diff --git a/src/azimuth/gui/screen.c b/src/azimuth/gui/screen.c index ba8fda0..f2813e5 100644 --- a/src/azimuth/gui/screen.c +++ b/src/azimuth/gui/screen.c @@ -30,6 +30,22 @@ #include "azimuth/util/misc.h" #include "azimuth/util/warning.h" +#ifdef __EMSCRIPTEN__ +#include + +// Size of the canvas in pixels: the 640x480 screen scaled to fit the screen of the device (the page +// shrinks or stretches it to the player with CSS), between 1x and 3x. +EM_JS(double, az_web_canvas_scale, (void), { + const s = Math.min(screen.width / 640, screen.height / 480) * (window.devicePixelRatio || 1); + return Math.max(1, Math.min(3, s)); +}); + +// Hands control back to the browser until it paints the next frame. +EM_ASYNC_JS(void, az_web_wait_frame, (void), { + await new Promise((resolve) => requestAnimationFrame(resolve)); +}); +#endif + /*===========================================================================*/ #ifdef __APPLE__ @@ -96,11 +112,21 @@ void az_init_gui(bool fullscreen, bool enable_audio) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2); SDL_GetDesktopDisplayMode(0, &display_mode); +#ifdef __EMSCRIPTEN__ + // In the browser the canvas has a fixed size and the page handles full screen. + (void)display_mode; + fullscreen = false; + const double scale = az_web_canvas_scale(); + window = SDL_CreateWindow( + "Azimuth", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + (int)(AZ_SCREEN_WIDTH * scale), (int)(AZ_SCREEN_HEIGHT * scale), SDL_WINDOW_OPENGL); +#else window = SDL_CreateWindow( "Azimuth", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0), display_mode.w, display_mode.h, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | (fullscreen ? SDL_WINDOW_FULLSCREEN : 0)); +#endif if (NULL == window) { AZ_FATAL("SDL_CreateWindow failed: %s\n", SDL_GetError()); } @@ -141,6 +167,11 @@ void az_set_fullscreen(bool fullscreen) { int display_index = 0; SDL_DisplayMode display_mode = {0}; assert(sdl_initialized); +#ifdef __EMSCRIPTEN__ + // Ctrl-F and the preference do nothing here; the page has its own full-screen button. + if (display_initialized) return; + fullscreen = false; +#endif if (display_initialized && fullscreen == currently_fullscreen) return; currently_fullscreen = fullscreen; az_pause_all_audio(); @@ -151,6 +182,7 @@ void az_set_fullscreen(bool fullscreen) { SDL_GetMouseState(&x, &y); } +#ifndef __EMSCRIPTEN__ display_index = SDL_GetWindowDisplayIndex(window); SDL_GetDesktopDisplayMode(display_index, &display_mode); SDL_SetWindowDisplayMode(window, &display_mode); @@ -166,6 +198,9 @@ void az_set_fullscreen(bool fullscreen) { if (display_initialized) { SDL_WarpMouseInWindow(window, x, y); } +#else + (void)display_index; (void)display_mode; (void)x; (void)y; +#endif // Turn off the depth buffer: glDisable(GL_DEPTH_TEST); @@ -234,9 +269,21 @@ void az_finish_screen_redraw(void) { assert(sdl_initialized); assert(display_initialized); SDL_GL_SwapWindow(window); +#ifdef __EMSCRIPTEN__ + // The browser shows the frame once the game yields. Wait for animation frames until the next + // 60 Hz tick is near, so the game keeps its speed on 120 Hz and faster displays too. + static uint64_t sync_time = 0; + uint64_t now; + do { + az_web_wait_frame(); + now = az_current_time_nanos(); + } while (now + 4000000u < sync_time); + sync_time = (now > sync_time + 4 * AZ_FRAME_TIME_NANOS ? now : sync_time) + AZ_FRAME_TIME_NANOS; +#else // Synchronize, in case vsync fails to lock us to 60Hz: static uint64_t sync_time = 0; sync_time = az_sleep_until(sync_time) + AZ_FRAME_TIME_NANOS; +#endif } void az_gl_scissor(int x, int y, int width, int height) { diff --git a/src/azimuth/system/resource.c b/src/azimuth/system/resource.c index f782b02..1ca069c 100644 --- a/src/azimuth/system/resource.c +++ b/src/azimuth/system/resource.c @@ -42,8 +42,8 @@ extern const struct resource_entry { extern const size_t resource_index_size; extern const char _binary_resources_start[]; -#if defined(__APPLE__) -// Use app-bundle resources on macOS +#if defined(__APPLE__) || defined(__EMSCRIPTEN__) +// Use app-bundle resources on macOS (and the preloaded files in the browser) bool az_system_resource_reader(const char *name, az_reader_t *reader) { char *resource_dir = SDL_GetBasePath(); if (resource_dir == NULL) return false;