From 01b4d8d17106a12bcfeee4dfe30ed4fc3a25fc10 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 5 Nov 2020 03:17:39 +0100 Subject: [PATCH] Platform: get EmscriptenApplication canvas target from Module['canvas']. And properly account for -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR. Module['canvas'] can be read even from code compiled with -s MODULARIZE so it's a preferrable option to hardcoding it in Configuration. The target strings in Emscripten depend on whether we're compiled with DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR (see https://github.com/emscripten-core/emscripten/pull/7977). This is now detected and handled at runtime to prepend element IDs with and use the correct window and document magic targets. --- src/Magnum/Platform/EmscriptenApplication.cpp | 135 +++++++++++------- src/Magnum/Platform/EmscriptenApplication.h | 3 + 2 files changed, 87 insertions(+), 51 deletions(-) diff --git a/src/Magnum/Platform/EmscriptenApplication.cpp b/src/Magnum/Platform/EmscriptenApplication.cpp index 3a08e9bce..7c89a2e05 100644 --- a/src/Magnum/Platform/EmscriptenApplication.cpp +++ b/src/Magnum/Platform/EmscriptenApplication.cpp @@ -43,6 +43,13 @@ #include "Magnum/Platform/GLContext.h" #endif +/** @todo drop once we don't support < 1.38.27 anymore */ +#ifndef EMSCRIPTEN_EVENT_TARGET_DOCUMENT +#define EMSCRIPTEN_EVENT_TARGET_DOCUMENT reinterpret_cast(1) +#define EMSCRIPTEN_EVENT_TARGET_WINDOW reinterpret_cast(2) +#define EMSCRIPTEN_EVENT_TARGET_SCREEN reinterpret_cast(3) +#endif + namespace Magnum { namespace Platform { namespace { @@ -174,6 +181,36 @@ namespace { return Key::Unknown; } + + std::string canvasId() { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + char* id = reinterpret_cast(EM_ASM_INT({ + return allocate(intArrayFromString(Module['canvas'].id), 'i8', ALLOC_NORMAL); + })); + #pragma GCC diagnostic pop + std::string str = id; + std::free(id); + return str; + } + + bool checkForDeprecatedEmscriptenTargetBehavior() { + /* Emscripten 1.38.27 changed to generic CSS selectors from element IDs + depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being + set. + https://github.com/emscripten-core/emscripten/pull/7977 + There is no simple way to check for compiler options so check + whether the new CSS selectors are being used. If so, it should find + canvas#[id] which is any canvas with the ID of Module.canvas. + The old target behavior will look for an element with id="canvas#[id]" + which could theoretically exist but that's highly unlikely. */ + bool deprecated = true; + Vector2d tempSize; + if(emscripten_get_element_css_size(("canvas#" + canvasId()).data(), &tempSize.x(), &tempSize.y()) >= 0) { + deprecated = false; + } + return deprecated; + } } EmscriptenApplication::EmscriptenApplication(const Arguments& arguments): EmscriptenApplication{arguments, Configuration{}} {} @@ -266,10 +303,19 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration) { } #endif + std::ostream* verbose = _verboseLog ? Debug::output() : nullptr; + + _deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior(); + if(_deprecatedTargetBehavior) { + Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior"; + } + + _canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId(); + _dpiScaling = dpiScaling(configuration); if(!configuration.size().isZero()) { const Vector2i scaledCanvasSize = configuration.size()*_dpiScaling; - emscripten_set_canvas_element_size("#canvas", scaledCanvasSize.x(), scaledCanvasSize.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y()); } setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable)); @@ -327,6 +373,17 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const _devicePixelRatio = Vector2{Float(emscripten_get_device_pixel_ratio())}; Debug{verbose} << "Platform::EmscriptenApplication: device pixel ratio" << _devicePixelRatio.x(); + /* Find out which element target strings Emscripten expects. This depends on + the DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR compiler option. */ + _deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior(); + if(_deprecatedTargetBehavior) { + Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior"; + } + + /* Get the canvas ID from Module.canvas, either set by EmscriptenApplication.js + or overridden/manually set by the user. */ + _canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId(); + /* Get CSS canvas size and cache it. This is used later to detect canvas resizes in emscripten_set_resize_callback() and fire viewport events, because browsers are only required to fire resize events on the window @@ -346,10 +403,10 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const } _dpiScaling = dpiScaling(configuration); const Vector2i scaledCanvasSize = canvasSize*_dpiScaling*_devicePixelRatio; - emscripten_set_canvas_element_size("#canvas", scaledCanvasSize.x(), scaledCanvasSize.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y()); /* Create WebGL context */ - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context("#canvas", &attrs); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(_canvasTarget.data(), &attrs); if(!context) { /* When context creation fails, `context` is a negative integer matching EMSCRIPTEN_RESULT_* defines */ @@ -371,23 +428,14 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const Vector2i EmscriptenApplication::windowSize() const { Vector2d size; - /* Emscripten 1.38.27 changed to generic CSS selectors from element IDs - depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being - set (which we can't detect at compile time). Fortunately, using #canvas - works the same way both in the previous versions and the current one. - Unfortunately, this is also the only value that works the same way for - both. Further details at - https://github.com/emscripten-core/emscripten/pull/7977 */ - /** @todo don't hardcode "#canvas" everywhere, make it configurable from outside */ - emscripten_get_element_css_size("#canvas", &size.x(), &size.y()); + emscripten_get_element_css_size(_canvasTarget.data(), &size.x(), &size.y()); return Vector2i{Math::round(size)}; } #ifdef MAGNUM_TARGET_GL Vector2i EmscriptenApplication::framebufferSize() const { Vector2i size; - /* See above why hardcoded */ - emscripten_get_canvas_element_size("#canvas", &size.x(), &size.y()); + emscripten_get_canvas_element_size(_canvasTarget.data(), &size.x(), &size.y()); return size; } #endif @@ -423,12 +471,11 @@ void EmscriptenApplication::swapBuffers() { /* Called from window resize event but also explicitly from setContainerCssClass() */ void EmscriptenApplication::handleCanvasResize(const EmscriptenUiEvent* event) { - /* See windowSize() for why we hardcode "#canvas" here */ const Vector2i canvasSize{windowSize()}; if(canvasSize != _lastKnownCanvasSize) { _lastKnownCanvasSize = canvasSize; const Vector2i size = canvasSize*_dpiScaling*_devicePixelRatio; - emscripten_set_canvas_element_size("#canvas", size.x(), size.y()); + emscripten_set_canvas_element_size(_canvasTarget.data(), size.x(), size.y()); ViewportEvent e{event, canvasSize, #ifdef MAGNUM_TARGET_GL framebufferSize(), @@ -454,11 +501,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { changes. Better than polling for this change in every frame like Sdl2Application does, but still not ideal. */ if(resizable) { - #ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW - const char* target = EMSCRIPTEN_EVENT_TARGET_WINDOW; - #else - const char* target = "#window"; - #endif + const char* target = _deprecatedTargetBehavior ? "#window" : EMSCRIPTEN_EVENT_TARGET_WINDOW; auto cb = [](int, const EmscriptenUiEvent* event, void* userData) -> Int { static_cast(userData)->handleCanvasResize(event); return false; /** @todo what does ignoring a resize event mean? */ @@ -466,23 +509,21 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { emscripten_set_resize_callback(target, this, false, cb); } - /* See windowSize() for why we hardcode "#canvas" here */ - - emscripten_set_mousedown_callback("#canvas", this, false, + emscripten_set_mousedown_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { MouseEvent e{*event}; static_cast(userData)->mousePressEvent(e); return e.isAccepted(); })); - emscripten_set_mouseup_callback("#canvas", this, false, + emscripten_set_mouseup_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { MouseEvent e{*event}; static_cast(userData)->mouseReleaseEvent(e); return e.isAccepted(); })); - emscripten_set_mousemove_callback("#canvas", this, false, + emscripten_set_mousemove_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { auto& app = *static_cast(userData); /* With DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR, canvasX/Y is @@ -499,7 +540,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { return e.isAccepted(); })); - emscripten_set_wheel_callback("#canvas", this, false, + emscripten_set_wheel_callback(_canvasTarget.data(), this, false, ([](int, const EmscriptenWheelEvent* event, void* userData) -> Int { MouseScrollEvent e{*event}; static_cast(userData)->mouseScrollEvent(e); @@ -516,31 +557,31 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { 1.38.27 depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 but we don't want to force this flag on the users so the behavior handles both. */ - #ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT - char* const keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ + const char* keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ var element = Module['keyboardListeningElement'] || document; - if(element === document) return 1; - if(element === window) return 2; + if(element === document) return 1; /* EMSCRIPTEN_EVENT_TARGET_DOCUMENT */ + if(element === window) return 2; /* EMSCRIPTEN_EVENT_TARGET_WINDOW */ if('id' in element) return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL); return 0; })); - #else - char* const keyboardListeningElement = reinterpret_cast(EM_ASM_INT({ - var element = Module['keyboardListeningElement'] || document; - - if(element === document) element = {id: '#document'}; - if(element === window) element = {id: '#window'}; - if('id' in element) - return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL); - - return 0; - })); - #endif #pragma GCC diagnostic pop + std::string keyboardListeningElementString; + if(keyboardListeningElement == EMSCRIPTEN_EVENT_TARGET_DOCUMENT) { + keyboardListeningElement = _deprecatedTargetBehavior ? "#document" : keyboardListeningElement; + } else if(keyboardListeningElement == EMSCRIPTEN_EVENT_TARGET_WINDOW) { + keyboardListeningElement = _deprecatedTargetBehavior ? "#window" : keyboardListeningElement; + } else if(keyboardListeningElement) { + if(!_deprecatedTargetBehavior) + keyboardListeningElementString = "#"; + keyboardListeningElementString += keyboardListeningElement; + std::free(const_cast(keyboardListeningElement)); + keyboardListeningElement = keyboardListeningElementString.data(); + } + /* Happens only if keyboardListeningElement was set, but did not have an `id` attribute. Instead it should be either null or undefined, a DOM element, `window` or `document`. */ @@ -571,14 +612,6 @@ void EmscriptenApplication::setupCallbacks(bool resizable) { static_cast(userData)->keyReleaseEvent(e); return e.isAccepted(); })); - - #ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT - if(keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_DOCUMENT && - keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_WINDOW) - #endif - { - std::free(keyboardListeningElement); - } } void EmscriptenApplication::setupAnimationFrame(bool forceAnimationFrame) { diff --git a/src/Magnum/Platform/EmscriptenApplication.h b/src/Magnum/Platform/EmscriptenApplication.h index 1216d686e..f75770e03 100644 --- a/src/Magnum/Platform/EmscriptenApplication.h +++ b/src/Magnum/Platform/EmscriptenApplication.h @@ -896,6 +896,9 @@ class EmscriptenApplication { Flags _flags; Cursor _cursor; + bool _deprecatedTargetBehavior{}; + std::string _canvasTarget; + #ifdef MAGNUM_TARGET_GL EMSCRIPTEN_WEBGL_CONTEXT_HANDLE _glContext{}; Containers::Pointer _context;