diff --git a/ChangeLog.md b/ChangeLog.md index 1ae431a20e5df..adcb0459c703a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -23,6 +23,8 @@ See docs/process.md for more on how version tagging works. - The `MEMORY64` setting is no longer experimental. At time of writing all browsers still require a flag to run the resulting binaries but that should change in the coming months since the proposal is now at stage 4. (#22864) +- GLFW: Fixed regression introduced in 3.1.51. CSS scaling is now available + again. Note that CSS scaling is disabled in HiDPI mode. (#22847, #22900) 3.1.71 - 11/04/24 ----------------- diff --git a/src/library_glfw.js b/src/library_glfw.js index 7a07e3633e98d..142f79e85aa29 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -1110,9 +1110,9 @@ var LibraryGLFW = { // If context creation failed, do not return a valid window if (!Module.ctx && useWebGL) return 0; - // Get non alive id + // Initializes the framebuffer size from the canvas const canvas = Module['canvas']; - var win = new GLFW_Window(id, canvas.clientWidth, canvas.clientHeight, canvas.width, canvas.height, title, monitor, share); + var win = new GLFW_Window(id, width, height, canvas.width, canvas.height, title, monitor, share); // Set window to array if (id - 1 == GLFW.windows.length) { @@ -1248,7 +1248,7 @@ var LibraryGLFW = { if (canvas.width != wNativeScaled) canvas.width = wNativeScaled; if (canvas.height != hNativeScaled) canvas.height = hNativeScaled; if (typeof canvas.style != 'undefined') { - if (wNativeScaled != wNative || hNativeScaled != hNative) { + if (!GLFW.isCSSScalingEnabled()) { canvas.style.setProperty( "width", wNative + "px", "important"); canvas.style.setProperty("height", hNative + "px", "important"); } else { @@ -1258,13 +1258,11 @@ var LibraryGLFW = { } }, - // Overrides Browser.calculateMouseCoords to account for hi dpi scaling + // Overrides Browser.calculateMouseCoords to account for HiDPI scaling and CSS scaling calculateMouseCoords(pageX, pageY) { // Calculate the movement based on the changes // in the coordinates. - var rect = Module["canvas"].getBoundingClientRect(); - var cw = Module["canvas"].clientWidth; - var ch = Module["canvas"].clientHeight; + const rect = Module["canvas"].getBoundingClientRect(); // Neither .scrollX or .pageXOffset are defined in a spec, but // we prefer .scrollX because it is currently in a spec draft. @@ -1279,11 +1277,14 @@ var LibraryGLFW = { var adjustedX = pageX - (scrollX + rect.left); var adjustedY = pageY - (scrollY + rect.top); - // the canvas might be CSS-scaled compared to its backbuffer; - // SDL-using content will want mouse coordinates in terms - // of backbuffer units. - adjustedX = adjustedX * (cw / rect.width); - adjustedY = adjustedY * (ch / rect.height); + // getBoundingClientRect() returns dimension affected by CSS, so as a result: + // - when CSS scaling is enabled, this will fix the mouse coordinates to match the width/height of the window + // - otherwise the CSS width/height are forced to the width/height of the GLFW window (see updateCanvasDimensions), + // so there is no need to adjust the position + if (GLFW.isCSSScalingEnabled() && GLFW.active) { + adjustedX = adjustedX * (GLFW.active.width / rect.width); + adjustedY = adjustedY * (GLFW.active.height / rect.height); + } return { x: adjustedX, y: adjustedY }; }, @@ -1308,10 +1309,19 @@ var LibraryGLFW = { return false; }, + /** + * CSS Scaling is a feature that is NOT part of the GLFW API, but for historical reasons, it is available + * in Emscripten. + * It is automatically disabled when using Hi DPI (the library overrides CSS sizes). */ + isCSSScalingEnabled() { + return !GLFW.isHiDPIAware(); + }, + adjustCanvasDimensions() { - const canvas = Module['canvas']; - Browser.updateCanvasDimensions(canvas, canvas.clientWidth, canvas.clientHeight); - Browser.updateResizeListeners(); + if (GLFW.active) { + Browser.updateCanvasDimensions(Module['canvas'], GLFW.active.width, GLFW.active.height); + Browser.updateResizeListeners(); + } }, getHiDPIScale() { diff --git a/test/browser/test_glfw3_css_scaling.c b/test/browser/test_glfw3_css_scaling.c new file mode 100644 index 0000000000000..f0c232d82be00 --- /dev/null +++ b/test/browser/test_glfw3_css_scaling.c @@ -0,0 +1,120 @@ +/* + * Copyright 2024 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include + +/* + * History: + * - This test was added after the issue #22847 was reported (November 2024). + * - For historical reasons, the JavaScript GLFW implementation includes a feature: the ability to scale the canvas via CSS. + * - Note that this feature is not part of GLFW as GLFW does not offer any API to scale the window. + * - Being undocumented/untested, this feature was accidentally removed when HiDPI support was added (in 3.1.51 / December 2023). + * - This test was added to document this feature and ensure proper behavior. + * + * What does this feature do? + * - if there is a CSS rule that specifies the size of the canvas (ex: `#canvas { width: 100%;}`), then the canvas + * will be scaled to match this value. Note that from a GLFW point of view, the size of the window remains what + * gets specified in `glfwCreateWindow` and/or `glfwSetWindowSize` + * - only global CSS rules apply, as setting a CSS rule on the canvas itself is removed (ex: `` is ignored) + * + * In HiDPI mode, (`glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE)`), this feature cannot work and as a result is disabled (because the canvas + * is sized using `devicePixelRatio` as the factor and CSS is used to scale it at the desired size). + */ + +/** + * As explained above, this function adds a global CSS rule instead of setting the CSS style directly on the canvas + * because it gets ignored otherwise. */ +EM_JS(void, addCSSScalingRule, (), { + const style = document.createElement('style'); + style.appendChild(document.createTextNode("#canvas { width: 700px; height: 500px; }")); + document.head.appendChild(style); +}) + +/** + * Since it is unclear in which browser/resolution this test will run we compute the actual ratio used by the test. + * This has the neat effect that the test can be run manually on a HiDPI screen. + */ +EM_JS(double, getDevicePixelRatio, (), {return (typeof devicePixelRatio == 'number' && devicePixelRatio) || 1.0;}) + +/** + * Checks window size and framebuffer size according to ratio */ +static void checkWindowSize(GLFWwindow *window, int expectedWidth, int expectedHeight, float ratio) { + // first check the window size + int w, h; + glfwGetWindowSize(window, &w, &h); + printf("windowSize => %d == %d && %d == %d\n", w, expectedWidth, h, expectedHeight); + assert(w == expectedWidth && h == expectedHeight); + + // second check the frame buffer size + int fbw, fbh; + glfwGetFramebufferSize(window, &fbw, &fbh); + printf("framebufferSize => %d == %d && %d == %d\n", fbw, (int) (expectedWidth * ratio), fbh, (int) (expectedHeight * ratio)); + assert(fbw == (int) (expectedWidth * ratio) && fbh == (int) (expectedHeight * ratio)); +} + +// Create a window without HiDPI support and without CSS rule => expected sizes to match +void use_case_1() { + printf("Use case #1\n"); + GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL); + assert(window); + checkWindowSize(window, 640, 480, 1.0); + double w, h; + emscripten_get_element_css_size("#canvas", &w, &h); + printf("CSS Size=%.0fx%.0f\n", w, h); + assert(w == 640 && h == 480); + glfwDestroyWindow(window); +} + +// Create a window without HiDPI support, and with CSS rule => +// the window size should match the creation size, but the CSS size should match the rule. +void use_case_2() { + printf("Use case #2\n"); + GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL); + assert(window); + checkWindowSize(window, 640, 480, 1.0); + double w, h; + emscripten_get_element_css_size("#canvas", &w, &h); + printf("CSS Size=%.0fx%.0f\n", w, h); + assert(w == 700 && h == 500); // Rule is "#canvas { width: 700px; height: 500px; }" + glfwDestroyWindow(window); +} + +// Create a window with HiDPI support, and with CSS rule => +// the window size and framebuffer size should match the creation size (CSS rule is ignored) +void use_case_3() { + printf("Use case #3\n"); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL); + assert(window); + double dpr = getDevicePixelRatio(); + printf("devicePixelRatio=%.0f\n", dpr); + checkWindowSize(window, 640, 480, dpr); + double w, h; + emscripten_get_element_css_size("#canvas", &w, &h); + printf("CSS Size=%.0fx%.0f\n", w, h); + assert(w == 640 && h == 480); + glfwDestroyWindow(window); +} + +int main() { + assert(glfwInit() == GLFW_TRUE); + + use_case_1(); + + // Add CSS rule for the following use cases + addCSSScalingRule(); + + use_case_2(); + use_case_3(); + + printf("All tests complete\n"); + glfwTerminate(); + return 0; +} diff --git a/test/browser/test_glfw3_default_hints.c b/test/browser/test_glfw3_default_hints.c index f53e90daa37f0..db5253b4825a4 100644 --- a/test/browser/test_glfw3_default_hints.c +++ b/test/browser/test_glfw3_default_hints.c @@ -59,7 +59,7 @@ int main() { TEST_GLFW3_DEFAULTS_HINTS[0x00022008] = 0; ); - assert(glfwInit() == GL_TRUE); + assert(glfwInit() == GLFW_TRUE); // Use case: after glfwInit, default window hints are correct { diff --git a/test/browser/test_glfw3_hi_dpi_aware.c b/test/browser/test_glfw3_hi_dpi_aware.c index 256dd3af404d6..c6c4c55dcd26b 100644 --- a/test/browser/test_glfw3_hi_dpi_aware.c +++ b/test/browser/test_glfw3_hi_dpi_aware.c @@ -12,6 +12,44 @@ #include #include +/** + * History: + * - HiDPI awareness is a feature that was added in 3.1.51 / December 2023 + * - It allows to render the canvas for modern 4k/retina screens in hi resolution + * - It behaves similarly to the way it is handled on the native desktop platform: + * - `glfwGetWindowSize` returns the size of the GLFW window (as set via `glfwCreateWindow` or `glfwSetWindowSize`) + * - `glfwGetFramebufferSize` returns the actual size of the framebuffer and is usually used for the viewport (ex: `glViewport(0, 0, fbWidth, fbHeight)`) + * + * The feature is enabled via a window hint: `glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE)` + * + * From an implementation point of view, the actual size of the canvas (canvas.width and canvas.height) is defined to be the framebuffer size. + * CSS is used to scale the canvas to the GLFW window size. The factor used for HiDPI scaling is `devicePixelRatio`. + * + * For example, assuming a window size of 640x480 and a devicePixelRatio of 2, the canvas element handled by the library would look like this: + * + * + * Important note: when HiDPI awareness is enabled, the library takes over the size of the canvas, in particular its CSS width and height. + * As a result, CSS Scaling (see `test_glfw3_css_scaling.c` for details) is disabled. + * If you need the canvas to be resized dynamically (for example when the browser window is resized), + * you can use a different HTML element with CSS sizing and set a callback to update the size of the canvas appropriately. + * + * Example: + * + * GLFWWindow *window = ...; + * emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, false, OnResize); + * + * // assuming HTML like this
+ * static EM_BOOL OnResize(int event_type, const EmscriptenUiEvent* event, void* user_data) { + * GLFWWindow *window = (GLFWWindow*) user_data; + * double w, h; + * // use ANOTHER HTML element + * emscripten_get_element_css_size("#canvas-container", &w, &h); + * // set the size of the GLFW window accordingly + * glfwSetWindowSize(window, (int)w, (int)h); + * return true; + * } + */ + // installing mock devicePixelRatio (independent of the browser/screen resolution) static void installMockDevicePixelRatio() { printf("installing mock devicePixelRatio...\n"); @@ -64,7 +102,7 @@ int main() { GLFWwindow* window; - assert(glfwInit() == GL_TRUE); + assert(glfwInit() == GLFW_TRUE); installMockDevicePixelRatio(); diff --git a/test/test_browser.py b/test/test_browser.py index 0b568a6ac352c..5ff34f895e472 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -2864,6 +2864,10 @@ def test_glfw_events(self, args): def test_glfw3_hi_dpi_aware(self): self.btest_exit('test_glfw3_hi_dpi_aware.c', args=['-sUSE_GLFW=3', '-lGL']) + @requires_graphics_hardware + def test_glfw3_css_scaling(self): + self.btest_exit('test_glfw3_css_scaling.c', args=['-sUSE_GLFW=3']) + @requires_graphics_hardware @also_with_wasm2js def test_sdl2_image(self):