| // 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 <algorithm> | 
 |  | 
 | #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" | 
 | #include "third_party/skia/include/gpu/ganesh/gl/GrGLDirectContext.h" | 
 |  | 
 | using namespace Skwasm; | 
 |  | 
 | Surface::Surface() { | 
 |   assert(emscripten_is_main_browser_thread()); | 
 |  | 
 |   pthread_attr_t attr; | 
 |   pthread_attr_init(&attr); | 
 |   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); | 
 |  | 
 |   pthread_create( | 
 |       &_thread, &attr, | 
 |       [](void* context) -> void* { | 
 |         static_cast<Surface*>(context)->_runWorker(); | 
 |         return nullptr; | 
 |       }, | 
 |       this); | 
 |   // Listen to messages from the worker | 
 |   skwasm_registerMessageListener(_thread); | 
 |  | 
 |   // Synchronize the time origin for the worker thread | 
 |   skwasm_syncTimeOriginForThread(_thread); | 
 | } | 
 |  | 
 | // 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 | 
 | uint32_t Surface::renderPictures(SkPicture** pictures, int count) { | 
 |   assert(emscripten_is_main_browser_thread()); | 
 |   uint32_t callbackId = ++_currentCallbackId; | 
 |   std::unique_ptr<sk_sp<SkPicture>[]> picturePointers = | 
 |       std::make_unique<sk_sp<SkPicture>[]>(count); | 
 |   for (int i = 0; i < count; i++) { | 
 |     picturePointers[i] = sk_ref_sp(pictures[i]); | 
 |   } | 
 |  | 
 |   // Releasing picturePointers here and will recreate the unique_ptr on the | 
 |   // other thread See surface_renderPicturesOnWorker | 
 |   skwasm_dispatchRenderPictures(_thread, this, picturePointers.release(), count, | 
 |                                 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; | 
 | } | 
 |  | 
 | std::unique_ptr<TextureSourceWrapper> Surface::createTextureSourceWrapper( | 
 |     SkwasmObject textureSource) { | 
 |   return std::unique_ptr<TextureSourceWrapper>( | 
 |       new TextureSourceWrapper(_thread, textureSource)); | 
 | } | 
 |  | 
 | // Main thread only | 
 | void Surface::setCallbackHandler(CallbackHandler* callbackHandler) { | 
 |   assert(emscripten_is_main_browser_thread()); | 
 |   _callbackHandler = callbackHandler; | 
 | } | 
 |  | 
 | // Worker thread only | 
 | void Surface::_runWorker() { | 
 |   _init(); | 
 |   emscripten_exit_with_live_runtime(); | 
 | } | 
 |  | 
 | // Worker thread only | 
 | void Surface::_init() { | 
 |   // Listen to messages from the main thread | 
 |   skwasm_registerMessageListener(0); | 
 |   _glContext = skwasm_createOffscreenCanvas(256, 256); | 
 |   if (!_glContext) { | 
 |     printf("Failed to create context!\n"); | 
 |     return; | 
 |   } | 
 |  | 
 |   makeCurrent(_glContext); | 
 |   emscripten_webgl_enable_extension(_glContext, "WEBGL_debug_renderer_info"); | 
 |  | 
 |   _grContext = GrDirectContexts::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::_resizeCanvasToFit(int width, int height) { | 
 |   if (!_surface || width > _canvasWidth || height > _canvasHeight) { | 
 |     _canvasWidth = std::max(width, _canvasWidth); | 
 |     _canvasHeight = std::max(height, _canvasHeight); | 
 |     _recreateSurface(); | 
 |   } | 
 | } | 
 |  | 
 | // Worker thread only | 
 | void Surface::_recreateSurface() { | 
 |   makeCurrent(_glContext); | 
 |   skwasm_resizeCanvas(_glContext, _canvasWidth, _canvasHeight); | 
 |   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::renderPicturesOnWorker(sk_sp<SkPicture>* pictures, | 
 |                                      int pictureCount, | 
 |                                      uint32_t callbackId, | 
 |                                      double rasterStart) { | 
 |   // This is populated by the `captureImageBitmap` call the first time it is | 
 |   // passed in. | 
 |   SkwasmObject imagePromiseArray = __builtin_wasm_ref_null_extern(); | 
 |   for (int i = 0; i < pictureCount; i++) { | 
 |     sk_sp<SkPicture> picture = pictures[i]; | 
 |     SkRect pictureRect = picture->cullRect(); | 
 |     SkIRect roundedOutRect; | 
 |     pictureRect.roundOut(&roundedOutRect); | 
 |     _resizeCanvasToFit(roundedOutRect.width(), roundedOutRect.height()); | 
 |     SkMatrix matrix = | 
 |         SkMatrix::Translate(-roundedOutRect.fLeft, -roundedOutRect.fTop); | 
 |     makeCurrent(_glContext); | 
 |     auto canvas = _surface->getCanvas(); | 
 |     canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); | 
 |     canvas->drawPicture(picture, &matrix, nullptr); | 
 |     _grContext->flush(_surface.get()); | 
 |     imagePromiseArray = | 
 |         skwasm_captureImageBitmap(_glContext, roundedOutRect.width(), | 
 |                                   roundedOutRect.height(), imagePromiseArray); | 
 |   } | 
 |   skwasm_resolveAndPostImages(this, imagePromiseArray, rasterStart, callbackId); | 
 | } | 
 |  | 
 | void Surface::_rasterizeImage(SkImage* image, | 
 |                               ImageByteFormat format, | 
 |                               uint32_t callbackId) { | 
 |   // We handle PNG encoding with browser APIs so that we can omit libpng from | 
 |   // skia to save binary size. | 
 |   assert(format != ImageByteFormat::png); | 
 |   sk_sp<SkData> data; | 
 |   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_async_run_in_main_runtime_thread( | 
 |       EM_FUNC_SIG_VIII, fOnRasterizeComplete, this, data.release(), callbackId); | 
 | } | 
 |  | 
 | void Surface::_onRasterizeComplete(SkData* data, uint32_t callbackId) { | 
 |   _callbackHandler(callbackId, data, __builtin_wasm_ref_null_extern()); | 
 | } | 
 |  | 
 | // Main thread only | 
 | void Surface::onRenderComplete(uint32_t callbackId, SkwasmObject imageBitmap) { | 
 |   assert(emscripten_is_main_browser_thread()); | 
 |   _callbackHandler(callbackId, nullptr, imageBitmap); | 
 | } | 
 |  | 
 | void Surface::fDispose(Surface* surface) { | 
 |   surface->_dispose(); | 
 | } | 
 |  | 
 | 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(); | 
 | } | 
 |  | 
 | SKWASM_EXPORT Surface* surface_create() { | 
 |   return new Surface(); | 
 | } | 
 |  | 
 | 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 uint32_t surface_renderPictures(Surface* surface, | 
 |                                               SkPicture** pictures, | 
 |                                               int count) { | 
 |   return surface->renderPictures(pictures, count); | 
 | } | 
 |  | 
 | SKWASM_EXPORT void surface_renderPicturesOnWorker(Surface* surface, | 
 |                                                   sk_sp<SkPicture>* pictures, | 
 |                                                   int pictureCount, | 
 |                                                   uint32_t callbackId, | 
 |                                                   double rasterStart) { | 
 |   // This will release the pictures when they leave scope. | 
 |   std::unique_ptr<sk_sp<SkPicture>[]> picturesPointer = | 
 |       std::unique_ptr<sk_sp<SkPicture>[]>(pictures); | 
 |   surface->renderPicturesOnWorker(pictures, pictureCount, callbackId, | 
 |                                   rasterStart); | 
 | } | 
 |  | 
 | SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface, | 
 |                                               SkImage* image, | 
 |                                               ImageByteFormat format) { | 
 |   return surface->rasterizeImage(image, format); | 
 | } | 
 |  | 
 | // This is used by the skwasm JS support code to call back into C++ when the | 
 | // we finish creating the image bitmap, which is an asynchronous operation. | 
 | SKWASM_EXPORT void surface_onRenderComplete(Surface* surface, | 
 |                                             uint32_t callbackId, | 
 |                                             SkwasmObject imageBitmap) { | 
 |   return surface->onRenderComplete(callbackId, imageBitmap); | 
 | } |