Skip to content

Commit

Permalink
Platform: get EmscriptenApplication canvas target from Module['canvas'].
Browse files Browse the repository at this point in the history
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
emscripten-core/emscripten#7977). This is now
detected and handled at runtime to prepend element IDs with and use
the correct window and document magic targets.
  • Loading branch information
pezcode authored and acui committed Feb 4, 2021
1 parent 6c2607f commit 36323f2
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 51 deletions.
135 changes: 84 additions & 51 deletions src/Magnum/Platform/EmscriptenApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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<const char*>(1)
#define EMSCRIPTEN_EVENT_TARGET_WINDOW reinterpret_cast<const char*>(2)
#define EMSCRIPTEN_EVENT_TARGET_SCREEN reinterpret_cast<const char*>(3)
#endif

namespace Magnum { namespace Platform {

namespace {
Expand Down Expand Up @@ -171,6 +178,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<char*>(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{}} {}
Expand Down Expand Up @@ -263,10 +300,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));
Expand Down Expand Up @@ -324,6 +370,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
Expand All @@ -343,10 +400,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 */
Expand All @@ -368,23 +425,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
Expand Down Expand Up @@ -420,12 +468,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(),
Expand All @@ -451,35 +498,29 @@ 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<EmscriptenApplication*>(userData)->handleCanvasResize(event);
return false; /** @todo what does ignoring a resize event mean? */
};
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<EmscriptenApplication*>(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<EmscriptenApplication*>(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<EmscriptenApplication*>(userData);
/* With DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR, canvasX/Y is
Expand All @@ -496,7 +537,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<EmscriptenApplication*>(userData)->mouseScrollEvent(e);
Expand All @@ -513,31 +554,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<char*>(EM_ASM_INT({
const char* keyboardListeningElement = reinterpret_cast<const char*>(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<char*>(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<char*>(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`. */
Expand Down Expand Up @@ -568,14 +609,6 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
static_cast<EmscriptenApplication*>(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) {
Expand Down
3 changes: 3 additions & 0 deletions src/Magnum/Platform/EmscriptenApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Platform::GLContext> _context;
Expand Down

0 comments on commit 36323f2

Please sign in to comment.