blob: 13523e0ea72c509189e5e55aee0bb55889bce269 [file] [log] [blame]
// 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 "skwasm_support.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"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLMakeWebGLInterface.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);
}
// Worker thread only
void Surface::dispose() {
delete 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();
skwasm_dispatchRasterizeImage(_thread, 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(GrGLInterfaces::MakeWebGL());
// 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::_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);
}
// Worker thread only
void Surface::rasterizeImageOnWorker(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());
// TODO(jacksongardner):
// Normally we'd just call `readPixels` on the image. However, this doesn't
// actually work in some cases due to a skia bug. Instead, we just draw the
// image to our scratch canvas and grab the pixels out directly with
// `glReadPixels`. Once the skia bug is fixed, we should switch back to using
// `SkImage::readPixels` instead.
// See https://g-issues.skia.org/issues/349201915
_resizeCanvasToFit(image->width(), image->height());
auto canvas = _surface->getCanvas();
canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
// We want the pixels from the upper left corner, but glReadPixels gives us
// the pixels from the lower left corner. So we have to flip the image when we
// are drawing it to get the pixels in the desired order.
canvas->save();
canvas->scale(1, -1);
canvas->drawImage(image, 0, -_canvasHeight);
canvas->restore();
_grContext->flush(_surface.get());
emscripten_glReadPixels(0, 0, image->width(), image->height(), GL_RGBA,
GL_UNSIGNED_BYTE, reinterpret_cast<void*>(pixels));
image->unref();
skwasm_postRasterizeResult(this, data.release(), callbackId);
}
void Surface::onRasterizeComplete(uint32_t callbackId, SkData* data) {
_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);
}
TextureSourceWrapper::TextureSourceWrapper(unsigned long threadId,
SkwasmObject textureSource)
: _rasterThreadId(threadId) {
skwasm_setAssociatedObjectOnThread(_rasterThreadId, this, textureSource);
}
TextureSourceWrapper::~TextureSourceWrapper() {
skwasm_disposeAssociatedObjectOnThread(_rasterThreadId, this);
}
SkwasmObject TextureSourceWrapper::getTextureSource() {
return skwasm_getAssociatedObject(this);
}
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) {
// Dispatch to the worker
skwasm_dispatchDisposeSurface(surface->getThreadId(), surface);
}
SKWASM_EXPORT void surface_dispose(Surface* surface) {
// This should be called directly only on the worker
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);
}
SKWASM_EXPORT void surface_rasterizeImageOnWorker(Surface* surface,
SkImage* image,
ImageByteFormat format,
uint32_t callbackId) {
surface->rasterizeImageOnWorker(image, format, callbackId);
}
// 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) {
surface->onRenderComplete(callbackId, imageBitmap);
}
SKWASM_EXPORT void surface_onRasterizeComplete(Surface* surface,
SkData* data,
uint32_t callbackId) {
surface->onRasterizeComplete(callbackId, data);
}