Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------
Expand Down
40 changes: 25 additions & 15 deletions src/library_glfw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand All @@ -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 };
},
Expand All @@ -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() {
Expand Down
120 changes: 120 additions & 0 deletions test/browser/test_glfw3_css_scaling.c
Original file line number Diff line number Diff line change
@@ -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 <GLFW/glfw3.h>
#include <assert.h>
#include <emscripten/html5.h>
#include <stdio.h>

/*
* 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: `<canvas style="width:100%;">` 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;
}
2 changes: 1 addition & 1 deletion test/browser/test_glfw3_default_hints.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
40 changes: 39 additions & 1 deletion test/browser/test_glfw3_hi_dpi_aware.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,44 @@
#include <stdbool.h>
#include <emscripten/html5.h>

/**
* 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:
* <canvas width="1280" height="960" style="width: 640; height: 480;">
*
* 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 <div id="canvas-container" style="width: 100%"><canvas id="canvas"></div>
* 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");
Expand Down Expand Up @@ -64,7 +102,7 @@ int main() {

GLFWwindow* window;

assert(glfwInit() == GL_TRUE);
assert(glfwInit() == GLFW_TRUE);

installMockDevicePixelRatio();

Expand Down
4 changes: 4 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading