Browse Source

Merge pull request #1159 from floooh/html5_canvas_cleanup

sokol_app.h html5: cleanup canvas lookup handling (see #1154)
Andre Weissflog 9 months ago
parent
commit
eaa1ca79a4
2 changed files with 139 additions and 22 deletions
  1. 32 0
      CHANGELOG.md
  2. 107 22
      sokol_app.h

+ 32 - 0
CHANGELOG.md

@@ -1,5 +1,37 @@
 ## Updates
 ## Updates
 
 
+### 23-Nov-2024
+
+- sokol_app.h html5: Merged PR https://github.com/floooh/sokol/pull/1159 (related
+  issue https://github.com/floooh/sokol/issues/1154).
+
+  This cleans up code that is concerned about finding the WebGL/WebGPU HTML canvas by:
+
+    - removing any leftover hacks from the time when Emscripten moved
+      from `document.getElementById()` to `document.querySelector()` for
+      looking up the canvas object
+    - adding two options for canvas objects that can't be looked up via
+      `document.querySelector()`
+
+  If you don't provide a custom canvas name to sokol_app.h this change
+  is non-breaking. Otherwise:
+
+    - in sokol_main(): change `.html5_canvas_name` to `.html5_canvas_selector`
+    - change the canvas name string to a CSS selector string (e.g.
+    from `"my_canvas"` to `"#my_canvas"`)
+
+  For more options to communicate the HTML canvas object to sokol_app.h,
+  please read the new doc section `SETTING THE CANVAS OBJECT ON THE WEB PLATFORM` in sokol_app.h.
+
+  Additionally, please also note the simplified `shell.html` in the
+  sokol-samples repository (some outdated cruft has been removed):
+
+  https://github.com/floooh/sokol-samples/blob/master/webpage/shell.html
+
+  Many thanks to @konsumer for kicking off the feature and the following
+  discussion :)
+
+
 ### 19-Nov-2024
 ### 19-Nov-2024
 
 
 - Merged PR https://github.com/floooh/sokol/pull/1155, this allows to use
 - Merged PR https://github.com/floooh/sokol/pull/1155, this allows to use

+ 107 - 22
sokol_app.h

@@ -998,6 +998,87 @@
     To prevent individual events from bubbling, call sapp_consume_event() from within
     To prevent individual events from bubbling, call sapp_consume_event() from within
     the sokol_app.h event callback when that specific event is reported.
     the sokol_app.h event callback when that specific event is reported.
 
 
+
+    SETTING THE CANVAS OBJECT ON THE WEB PLATFORM
+    =============================================
+    On the web, sokol_app.h and the Emscripten SDK functions need to find
+    the WebGL/WebGPU canvas intended for rendering and attaching event
+    handlers. This can happen in four ways:
+
+    1. do nothing and just set the id of the canvas object to 'canvas' (preferred)
+    2. via a CSS Selector string (preferred)
+    3. by setting the `Module.canvas` property to the canvas object
+    4. by adding the canvas object to the global variable `specialHTMLTargets[]`
+       (this is a special variable used by the Emscripten runtime to lookup
+       event target objects for which document.querySelector() cannot be used)
+
+    The easiest way is to just name your canvas object 'canvas':
+
+        <canvas id="canvas" ...></canvas>
+
+    This works because the default css selector string used by sokol_app.h
+    is '#canvas'.
+
+    If you name your canvas differently, you need to communicate that name to
+    sokol_app.h via `sapp_desc.html5_canvas_selector` as a regular css selector
+    string that's compatible with `document.querySelector()`. E.g. if your canvas
+    object looks like this:
+
+        <canvas id="bla" ...></canvas>
+
+    The `sapp_desc.html5_canvas_selector` string must be set to '#bla':
+
+        .html5_canvas_selector = "#bla"
+
+    If the canvas object cannot be looked up via `document.querySelector()` you
+    need to use one of the alternative methods, both involve the special
+    Emscripten runtime `Module` object which is usually setup in the index.html
+    like this before the WASM blob is loaded and instantiated:
+
+        <script type='text/javascript'>
+            var Module = {
+                // ...
+            };
+        </script>
+
+    The first option is to set the `Module.canvas` property to your canvas object:
+
+        <script type='text/javascript'>
+            var Module = {
+                canvas: my_canvas_object,
+            };
+        </script>
+
+    When sokol_app.h initializes, it will check the global Module object whether
+    a `Module.canvas` property exists and is an object. This method will add
+    a new entry to the `specialHTMLTargets[]` object
+
+    The other option is to add the canvas under a name chosen by you to the
+    special `specialHTMLTargets[]` map, which is used by the Emscripten runtime
+    to lookup 'event target objects' which are not visible to `document.querySelector()`.
+    Note that `specialHTMLTargets[]` must be updated after the Emscripten runtime
+    has started but before the WASM code is running. A good place for this is
+    the special `Module.preRun` array in index.html:
+
+        <script type='text/javascript'>
+            var Module = {
+                preRun: [
+                    () => {
+                        specialHTMLTargets['my_canvas'] = my_canvas_object;
+                    }
+                ],
+            };
+        </script>
+
+    In that case, pass the same string to sokol_app.h which is used as key
+    in the specialHTMLTargets[] map:
+
+        .html5_canvas_selector = "my_canvas"
+
+    If sokol_app.h can't find your canvas for some reason check for warning
+    messages on the browser console.
+
+
     OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY)
     OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY)
     ======================================================
     ======================================================
     NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android.
     NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android.
@@ -1147,7 +1228,6 @@
     the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
     the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
     errors.
     errors.
 
 
-
     TEMP NOTE DUMP
     TEMP NOTE DUMP
     ==============
     ==============
     - sapp_desc needs a bool whether to initialize depth-stencil surface
     - sapp_desc needs a bool whether to initialize depth-stencil surface
@@ -1717,7 +1797,7 @@ typedef struct sapp_desc {
     bool win32_console_utf8;            // if true, set the output console codepage to UTF-8
     bool win32_console_utf8;            // if true, set the output console codepage to UTF-8
     bool win32_console_create;          // if true, attach stdout/stderr to a new console window
     bool win32_console_create;          // if true, attach stdout/stderr to a new console window
     bool win32_console_attach;          // if true, attach stdout/stderr to parent process
     bool win32_console_attach;          // if true, attach stdout/stderr to parent process
-    const char* html5_canvas_name;      // the name (id) of the HTML5 canvas element, default is "canvas"
+    const char* html5_canvas_selector;  // css selector of the HTML5 canvas element, default is "#canvas"
     bool html5_canvas_resize;           // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked
     bool html5_canvas_resize;           // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked
     bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames
     bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames
     bool html5_premultiplied_alpha;     // HTML5 only: whether the rendered pixels use premultiplied alpha convention
     bool html5_premultiplied_alpha;     // HTML5 only: whether the rendered pixels use premultiplied alpha convention
@@ -3115,7 +3195,7 @@ _SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) {
             res.gl_minor_version = 3;
             res.gl_minor_version = 3;
         #endif
         #endif
     }
     }
-    res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas");
+    res.html5_canvas_selector = _sapp_def(res.html5_canvas_selector, "#canvas");
     res.clipboard_size = _sapp_def(res.clipboard_size, 8192);
     res.clipboard_size = _sapp_def(res.clipboard_size, 8192);
     res.max_dropped_files = _sapp_def(res.max_dropped_files, 1);
     res.max_dropped_files = _sapp_def(res.max_dropped_files, 1);
     res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048);
     res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048);
@@ -3142,9 +3222,8 @@ _SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) {
     _sapp.framebuffer_height = _sapp.window_height;
     _sapp.framebuffer_height = _sapp.window_height;
     _sapp.sample_count = _sapp.desc.sample_count;
     _sapp.sample_count = _sapp.desc.sample_count;
     _sapp.swap_interval = _sapp.desc.swap_interval;
     _sapp.swap_interval = _sapp.desc.swap_interval;
-    _sapp.html5_canvas_selector[0] = '#';
-    _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1);
-    _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1];
+    _sapp_strcpy(_sapp.desc.html5_canvas_selector, _sapp.html5_canvas_selector, sizeof(_sapp.html5_canvas_selector));
+    _sapp.desc.html5_canvas_selector = _sapp.html5_canvas_selector;
     _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site;
     _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site;
     _sapp.clipboard.enabled = _sapp.desc.enable_clipboard;
     _sapp.clipboard.enabled = _sapp.desc.enable_clipboard;
     if (_sapp.clipboard.enabled) {
     if (_sapp.clipboard.enabled) {
@@ -4780,7 +4859,7 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) {
 #if defined(_SAPP_EMSCRIPTEN)
 #if defined(_SAPP_EMSCRIPTEN)
 
 
 #if defined(EM_JS_DEPS)
 #if defined(EM_JS_DEPS)
-EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack");
+EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack,$findCanvasEventTarget");
 #endif
 #endif
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
@@ -4930,10 +5009,8 @@ _SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) {
     sapp_js_write_clipboard(str);
     sapp_js_write_clipboard(str);
 }
 }
 
 
-EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), {
+EM_JS(void, sapp_js_add_dragndrop_listeners, (void), {
     Module.sokol_drop_files = [];
     Module.sokol_drop_files = [];
-    const canvas_name = UTF8ToString(canvas_name_cstr);
-    const canvas = document.getElementById(canvas_name);
     Module.sokol_dragenter = (event) => {
     Module.sokol_dragenter = (event) => {
         event.stopPropagation();
         event.stopPropagation();
         event.preventDefault();
         event.preventDefault();
@@ -4966,6 +5043,8 @@ EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), {
         // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect
         // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect
         __sapp_emsc_end_drop(event.clientX, event.clientY, mods);
         __sapp_emsc_end_drop(event.clientX, event.clientY, mods);
     };
     };
+    \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
+    const canvas = Module.sapp_emsc_target;
     canvas.addEventListener('dragenter', Module.sokol_dragenter, false);
     canvas.addEventListener('dragenter', Module.sokol_dragenter, false);
     canvas.addEventListener('dragleave', Module.sokol_dragleave, false);
     canvas.addEventListener('dragleave', Module.sokol_dragleave, false);
     canvas.addEventListener('dragover',  Module.sokol_dragover, false);
     canvas.addEventListener('dragover',  Module.sokol_dragover, false);
@@ -5005,24 +5084,30 @@ EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback c
     reader.readAsArrayBuffer(files[index]);
     reader.readAsArrayBuffer(files[index]);
 });
 });
 
 
-EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), {
-    const canvas_name = UTF8ToString(canvas_name_cstr);
-    const canvas = document.getElementById(canvas_name);
+EM_JS(void, sapp_js_remove_dragndrop_listeners, (void), {
+    \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
+    const canvas = Module.sapp_emsc_target;
     canvas.removeEventListener('dragenter', Module.sokol_dragenter);
     canvas.removeEventListener('dragenter', Module.sokol_dragenter);
     canvas.removeEventListener('dragleave', Module.sokol_dragleave);
     canvas.removeEventListener('dragleave', Module.sokol_dragleave);
     canvas.removeEventListener('dragover',  Module.sokol_dragover);
     canvas.removeEventListener('dragover',  Module.sokol_dragover);
     canvas.removeEventListener('drop',      Module.sokol_drop);
     canvas.removeEventListener('drop',      Module.sokol_drop);
 });
 });
 
 
-EM_JS(void, sapp_js_init, (const char* c_str_target), {
-    // lookup and store canvas object by name
-    const target_str = UTF8ToString(c_str_target);
-    Module.sapp_emsc_target = document.getElementById(target_str);
+EM_JS(void, sapp_js_init, (const char* c_str_target_selector), {
+    const target_selector_str = UTF8ToString(c_str_target_selector);
+    if (Module['canvas'] !== undefined) {
+        if (typeof Module['canvas'] === 'object') {
+            specialHTMLTargets[target_selector_str] = Module['canvas'];
+        } else {
+            console.warn("sokol_app.h: Module['canvas'] is set but is not an object");
+        }
+    }
+    Module.sapp_emsc_target = findCanvasEventTarget(target_selector_str);
     if (!Module.sapp_emsc_target) {
     if (!Module.sapp_emsc_target) {
-        console.log("sokol_app.h: invalid target:" + target_str);
+        console.warn("sokol_app.h: can't find html5_canvas_selector ", target_selector_str);
     }
     }
     if (!Module.sapp_emsc_target.requestPointerLock) {
     if (!Module.sapp_emsc_target.requestPointerLock) {
-        console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str);
+        console.warn("sokol_app.h: target doesn't support requestPointerLock: ", target_selector_str);
     }
     }
 });
 });
 
 
@@ -5853,7 +5938,7 @@ _SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) {
         sapp_js_add_clipboard_listener();
         sapp_js_add_clipboard_listener();
     }
     }
     if (_sapp.drop.enabled) {
     if (_sapp.drop.enabled) {
-        sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
+        sapp_js_add_dragndrop_listeners();
     }
     }
     #if defined(SOKOL_GLES3)
     #if defined(SOKOL_GLES3)
         emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb);
         emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb);
@@ -5887,7 +5972,7 @@ _SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers(void) {
         sapp_js_remove_clipboard_listener();
         sapp_js_remove_clipboard_listener();
     }
     }
     if (_sapp.drop.enabled) {
     if (_sapp.drop.enabled) {
-        sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]);
+        sapp_js_remove_dragndrop_listeners();
     }
     }
     #if defined(SOKOL_GLES3)
     #if defined(SOKOL_GLES3)
         emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0);
         emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0);
@@ -5931,7 +6016,7 @@ _SOKOL_PRIVATE void _sapp_emsc_frame_main_loop(void) {
 
 
 _SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) {
 _SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) {
     _sapp_init_state(desc);
     _sapp_init_state(desc);
-    sapp_js_init(&_sapp.html5_canvas_selector[1]);
+    sapp_js_init(_sapp.html5_canvas_selector);
     double w, h;
     double w, h;
     if (_sapp.desc.html5_canvas_resize) {
     if (_sapp.desc.html5_canvas_resize) {
         w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH);
         w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH);