Skip to content

Commit 4d07e79

Browse files
committed
Fix GLUT window resizing when CSS scaling (#7133)
1) Add reshapeHandler() to set canvas size to clientWidth, clientHeight, and pass these along to glutReshapeFunc. 2) Register reshapeHandler as 'resize' event listener with addEventListener. 3) Call reshapeHandler on glutMainLoop instead of glutReshapeWindow, which unnecessarily exits fullscreen.
1 parent eeeb51e commit 4d07e79

File tree

3 files changed

+163
-8
lines changed

3 files changed

+163
-8
lines changed

src/lib/libglut.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,15 @@ var LibraryGLUT = {
298298
{{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height);
299299
}
300300
_glutPostRedisplay();
301+
},
302+
303+
reshapeHandler: () => {
304+
// Use clientWidth and clientHeight, which include CSS scaling of the canvas
305+
var canvas = Browser.getCanvas();
306+
Browser.setCanvasSize(canvas.clientWidth, canvas.clientHeight, true);
307+
if (GLUT.reshapeFunc) {
308+
{{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(canvas.clientWidth, canvas.clientHeight);
309+
}
301310
}
302311
},
303312

@@ -336,11 +345,7 @@ var LibraryGLUT = {
336345
// Firefox
337346
window.addEventListener('DOMMouseScroll', GLUT.onMouseWheel, true);
338347

339-
Browser.resizeListeners.push((width, height) => {
340-
if (GLUT.reshapeFunc) {
341-
{{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height);
342-
}
343-
});
348+
window.addEventListener('resize', GLUT.reshapeHandler, true);
344349

345350
addOnExit(() => {
346351
if (isTouchDevice) {
@@ -359,6 +364,8 @@ var LibraryGLUT = {
359364
// Firefox
360365
window.removeEventListener('DOMMouseScroll', GLUT.onMouseWheel, true);
361366

367+
window.removeEventListener('resize', GLUT.reshapeHandler, true);
368+
362369
var canvas = Browser.getCanvas();
363370
canvas.width = canvas.height = 1;
364371
});
@@ -633,10 +640,9 @@ var LibraryGLUT = {
633640
},
634641

635642
glutMainLoop__proxy: 'sync',
636-
glutMainLoop__deps: ['$GLUT', 'glutReshapeWindow', 'glutPostRedisplay'],
643+
glutMainLoop__deps: ['$GLUT', 'glutPostRedisplay'],
637644
glutMainLoop: () => {
638-
var canvas = Browser.getCanvas();
639-
_glutReshapeWindow(canvas.width, canvas.height);
645+
GLUT.reshapeHandler();
640646
_glutPostRedisplay();
641647
throw 'unwind';
642648
},

test/browser/test_glut_resize.c

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2025 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 <stdio.h>
9+
#include <assert.h>
10+
#include <emscripten/emscripten.h>
11+
#include <GL/glut.h>
12+
13+
typedef struct {
14+
int width;
15+
int height;
16+
} rect_size_t;
17+
18+
static rect_size_t browser_window_size = { 0, 0 };
19+
static rect_size_t glut_init_size = { 0, 0 };
20+
static rect_size_t glut_reshape_size = { 0, 0 };
21+
22+
void print_size_test(const char* name, rect_size_t rect_size) {
23+
static int test_count = 0;
24+
printf("Test %d: %s = %d x %d\n", ++test_count, name, rect_size.width, rect_size.height);
25+
}
26+
27+
int equal_size(rect_size_t rect_1, rect_size_t rect_2) {
28+
return (rect_1.width == rect_2.width && rect_1.height == rect_2.height);
29+
}
30+
31+
/**
32+
* Obtain various dimensions
33+
*/
34+
EM_JS(void, get_browser_window_size, (int* width, int* height), {
35+
setValue(width, window.innerWidth, 'i32');
36+
setValue(height, window.innerHeight, 'i32');
37+
});
38+
39+
EM_JS(void, get_canvas_client_size, (int* width, int* height), {
40+
const canvas = Module.canvas;
41+
setValue(width, canvas.clientWidth, 'i32');
42+
setValue(height, canvas.clientHeight, 'i32');
43+
});
44+
45+
EM_JS(void, get_canvas_size, (int* width, int* height), {
46+
const canvas = Module.canvas;
47+
setValue(width, canvas.width, 'i32');
48+
setValue(height, canvas.height, 'i32');
49+
});
50+
51+
/**
52+
* Update canvas style with given width and height, then invoke window resize event
53+
*/
54+
EM_JS(void, test_resize_with_CSS, (const char* position, const char* width, const char* height), {
55+
const canvas = Module.canvas;
56+
canvas.style.position = UTF8ToString(position);
57+
canvas.style.width = UTF8ToString(width);
58+
canvas.style.height = UTF8ToString(height);
59+
60+
window.dispatchEvent(new UIEvent('resize'));
61+
});
62+
63+
/**
64+
* Verify canvas and reshape callback match target size, and also that
65+
* canvas width, height matches canvas clientWidth, clientHeight
66+
*/
67+
void assert_sizes_equal(rect_size_t target_size) {
68+
/* verify target size match */
69+
rect_size_t canvas_size;
70+
get_canvas_size(&canvas_size.width, &canvas_size.height);
71+
assert(equal_size(canvas_size, target_size));
72+
assert(equal_size(glut_reshape_size, target_size));
73+
74+
/* verify canvas client size match */
75+
rect_size_t canvas_client_size;
76+
get_canvas_client_size(&canvas_client_size.width, &canvas_client_size.height);
77+
assert(equal_size(canvas_size, canvas_client_size));
78+
}
79+
80+
/**
81+
* Resizing tests
82+
*/
83+
void run_tests() {
84+
85+
/* startup */
86+
print_size_test("startup, no CSS: canvas == glutReshapeFunc == glutInitWindow size", glut_init_size);
87+
assert_sizes_equal(glut_init_size);
88+
89+
/* glutReshapeWindow */
90+
rect_size_t new_reshape_size = { glut_init_size.width + 40, glut_init_size.height + 20 };
91+
print_size_test("glut reshape, no CSS: canvas == glutReshapeFunc == glutReshapeWindow size", new_reshape_size);
92+
glutReshapeWindow(new_reshape_size.width, new_reshape_size.height);
93+
assert_sizes_equal(new_reshape_size);
94+
95+
/* 100% scale CSS */
96+
print_size_test("100% window scale CSS: canvas == glutReshapeFunc == browser window size", browser_window_size);
97+
test_resize_with_CSS("fixed", "100%", "100%"); /* fixed, so canvas is driven by window size */
98+
assert_sizes_equal(browser_window_size);
99+
100+
/* specific pixel size CSS */
101+
rect_size_t css_pixels_size = { glut_init_size.width - 20, glut_init_size.height + 40 };
102+
print_size_test("specific pixel size CSS: canvas == glutReshapeFunc == CSS specific size", css_pixels_size);
103+
char css_width[16], css_height[16];
104+
snprintf (css_width, 16, "%dpx", css_pixels_size.width);
105+
snprintf (css_height, 16, "%dpx", css_pixels_size.height);
106+
test_resize_with_CSS("static", css_width, css_height); /* static, canvas is driven by CSS size */
107+
assert_sizes_equal(css_pixels_size);
108+
109+
/* mix of CSS scale and pixel size */
110+
rect_size_t css_mixed_size = { browser_window_size.width * 0.6, 100 };
111+
print_size_test("60% width, 100px height CSS: canvas == glutReshapeFunc == CSS mixed size", css_mixed_size);
112+
test_resize_with_CSS("fixed", "60%", "100px"); /* fixed, canvas width is driven by window size */
113+
assert_sizes_equal(css_mixed_size);
114+
115+
/* run tests once */
116+
glutIdleFunc(NULL);
117+
emscripten_force_exit(0);
118+
}
119+
120+
/**
121+
* Reshape callback
122+
*/
123+
void reshape(int w, int h) {
124+
glut_reshape_size.width = w;
125+
glut_reshape_size.height = h;
126+
}
127+
128+
int main(int argc, char *argv[]) {
129+
/* Make glut initial canvas size be 1/2 of browser window */
130+
get_browser_window_size(&browser_window_size.width, &browser_window_size.height);
131+
glut_init_size.width = browser_window_size.width / 2;
132+
glut_init_size.height = browser_window_size.height / 2;
133+
134+
glutInit(&argc, argv);
135+
glutInitWindowSize(glut_init_size.width, glut_init_size.height);
136+
glutInitDisplayMode(GLUT_RGB);
137+
glutCreateWindow("test_glut_resize.c");
138+
139+
/* Set up glut callback functions */
140+
glutIdleFunc(run_tests);
141+
glutReshapeFunc(reshape);
142+
glutDisplayFunc(NULL);
143+
144+
glutMainLoop();
145+
return 0;
146+
}

test/test_browser.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,9 @@ def test_glut_glutget(self):
11351135
self.btest_exit('glut_glutget.c', cflags=['-lglut', '-lGL'])
11361136
self.btest_exit('glut_glutget.c', cflags=['-lglut', '-lGL', '-DAA_ACTIVATED', '-DDEPTH_ACTIVATED', '-DSTENCIL_ACTIVATED', '-DALPHA_ACTIVATED'])
11371137

1138+
def test_glut_resize(self):
1139+
self.btest_exit('test_glut_resize.c')
1140+
11381141
def test_sdl_joystick_1(self):
11391142
# Generates events corresponding to the Working Draft of the HTML5 Gamepad API.
11401143
# http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface

0 commit comments

Comments
 (0)