Skip to content

Commit 5203d08

Browse files
authored
[GLFW] Restore css scaling behavior (removed in 3.1.51) (#22900)
- the changes in 3.1.51 to add HiDPI support removed a feature that is not documented nor a feature of GLFW - raylib wants it back so this patch restores it Some points: * I added a function `isCSSScalingEnabled()` with documentation to make it clear what is happening * Due to the fact that HiDPI / 4k support overrides CSS, CSS scaling is disabled in that mode. There would be a conflict otherwise * Since this feature is not really standard, I introduced a way to disable it (since it was implemented prior to 3.1.51, I felt it was better to have it on by default with a way to disable it rather than the other way around). I have tested the changes as best as I could with ImGui and raylib. I tested a few examples with raylib and it seems to be doing what they want (the canvas occupies the fullscreen and the mouse coordinates are correct and resizing the window follows appriopriately) FIxes: #22847
1 parent 473d961 commit 5203d08

File tree

6 files changed

+191
-17
lines changed

6 files changed

+191
-17
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ See docs/process.md for more on how version tagging works.
2323
- The `MEMORY64` setting is no longer experimental. At time of writing all
2424
browsers still require a flag to run the resulting binaries but that should
2525
change in the coming months since the proposal is now at stage 4. (#22864)
26+
- GLFW: Fixed regression introduced in 3.1.51. CSS scaling is now available
27+
again. Note that CSS scaling is disabled in HiDPI mode. (#22847, #22900)
2628

2729
3.1.71 - 11/04/24
2830
-----------------

src/library_glfw.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,9 +1110,9 @@ var LibraryGLFW = {
11101110
// If context creation failed, do not return a valid window
11111111
if (!Module.ctx && useWebGL) return 0;
11121112

1113-
// Get non alive id
1113+
// Initializes the framebuffer size from the canvas
11141114
const canvas = Module['canvas'];
1115-
var win = new GLFW_Window(id, canvas.clientWidth, canvas.clientHeight, canvas.width, canvas.height, title, monitor, share);
1115+
var win = new GLFW_Window(id, width, height, canvas.width, canvas.height, title, monitor, share);
11161116

11171117
// Set window to array
11181118
if (id - 1 == GLFW.windows.length) {
@@ -1248,7 +1248,7 @@ var LibraryGLFW = {
12481248
if (canvas.width != wNativeScaled) canvas.width = wNativeScaled;
12491249
if (canvas.height != hNativeScaled) canvas.height = hNativeScaled;
12501250
if (typeof canvas.style != 'undefined') {
1251-
if (wNativeScaled != wNative || hNativeScaled != hNative) {
1251+
if (!GLFW.isCSSScalingEnabled()) {
12521252
canvas.style.setProperty( "width", wNative + "px", "important");
12531253
canvas.style.setProperty("height", hNative + "px", "important");
12541254
} else {
@@ -1258,13 +1258,11 @@ var LibraryGLFW = {
12581258
}
12591259
},
12601260

1261-
// Overrides Browser.calculateMouseCoords to account for hi dpi scaling
1261+
// Overrides Browser.calculateMouseCoords to account for HiDPI scaling and CSS scaling
12621262
calculateMouseCoords(pageX, pageY) {
12631263
// Calculate the movement based on the changes
12641264
// in the coordinates.
1265-
var rect = Module["canvas"].getBoundingClientRect();
1266-
var cw = Module["canvas"].clientWidth;
1267-
var ch = Module["canvas"].clientHeight;
1265+
const rect = Module["canvas"].getBoundingClientRect();
12681266

12691267
// Neither .scrollX or .pageXOffset are defined in a spec, but
12701268
// we prefer .scrollX because it is currently in a spec draft.
@@ -1279,11 +1277,14 @@ var LibraryGLFW = {
12791277
var adjustedX = pageX - (scrollX + rect.left);
12801278
var adjustedY = pageY - (scrollY + rect.top);
12811279

1282-
// the canvas might be CSS-scaled compared to its backbuffer;
1283-
// SDL-using content will want mouse coordinates in terms
1284-
// of backbuffer units.
1285-
adjustedX = adjustedX * (cw / rect.width);
1286-
adjustedY = adjustedY * (ch / rect.height);
1280+
// getBoundingClientRect() returns dimension affected by CSS, so as a result:
1281+
// - when CSS scaling is enabled, this will fix the mouse coordinates to match the width/height of the window
1282+
// - otherwise the CSS width/height are forced to the width/height of the GLFW window (see updateCanvasDimensions),
1283+
// so there is no need to adjust the position
1284+
if (GLFW.isCSSScalingEnabled() && GLFW.active) {
1285+
adjustedX = adjustedX * (GLFW.active.width / rect.width);
1286+
adjustedY = adjustedY * (GLFW.active.height / rect.height);
1287+
}
12871288

12881289
return { x: adjustedX, y: adjustedY };
12891290
},
@@ -1308,10 +1309,19 @@ var LibraryGLFW = {
13081309
return false;
13091310
},
13101311

1312+
/**
1313+
* CSS Scaling is a feature that is NOT part of the GLFW API, but for historical reasons, it is available
1314+
* in Emscripten.
1315+
* It is automatically disabled when using Hi DPI (the library overrides CSS sizes). */
1316+
isCSSScalingEnabled() {
1317+
return !GLFW.isHiDPIAware();
1318+
},
1319+
13111320
adjustCanvasDimensions() {
1312-
const canvas = Module['canvas'];
1313-
Browser.updateCanvasDimensions(canvas, canvas.clientWidth, canvas.clientHeight);
1314-
Browser.updateResizeListeners();
1321+
if (GLFW.active) {
1322+
Browser.updateCanvasDimensions(Module['canvas'], GLFW.active.width, GLFW.active.height);
1323+
Browser.updateResizeListeners();
1324+
}
13151325
},
13161326

13171327
getHiDPIScale() {

test/browser/test_glfw3_css_scaling.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2024 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <GLFW/glfw3.h>
9+
#include <assert.h>
10+
#include <emscripten/html5.h>
11+
#include <stdio.h>
12+
13+
/*
14+
* History:
15+
* - This test was added after the issue #22847 was reported (November 2024).
16+
* - For historical reasons, the JavaScript GLFW implementation includes a feature: the ability to scale the canvas via CSS.
17+
* - Note that this feature is not part of GLFW as GLFW does not offer any API to scale the window.
18+
* - Being undocumented/untested, this feature was accidentally removed when HiDPI support was added (in 3.1.51 / December 2023).
19+
* - This test was added to document this feature and ensure proper behavior.
20+
*
21+
* What does this feature do?
22+
* - if there is a CSS rule that specifies the size of the canvas (ex: `#canvas { width: 100%;}`), then the canvas
23+
* will be scaled to match this value. Note that from a GLFW point of view, the size of the window remains what
24+
* gets specified in `glfwCreateWindow` and/or `glfwSetWindowSize`
25+
* - only global CSS rules apply, as setting a CSS rule on the canvas itself is removed (ex: `<canvas style="width:100%;">` is ignored)
26+
*
27+
* In HiDPI mode, (`glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE)`), this feature cannot work and as a result is disabled (because the canvas
28+
* is sized using `devicePixelRatio` as the factor and CSS is used to scale it at the desired size).
29+
*/
30+
31+
/**
32+
* As explained above, this function adds a global CSS rule instead of setting the CSS style directly on the canvas
33+
* because it gets ignored otherwise. */
34+
EM_JS(void, addCSSScalingRule, (), {
35+
const style = document.createElement('style');
36+
style.appendChild(document.createTextNode("#canvas { width: 700px; height: 500px; }"));
37+
document.head.appendChild(style);
38+
})
39+
40+
/**
41+
* Since it is unclear in which browser/resolution this test will run we compute the actual ratio used by the test.
42+
* This has the neat effect that the test can be run manually on a HiDPI screen.
43+
*/
44+
EM_JS(double, getDevicePixelRatio, (), {return (typeof devicePixelRatio == 'number' && devicePixelRatio) || 1.0;})
45+
46+
/**
47+
* Checks window size and framebuffer size according to ratio */
48+
static void checkWindowSize(GLFWwindow *window, int expectedWidth, int expectedHeight, float ratio) {
49+
// first check the window size
50+
int w, h;
51+
glfwGetWindowSize(window, &w, &h);
52+
printf("windowSize => %d == %d && %d == %d\n", w, expectedWidth, h, expectedHeight);
53+
assert(w == expectedWidth && h == expectedHeight);
54+
55+
// second check the frame buffer size
56+
int fbw, fbh;
57+
glfwGetFramebufferSize(window, &fbw, &fbh);
58+
printf("framebufferSize => %d == %d && %d == %d\n", fbw, (int) (expectedWidth * ratio), fbh, (int) (expectedHeight * ratio));
59+
assert(fbw == (int) (expectedWidth * ratio) && fbh == (int) (expectedHeight * ratio));
60+
}
61+
62+
// Create a window without HiDPI support and without CSS rule => expected sizes to match
63+
void use_case_1() {
64+
printf("Use case #1\n");
65+
GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL);
66+
assert(window);
67+
checkWindowSize(window, 640, 480, 1.0);
68+
double w, h;
69+
emscripten_get_element_css_size("#canvas", &w, &h);
70+
printf("CSS Size=%.0fx%.0f\n", w, h);
71+
assert(w == 640 && h == 480);
72+
glfwDestroyWindow(window);
73+
}
74+
75+
// Create a window without HiDPI support, and with CSS rule =>
76+
// the window size should match the creation size, but the CSS size should match the rule.
77+
void use_case_2() {
78+
printf("Use case #2\n");
79+
GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL);
80+
assert(window);
81+
checkWindowSize(window, 640, 480, 1.0);
82+
double w, h;
83+
emscripten_get_element_css_size("#canvas", &w, &h);
84+
printf("CSS Size=%.0fx%.0f\n", w, h);
85+
assert(w == 700 && h == 500); // Rule is "#canvas { width: 700px; height: 500px; }"
86+
glfwDestroyWindow(window);
87+
}
88+
89+
// Create a window with HiDPI support, and with CSS rule =>
90+
// the window size and framebuffer size should match the creation size (CSS rule is ignored)
91+
void use_case_3() {
92+
printf("Use case #3\n");
93+
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
94+
GLFWwindow* window = glfwCreateWindow(640, 480, "test_glfw3_css_scaling.c", NULL, NULL);
95+
assert(window);
96+
double dpr = getDevicePixelRatio();
97+
printf("devicePixelRatio=%.0f\n", dpr);
98+
checkWindowSize(window, 640, 480, dpr);
99+
double w, h;
100+
emscripten_get_element_css_size("#canvas", &w, &h);
101+
printf("CSS Size=%.0fx%.0f\n", w, h);
102+
assert(w == 640 && h == 480);
103+
glfwDestroyWindow(window);
104+
}
105+
106+
int main() {
107+
assert(glfwInit() == GLFW_TRUE);
108+
109+
use_case_1();
110+
111+
// Add CSS rule for the following use cases
112+
addCSSScalingRule();
113+
114+
use_case_2();
115+
use_case_3();
116+
117+
printf("All tests complete\n");
118+
glfwTerminate();
119+
return 0;
120+
}

test/browser/test_glfw3_default_hints.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ int main() {
5959
TEST_GLFW3_DEFAULTS_HINTS[0x00022008] = 0;
6060
);
6161

62-
assert(glfwInit() == GL_TRUE);
62+
assert(glfwInit() == GLFW_TRUE);
6363

6464
// Use case: after glfwInit, default window hints are correct
6565
{

test/browser/test_glfw3_hi_dpi_aware.c

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,44 @@
1212
#include <stdbool.h>
1313
#include <emscripten/html5.h>
1414

15+
/**
16+
* History:
17+
* - HiDPI awareness is a feature that was added in 3.1.51 / December 2023
18+
* - It allows to render the canvas for modern 4k/retina screens in hi resolution
19+
* - It behaves similarly to the way it is handled on the native desktop platform:
20+
* - `glfwGetWindowSize` returns the size of the GLFW window (as set via `glfwCreateWindow` or `glfwSetWindowSize`)
21+
* - `glfwGetFramebufferSize` returns the actual size of the framebuffer and is usually used for the viewport (ex: `glViewport(0, 0, fbWidth, fbHeight)`)
22+
*
23+
* The feature is enabled via a window hint: `glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE)`
24+
*
25+
* From an implementation point of view, the actual size of the canvas (canvas.width and canvas.height) is defined to be the framebuffer size.
26+
* CSS is used to scale the canvas to the GLFW window size. The factor used for HiDPI scaling is `devicePixelRatio`.
27+
*
28+
* For example, assuming a window size of 640x480 and a devicePixelRatio of 2, the canvas element handled by the library would look like this:
29+
* <canvas width="1280" height="960" style="width: 640; height: 480;">
30+
*
31+
* Important note: when HiDPI awareness is enabled, the library takes over the size of the canvas, in particular its CSS width and height.
32+
* As a result, CSS Scaling (see `test_glfw3_css_scaling.c` for details) is disabled.
33+
* If you need the canvas to be resized dynamically (for example when the browser window is resized),
34+
* you can use a different HTML element with CSS sizing and set a callback to update the size of the canvas appropriately.
35+
*
36+
* Example:
37+
*
38+
* GLFWWindow *window = ...;
39+
* emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, false, OnResize);
40+
*
41+
* // assuming HTML like this <div id="canvas-container" style="width: 100%"><canvas id="canvas"></div>
42+
* static EM_BOOL OnResize(int event_type, const EmscriptenUiEvent* event, void* user_data) {
43+
* GLFWWindow *window = (GLFWWindow*) user_data;
44+
* double w, h;
45+
* // use ANOTHER HTML element
46+
* emscripten_get_element_css_size("#canvas-container", &w, &h);
47+
* // set the size of the GLFW window accordingly
48+
* glfwSetWindowSize(window, (int)w, (int)h);
49+
* return true;
50+
* }
51+
*/
52+
1553
// installing mock devicePixelRatio (independent of the browser/screen resolution)
1654
static void installMockDevicePixelRatio() {
1755
printf("installing mock devicePixelRatio...\n");
@@ -64,7 +102,7 @@ int main() {
64102

65103
GLFWwindow* window;
66104

67-
assert(glfwInit() == GL_TRUE);
105+
assert(glfwInit() == GLFW_TRUE);
68106

69107
installMockDevicePixelRatio();
70108

test/test_browser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,6 +2864,10 @@ def test_glfw_events(self, args):
28642864
def test_glfw3_hi_dpi_aware(self):
28652865
self.btest_exit('test_glfw3_hi_dpi_aware.c', args=['-sUSE_GLFW=3', '-lGL'])
28662866

2867+
@requires_graphics_hardware
2868+
def test_glfw3_css_scaling(self):
2869+
self.btest_exit('test_glfw3_css_scaling.c', args=['-sUSE_GLFW=3'])
2870+
28672871
@requires_graphics_hardware
28682872
@also_with_wasm2js
28692873
def test_sdl2_image(self):

0 commit comments

Comments
 (0)