// Copyright 2019 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "absl/memory/memory.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status_builder.h" #include "mediapipe/gpu/gl_context.h" #include "mediapipe/gpu/gl_context_internal.h" #if defined(__EMSCRIPTEN__) #include namespace mediapipe { // TODO: Handle webGL "context lost" and "context restored" events. GlContext::StatusOrGlContext GlContext::Create(std::nullptr_t nullp, bool create_thread) { return Create(0, create_thread); } GlContext::StatusOrGlContext GlContext::Create(const GlContext& share_context, bool create_thread) { return Create(share_context.context_, create_thread); } GlContext::StatusOrGlContext GlContext::Create( EMSCRIPTEN_WEBGL_CONTEXT_HANDLE share_context, bool create_thread) { std::shared_ptr context(new GlContext()); MP_RETURN_IF_ERROR(context->CreateContext(share_context)); MP_RETURN_IF_ERROR(context->FinishInitialization(create_thread)); return std::move(context); } absl::Status GlContext::CreateContextInternal( EMSCRIPTEN_WEBGL_CONTEXT_HANDLE external_context, int webgl_version) { CHECK(webgl_version == 1 || webgl_version == 2); EmscriptenWebGLContextAttributes attrs; emscripten_webgl_init_context_attributes(&attrs); attrs.explicitSwapControl = 0; attrs.depth = 1; attrs.stencil = 0; attrs.antialias = 0; attrs.majorVersion = webgl_version; attrs.minorVersion = 0; // This flag tells the page compositor that the image written to the canvas // uses premultiplied alpha, and so can be used directly for compositing. // Without this, it needs to make an additional full-canvas rendering pass. attrs.premultipliedAlpha = 1; // TODO: Investigate this option in more detail, esp. on Safari. attrs.preserveDrawingBuffer = 0; // Since the Emscripten canvas target finding function is visible from here, // we hijack findCanvasEventTarget directly for enforcing old Module.canvas // behavior if the user desires, falling back to the new DOM element CSS // selector behavior next if that is specified, and finally just allowing the // lookup to proceed on a null target. // TODO: Ensure this works with all options (in particular, // multithreading options, like the special-case combination of USE_PTHREADS // and OFFSCREEN_FRAMEBUFFER) // clang-format off EM_ASM( let init_once = true; if (init_once) { const cachedFindCanvasEventTarget = findCanvasEventTarget; if (typeof cachedFindCanvasEventTarget !== 'function') { if (typeof console !== 'undefined') { console.error('Expected Emscripten global function ' + '"findCanvasEventTarget" not found. WebGL context creation ' + 'may fail.'); } return; } findCanvasEventTarget = function(target) { if (target == 0) { if (Module && Module.canvas) { return Module.canvas; } else if (Module && Module.canvasCssSelector) { return cachedFindCanvasEventTarget(Module.canvasCssSelector); } if (typeof console !== 'undefined') { console.warn('Module properties canvas and canvasCssSelector not ' + 'found during WebGL context creation.'); } } // We still go through with the find attempt, although for most use // cases it will not succeed, just in case the user does want to fall- // back. return cachedFindCanvasEventTarget(target); }; // NOLINT: Necessary semicolon. init_once = false; } ); // clang-format on EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_handle = emscripten_webgl_create_context(nullptr, &attrs); // Check for failure if (context_handle <= 0) { LOG(INFO) << "Couldn't create webGL " << webgl_version << " context."; return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) << "emscripten_webgl_create_context() returned error " << context_handle; } else { emscripten_webgl_get_context_attributes(context_handle, &attrs); webgl_version = attrs.majorVersion; } context_ = context_handle; attrs_ = attrs; // We can't always rely on GL_MAJOR_VERSION and GL_MINOR_VERSION, since // GLES 2 does not have them, so let's set the major version here at least. // WebGL 1.0 maps to GLES 2.0 and WebGL 2.0 maps to GLES 3.0, so we add 1. gl_major_version_ = webgl_version + 1; return absl::OkStatus(); } absl::Status GlContext::CreateContext( EMSCRIPTEN_WEBGL_CONTEXT_HANDLE external_context) { // TODO: If we're given a non-0 external_context, could try to use // that directly, since we're assuming a single-threaded single-context // environment anyways now. auto status = CreateContextInternal(external_context, 2); if (!status.ok()) { LOG(WARNING) << "Creating a context with WebGL 2 failed: " << status; LOG(WARNING) << "Fall back on WebGL 1."; status = CreateContextInternal(external_context, 1); } MP_RETURN_IF_ERROR(status); LOG(INFO) << "Successfully created a WebGL context with major version " << gl_major_version_ << " and handle " << context_; return absl::OkStatus(); } void GlContext::DestroyContext() { if (thread_) { // For now, we force web MediaPipe to be single-threaded, so error here. LOG(ERROR) << "thread_ should not exist in DestroyContext() on web."; } // Destroy the context and surface. if (context_ != 0) { EMSCRIPTEN_RESULT res = emscripten_webgl_destroy_context(context_); if (res != EMSCRIPTEN_RESULT_SUCCESS) { LOG(ERROR) << "emscripten_webgl_destroy_context() returned error " << res; } else { LOG(INFO) << "Successfully destroyed WebGL context with handle " << context_; } context_ = 0; } } GlContext::ContextBinding GlContext::ThisContextBindingPlatform() { GlContext::ContextBinding result; result.context = context_; return result; } void GlContext::GetCurrentContextBinding(GlContext::ContextBinding* binding) { binding->context = emscripten_webgl_get_current_context(); } absl::Status GlContext::SetCurrentContextBinding( const ContextBinding& new_binding) { if (new_binding.context == 0) { // Calling emscripten_webgl_make_context_current(0) is resulting in an error // so don't remove context for now, only replace! In the future, can // perhaps create a separate "do-nothing" context for this. return absl::OkStatus(); } // TODO: See if setting the same context to current multiple times // comes with a performance cost, and fix if so. EMSCRIPTEN_RESULT res = emscripten_webgl_make_context_current(new_binding.context); RET_CHECK(res == EMSCRIPTEN_RESULT_SUCCESS) << "emscripten_webgl_make_context_current() returned error " << res; return absl::OkStatus(); } bool GlContext::HasContext() const { return context_ != 0; } bool GlContext::IsCurrent() const { return HasContext() && (emscripten_webgl_get_current_context() == context_); } } // namespace mediapipe #endif // defined(__EMSCRIPTEN__)