| // 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 "surface.h" |
| |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" |
| #include "third_party/skia/include/gpu/GrDirectContext.h" |
| #include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" |
| |
| using namespace Skwasm; |
| |
| Surface::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<Surface*>(context)->_runWorker(); |
| return nullptr; |
| }, |
| this); |
| } |
| |
| // Main thread only |
| void Surface::dispose() { |
| assert(emscripten_is_main_browser_thread()); |
| emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VI, |
| reinterpret_cast<void*>(fDispose), nullptr, |
| this); |
| } |
| |
| // Main thread only |
| void Surface::setCanvasSize(int width, int height) { |
| assert(emscripten_is_main_browser_thread()); |
| emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIII, |
| reinterpret_cast<void*>(fSetCanvasSize), |
| nullptr, this, width, height); |
| } |
| |
| // Main thread only |
| uint32_t Surface::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<void*>(fRenderPicture), |
| nullptr, this, picture); |
| |
| // After drawing to the surface, the browser implicitly flushes 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<void*>(fNotifyRenderComplete), |
| nullptr, this, callbackId); |
| return callbackId; |
| } |
| |
| // Main thread only |
| uint32_t Surface::rasterizeImage(SkImage* image, ImageByteFormat format) { |
| assert(emscripten_is_main_browser_thread()); |
| uint32_t callbackId = ++_currentCallbackId; |
| image->ref(); |
| |
| emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIIII, |
| reinterpret_cast<void*>(fRasterizeImage), |
| nullptr, this, image, format, callbackId); |
| return callbackId; |
| } |
| |
| void Surface::disposeVideoFrame(SkwasmObjectId videoFrameId) { |
| emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII, |
| reinterpret_cast<void*>(fDisposeVideoFrame), |
| nullptr, this, videoFrameId); |
| } |
| |
| // Main thread only |
| void Surface::setCallbackHandler(CallbackHandler* callbackHandler) { |
| assert(emscripten_is_main_browser_thread()); |
| _callbackHandler = callbackHandler; |
| } |
| |
| // Worker thread only |
| void Surface::_runWorker() { |
| _init(); |
| emscripten_unwind_to_js_event_loop(); |
| } |
| |
| // Worker thread only |
| void Surface::_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 Surface::_dispose() { |
| delete this; |
| } |
| |
| // Worker thread only |
| void Surface::_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 Surface::_recreateSurface() { |
| makeCurrent(_glContext); |
| auto target = GrBackendRenderTargets::MakeGL(_canvasWidth, _canvasHeight, |
| _sampleCount, _stencil, _fbInfo); |
| _surface = SkSurfaces::WrapBackendRenderTarget( |
| _grContext.get(), target, kBottomLeft_GrSurfaceOrigin, |
| kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), nullptr); |
| } |
| |
| // Worker thread only |
| void Surface::_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); |
| _grContext->flush(_surface); |
| } |
| |
| void Surface::_rasterizeImage(SkImage* image, |
| ImageByteFormat format, |
| uint32_t callbackId) { |
| sk_sp<SkData> 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<uint8_t*>(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 Surface::_disposeVideoFrame(SkwasmObjectId objectId) { |
| skwasm_disposeVideoFrame(objectId); |
| } |
| |
| void Surface::_onRasterizeComplete(SkData* data, uint32_t callbackId) { |
| _callbackHandler(callbackId, data); |
| } |
| |
| // Worker thread only |
| void Surface::_notifyRenderComplete(uint32_t callbackId) { |
| emscripten_sync_run_in_main_runtime_thread(EM_FUNC_SIG_VII, fOnRenderComplete, |
| this, callbackId); |
| } |
| |
| // Main thread only |
| void Surface::_onRenderComplete(uint32_t callbackId) { |
| assert(emscripten_is_main_browser_thread()); |
| _callbackHandler(callbackId, nullptr); |
| } |
| |
| void Surface::fDispose(Surface* surface) { |
| surface->_dispose(); |
| } |
| |
| void Surface::fSetCanvasSize(Surface* surface, int width, int height) { |
| surface->_setCanvasSize(width, height); |
| } |
| |
| void Surface::fRenderPicture(Surface* surface, SkPicture* picture) { |
| surface->_renderPicture(picture); |
| picture->unref(); |
| } |
| |
| void Surface::fNotifyRenderComplete(Surface* surface, uint32_t callbackId) { |
| surface->_notifyRenderComplete(callbackId); |
| } |
| |
| void Surface::fOnRenderComplete(Surface* surface, uint32_t callbackId) { |
| surface->_onRenderComplete(callbackId); |
| } |
| |
| void Surface::fOnRasterizeComplete(Surface* surface, |
| SkData* imageData, |
| uint32_t callbackId) { |
| surface->_onRasterizeComplete(imageData, callbackId); |
| } |
| |
| void Surface::fRasterizeImage(Surface* surface, |
| SkImage* image, |
| ImageByteFormat format, |
| uint32_t callbackId) { |
| surface->_rasterizeImage(image, format, callbackId); |
| image->unref(); |
| } |
| |
| void Surface::fDisposeVideoFrame(Surface* surface, |
| SkwasmObjectId videoFrameId) { |
| surface->_disposeVideoFrame(videoFrameId); |
| } |
| |
| SKWASM_EXPORT Surface* surface_createFromCanvas(const char* canvasID) { |
| return new Surface(canvasID); |
| } |
| |
| SKWASM_EXPORT unsigned long surface_getThreadId(Surface* surface) { |
| return surface->getThreadId(); |
| } |
| |
| 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); |
| } |