diff --git a/src/lib/libglut.js b/src/lib/libglut.js index a490cbf8b4557..b8f9e5c40304f 100644 --- a/src/lib/libglut.js +++ b/src/lib/libglut.js @@ -298,6 +298,13 @@ var LibraryGLUT = { {{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height); } _glutPostRedisplay(); + }, + + // Resize callback stage 1: update canvas by setCanvasSize, which notifies resizeListeners including GLUT.reshapeFunc + onResize: () => { + // Update canvas size to clientWidth and clientHeight, which include CSS scaling + var canvas = Browser.getCanvas(); + Browser.setCanvasSize(canvas.clientWidth, canvas.clientHeight, /*noUpdates*/false); } }, @@ -336,6 +343,10 @@ var LibraryGLUT = { // Firefox window.addEventListener('DOMMouseScroll', GLUT.onMouseWheel, true); + // Resize callback stage 1: update canvas which notifies resizeListeners + window.addEventListener('resize', GLUT.onResize, true); + + // Resize callback stage 2: updateResizeListeners notifies reshapeFunc Browser.resizeListeners.push((width, height) => { if (GLUT.reshapeFunc) { {{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height); @@ -359,6 +370,8 @@ var LibraryGLUT = { // Firefox window.removeEventListener('DOMMouseScroll', GLUT.onMouseWheel, true); + window.removeEventListener('resize', GLUT.onResize, true); + var canvas = Browser.getCanvas(); canvas.width = canvas.height = 1; }); @@ -633,10 +646,10 @@ var LibraryGLUT = { }, glutMainLoop__proxy: 'sync', - glutMainLoop__deps: ['$GLUT', 'glutReshapeWindow', 'glutPostRedisplay'], + glutMainLoop__deps: ['$GLUT', 'glutPostRedisplay'], glutMainLoop: () => { - var canvas = Browser.getCanvas(); - _glutReshapeWindow(canvas.width, canvas.height); + // Do an initial resize, since there's no window resize event on startup + GLUT.onResize(); _glutPostRedisplay(); throw 'unwind'; }, diff --git a/test/browser/test_glut_resize.c b/test/browser/test_glut_resize.c new file mode 100644 index 0000000000000..f8d568cc75d6d --- /dev/null +++ b/test/browser/test_glut_resize.c @@ -0,0 +1,208 @@ +/* + * Copyright 2025 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 +#include + +typedef struct { + int32_t width; + int32_t height; +} rect_size_t; + +static rect_size_t browser_window_size = { 0, 0 }; +static rect_size_t glut_init_size = { 0, 0 }; +static rect_size_t glut_reshape_size = { 0, 0 }; +static rect_size_t target_size = { 0, 0 }; + +/* + * Set run_async_verification to 0 for sync test cases, and 1 for async tests. + * + * Callback sequence for test case 1 & 2 (synchronous): + * glutMainLoop -> GLUT.onSize -> Browser.setCanvasSize -> updateResizeListeners -> GLUT.reshapeFunc + * glutResizeWindow -> Browser.setCanvasSize -> updateResizeListeners -> GLUT.reshapeFunc + * + * Callback sequence for test cases 3-5 (async): + * window resize -> async update -> GLUT.onSize -> Browser.setCanvasSize -> updateResizeListeners -> GLUT.reshapeFunc + * + * Because window resize does not immediately call GLUT.onSize, we wait to run verification of a test until we get + * confirmation in GLUT.reshapeFunc. And after verification is done, we move on to the next test. + * + */ +static int run_async_verification = 0; + +void print_size_test(int test_num, const char* name, rect_size_t rect_size) { + printf("Test %d: %s = %d x %d\n", test_num, name, rect_size.width, rect_size.height); +} + +int equal_size(rect_size_t rect_1, rect_size_t rect_2) { + return (rect_1.width == rect_2.width && rect_1.height == rect_2.height); +} + +/** + * Obtain various dimensions + */ +EM_JS(void, get_browser_window_size, (int32_t* width, int32_t* height), { + setValue(Number(width), window.innerWidth, 'i32'); + setValue(Number(height), window.innerHeight, 'i32'); +}); + +EM_JS(void, get_canvas_client_size, (int32_t* width, int32_t* height), { + const canvas = Module.canvas; + setValue(Number(width), canvas.clientWidth, 'i32'); + setValue(Number(height), canvas.clientHeight, 'i32'); +}); + +EM_JS(void, get_canvas_size, (int32_t* width, int32_t* height), { + const canvas = Module.canvas; + setValue(Number(width), canvas.width, 'i32'); + setValue(Number(height), canvas.height, 'i32'); +}); + +/** + * Update canvas style with given width and height, then invoke window resize event + */ +EM_JS(void, test_resize_with_CSS, (const char* position, const char* width, const char* height), { + const canvas = Module.canvas; + canvas.style.position = UTF8ToString(Number(position)); + canvas.style.width = UTF8ToString(Number(width)); + canvas.style.height = UTF8ToString(Number(height)); + + window.dispatchEvent(new UIEvent('resize')); +}); + +/** + * Verify canvas and reshape callback match target size, and also that + * canvas width, height matches canvas clientWidth, clientHeight + */ +void assert_canvas_and_target_sizes_equal() { + /* verify target size match */ + rect_size_t canvas_size; + get_canvas_size(&canvas_size.width, &canvas_size.height); + assert(equal_size(canvas_size, target_size)); + assert(equal_size(glut_reshape_size, target_size)); + + /* verify canvas client size match */ + rect_size_t canvas_client_size; + get_canvas_client_size(&canvas_client_size.width, &canvas_client_size.height); + assert(equal_size(canvas_size, canvas_client_size)); +} + +/** + * Verify the result of the previous test and then run the next one + */ +void verify_test_and_run_next() { + void run_next_test(); + + assert_canvas_and_target_sizes_equal(); + run_next_test(); +} + +/** + * Resizing tests + */ +void run_next_test() { + static int test_num = 0; + ++test_num; + + switch(test_num) { + case 1: { + /* startup */ + target_size = glut_init_size; + print_size_test(test_num, "startup, no CSS: canvas == glutReshapeFunc == glutInitWindow size", target_size); + verify_test_and_run_next(); + break; + } + case 2: { + /* glutReshapeWindow */ + target_size.width = glut_init_size.width + 40; + target_size.height = glut_init_size.height + 20; + print_size_test(test_num, "glut reshape, no CSS: canvas == glutReshapeFunc == glutReshapeWindow size", target_size); + glutReshapeWindow(target_size.width, target_size.height); + verify_test_and_run_next(); + break; + } + case 3: { + /* 100% scale CSS */ + target_size = browser_window_size; + print_size_test(test_num, "100% window scale CSS: canvas == glutReshapeFunc == browser window size", target_size); + run_async_verification = 1; + test_resize_with_CSS("fixed", "100%", "100%"); /* fixed, so canvas is driven by window size */ + break; + } + case 4: { + /* specific pixel size CSS */ + target_size.width = glut_init_size.width - 20; + target_size.height = glut_init_size.height + 40; + print_size_test(test_num, "specific pixel size CSS: canvas == glutReshapeFunc == CSS specific size", target_size); + char css_width[16], css_height[16]; + snprintf (css_width, 16, "%dpx", target_size.width); + snprintf (css_height, 16, "%dpx", target_size.height); + run_async_verification = 1; + test_resize_with_CSS("static", css_width, css_height); /* static, canvas is driven by CSS size */ + break; + } + case 5: { + /* mix of CSS scale and pixel size */ + target_size.width = browser_window_size.width; + target_size.height = 100; + print_size_test(test_num, "100% width, 100px height CSS: canvas == glutReshapeFunc == CSS mixed size", target_size); + run_async_verification = 1; + test_resize_with_CSS("fixed", "100%", "100px"); /* fixed, canvas width is driven by window size */ + break; + } + default: { + /* all tests complete */ + emscripten_force_exit(0); + break; + } + } +} + +/** + * Idle callback - start tests + */ +void start_tests() { + glutIdleFunc(NULL); + run_next_test(); +} + +/** + * Reshape callback - record latest size, verify and run next test if async + */ +void reshape(int w, int h) { + glut_reshape_size.width = (int32_t)w; + glut_reshape_size.height = (int32_t)h; + + if (run_async_verification) { + run_async_verification = 0; /* Only one verification per test */ + verify_test_and_run_next(); + } +} + +int main(int argc, char *argv[]) { + get_browser_window_size(&browser_window_size.width, &browser_window_size.height); + + /* Make glut initial canvas size be 1/2 of browser window */ + glut_init_size.width = browser_window_size.width / 2; + glut_init_size.height = browser_window_size.height / 2; + + glutInit(&argc, argv); + glutInitWindowSize(glut_init_size.width, glut_init_size.height); + glutInitDisplayMode(GLUT_RGB); + glutCreateWindow("test_glut_resize.c"); + + /* Set up glut callback functions */ + glutIdleFunc(start_tests); + glutReshapeFunc(reshape); + glutDisplayFunc(NULL); + + glutMainLoop(); + return 0; +} diff --git a/test/code_size/test_codesize_hello_dylink_all.json b/test/code_size/test_codesize_hello_dylink_all.json index 2600ae2486774..5be31a28cbd31 100644 --- a/test/code_size/test_codesize_hello_dylink_all.json +++ b/test/code_size/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 246735, + "a.out.js": 246847, "a.out.nodebug.wasm": 597852, - "total": 844587, + "total": 844699, "sent": [ "IMG_Init", "IMG_Load", diff --git a/test/test_browser.py b/test/test_browser.py index df9275cc74111..b4fb30d5e9f35 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -1135,6 +1135,9 @@ def test_glut_glutget(self): self.btest_exit('glut_glutget.c', cflags=['-lglut', '-lGL']) self.btest_exit('glut_glutget.c', cflags=['-lglut', '-lGL', '-DAA_ACTIVATED', '-DDEPTH_ACTIVATED', '-DSTENCIL_ACTIVATED', '-DALPHA_ACTIVATED']) + def test_glut_resize(self): + self.btest_exit('test_glut_resize.c') + def test_sdl_joystick_1(self): # Generates events corresponding to the Working Draft of the HTML5 Gamepad API. # http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface