// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include "export.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/encode/SkPngEncoder.h" #include "third_party/skia/include/gpu/GrDirectContext.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" #include "third_party/skia/include/gpu/gl/GrGLInterface.h" #include "third_party/skia/include/gpu/gl/GrGLTypes.h" #include "wrappers.h" using namespace Skwasm; namespace { // This must be kept in sync with the `ImageByteFormat` enum in dart:ui. enum class ImageByteFormat { rawRgba, rawStraightRgba, rawUnmodified, png, }; class Surface; void fDispose(Surface* surface); void fSetCanvasSize(Surface* surface, int width, int height); void fRenderPicture(Surface* surface, SkPicture* picture); void fNotifyRenderComplete(Surface* surface, uint32_t callbackId); void fOnRenderComplete(Surface* surface, uint32_t callbackId); void fRasterizeImage(Surface* surface, SkImage* image, ImageByteFormat format, uint32_t callbackId); void fOnRasterizeComplete(Surface* surface, SkData* imageData, uint32_t callbackId); class Surface { public: using CallbackHandler = void(uint32_t, void*); // Main thread only Surface(const char* canvasID) : _canvasID(canvasID) { assert(emscripten_is_main_browser_thread()); pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); emscripten_pthread_attr_settransferredcanvases(&attr, _canvasID.c_str()); pthread_create( &_thread, &attr, [](void* context) -> void* { static_cast(context)->_runWorker(); return nullptr; }, this); } // Main thread only void dispose() { assert(emscripten_is_main_browser_thread()); emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VI, reinterpret_cast(fDispose), nullptr, this); } // Main thread only void setCanvasSize(int width, int height) { assert(emscripten_is_main_browser_thread()); emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIII, reinterpret_cast(fSetCanvasSize), nullptr, this, width, height); } // Main thread only uint32_t renderPicture(SkPicture* picture) { assert(emscripten_is_main_browser_thread()); uint32_t callbackId = ++_currentCallbackId; picture->ref(); emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII, reinterpret_cast(fRenderPicture), nullptr, this, picture); // After drawing to the surface, the browser implicitly flushed the drawing // commands at the end of the event loop. As a result, in order to make // sure we call back after the rendering has actually occurred, we issue // the callback in a subsequent event, after the flushing has happened. emscripten_dispatch_to_thread( _thread, EM_FUNC_SIG_VII, reinterpret_cast(fNotifyRenderComplete), nullptr, this, callbackId); return callbackId; } uint32_t rasterizeImage(SkImage* image, ImageByteFormat format) { uint32_t callbackId = ++_currentCallbackId; image->ref(); emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIIII, reinterpret_cast(fRasterizeImage), nullptr, this, image, format, callbackId); return callbackId; } // Main thread only void setCallbackHandler(CallbackHandler* callbackHandler) { assert(emscripten_is_main_browser_thread()); _callbackHandler = callbackHandler; } private: // Worker thread only void _runWorker() { _init(); emscripten_unwind_to_js_event_loop(); } // Worker thread only void _init() { EmscriptenWebGLContextAttributes attributes; emscripten_webgl_init_context_attributes(&attributes); attributes.alpha = true; attributes.depth = true; attributes.stencil = true; attributes.antialias = false; attributes.premultipliedAlpha = true; attributes.preserveDrawingBuffer = 0; attributes.powerPreference = EM_WEBGL_POWER_PREFERENCE_DEFAULT; attributes.failIfMajorPerformanceCaveat = false; attributes.enableExtensionsByDefault = true; attributes.explicitSwapControl = false; attributes.renderViaOffscreenBackBuffer = true; attributes.majorVersion = 2; _glContext = emscripten_webgl_create_context(_canvasID.c_str(), &attributes); if (!_glContext) { printf("Failed to create context!\n"); return; } makeCurrent(_glContext); _grContext = GrDirectContext::MakeGL(GrGLMakeNativeInterface()); // WebGL should already be clearing the color and stencil buffers, but do it // again here to ensure Skia receives them in the expected state. emscripten_glBindFramebuffer(GL_FRAMEBUFFER, 0); emscripten_glClearColor(0, 0, 0, 0); emscripten_glClearStencil(0); emscripten_glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); _grContext->resetContext(kRenderTarget_GrGLBackendState | kMisc_GrGLBackendState); // The on-screen canvas is FBO 0. Wrap it in a Skia render target so Skia // can render to it. _fbInfo.fFBOID = 0; _fbInfo.fFormat = GL_RGBA8_OES; emscripten_glGetIntegerv(GL_SAMPLES, &_sampleCount); emscripten_glGetIntegerv(GL_STENCIL_BITS, &_stencil); } // Worker thread only void _dispose() { delete this; } // Worker thread only void _setCanvasSize(int width, int height) { if (_canvasWidth != width || _canvasHeight != height) { emscripten_set_canvas_element_size(_canvasID.c_str(), width, height); _canvasWidth = width; _canvasHeight = height; _recreateSurface(); } } // Worker thread only void _recreateSurface() { makeCurrent(_glContext); GrBackendRenderTarget target(_canvasWidth, _canvasHeight, _sampleCount, _stencil, _fbInfo); _surface = SkSurfaces::WrapBackendRenderTarget( _grContext.get(), target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), nullptr); } // Worker thread only void _renderPicture(const SkPicture* picture) { if (!_surface) { printf("Can't render picture with no surface.\n"); return; } makeCurrent(_glContext); auto canvas = _surface->getCanvas(); canvas->drawPicture(picture); _surface->flush(); } void _rasterizeImage(SkImage* image, ImageByteFormat format, uint32_t callbackId) { sk_sp data; if (format == ImageByteFormat::png) { data = SkPngEncoder::Encode(_grContext.get(), image, {}); } else { SkAlphaType alphaType = format == ImageByteFormat::rawStraightRgba ? SkAlphaType::kUnpremul_SkAlphaType : SkAlphaType::kPremul_SkAlphaType; SkImageInfo info = SkImageInfo::Make(image->width(), image->height(), SkColorType::kRGBA_8888_SkColorType, alphaType, SkColorSpace::MakeSRGB()); size_t bytesPerRow = 4 * image->width(); size_t byteSize = info.computeByteSize(bytesPerRow); data = SkData::MakeUninitialized(byteSize); uint8_t* pixels = reinterpret_cast(data->writable_data()); bool success = image->readPixels(_grContext.get(), image->imageInfo(), pixels, bytesPerRow, 0, 0); if (!success) { printf("Failed to read pixels from image!\n"); data = nullptr; } } emscripten_sync_run_in_main_runtime_thread(EM_FUNC_SIG_VIII, fOnRasterizeComplete, this, data.release(), callbackId); } void _onRasterizeComplete(SkData* data, uint32_t callbackId) { _callbackHandler(callbackId, data); } // Worker thread only void _notifyRenderComplete(uint32_t callbackId) { emscripten_sync_run_in_main_runtime_thread( EM_FUNC_SIG_VII, fOnRenderComplete, this, callbackId); } // Main thread only void _onRenderComplete(uint32_t callbackId) { assert(emscripten_is_main_browser_thread()); _callbackHandler(callbackId, nullptr); } std::string _canvasID; CallbackHandler* _callbackHandler = nullptr; uint32_t _currentCallbackId = 0; int _canvasWidth = 0; int _canvasHeight = 0; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE _glContext = 0; sk_sp _grContext = nullptr; sk_sp _surface = nullptr; GrGLFramebufferInfo _fbInfo; GrGLint _sampleCount; GrGLint _stencil; pthread_t _thread; friend void fDispose(Surface* surface); friend void fSetCanvasSize(Surface* surface, int width, int height); friend void fRenderPicture(Surface* surface, SkPicture* picture); friend void fNotifyRenderComplete(Surface* surface, uint32_t callbackId); friend void fOnRenderComplete(Surface* surface, uint32_t callbackId); friend void fRasterizeImage(Surface* surface, SkImage* image, ImageByteFormat format, uint32_t callbackId); friend void fOnRasterizeComplete(Surface* surface, SkData* imageData, uint32_t callbackId); }; void fDispose(Surface* surface) { surface->_dispose(); } void fSetCanvasSize(Surface* surface, int width, int height) { surface->_setCanvasSize(width, height); } void fRenderPicture(Surface* surface, SkPicture* picture) { surface->_renderPicture(picture); picture->unref(); } void fNotifyRenderComplete(Surface* surface, uint32_t callbackId) { surface->_notifyRenderComplete(callbackId); } void fOnRenderComplete(Surface* surface, uint32_t callbackId) { surface->_onRenderComplete(callbackId); } void fOnRasterizeComplete(Surface* surface, SkData* imageData, uint32_t callbackId) { surface->_onRasterizeComplete(imageData, callbackId); } void fRasterizeImage(Surface* surface, SkImage* image, ImageByteFormat format, uint32_t callbackId) { surface->_rasterizeImage(image, format, callbackId); image->unref(); } } // namespace SKWASM_EXPORT Surface* surface_createFromCanvas(const char* canvasID) { return new Surface(canvasID); } SKWASM_EXPORT void surface_setCallbackHandler( Surface* surface, Surface::CallbackHandler* callbackHandler) { surface->setCallbackHandler(callbackHandler); } SKWASM_EXPORT void surface_destroy(Surface* surface) { surface->dispose(); } SKWASM_EXPORT void surface_setCanvasSize(Surface* surface, int width, int height) { surface->setCanvasSize(width, height); } SKWASM_EXPORT uint32_t surface_renderPicture(Surface* surface, SkPicture* picture) { return surface->renderPicture(picture); } SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface, SkImage* image, ImageByteFormat format) { return surface->rasterizeImage(image, format); }