Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[skwasm] Fixes for getting pixels from an image. #53561

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions lib/web_ui/skwasm/library_skwasm_support.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ mergeInto(LibraryManager.library, {
}
associatedObjectsMap.delete(pointer);
return;
case 'disposeSurface':
_surface_dispose(data.surface);
return;
case 'rasterizeImage':
_surface_rasterizeImageOnWorker(
data.surface,
data.image,
data.format,
data.callbackId,
);
return;
case 'onRasterizeComplete':
_surface_onRasterizeComplete(
data.surface,
data.data,
data.callbackId,
);
return;
default:
console.warn(`unrecognized skwasm message: ${skwasmMessage}`);
}
Expand Down Expand Up @@ -153,6 +171,29 @@ mergeInto(LibraryManager.library, {
pointer,
});
};
_skwasm_dispatchDisposeSurface = function(threadId, surface) {
PThread.pthreads[threadId].postMessage({
skwasmMessage: 'disposeSurface',
surface,
});
}
_skwasm_dispatchRasterizeImage = function(threadId, surface, image, format, callbackId) {
PThread.pthreads[threadId].postMessage({
skwasmMessage: 'rasterizeImage',
surface,
image,
format,
callbackId,
});
}
_skwasm_postRasterizeResult = function(surface, data, callbackId) {
postMessage({
skwasmMessage: 'onRasterizeComplete',
surface,
data,
callbackId,
});
}
},
skwasm_setAssociatedObjectOnThread: function () {},
skwasm_setAssociatedObjectOnThread__deps: ['$skwasm_support_setup'],
Expand All @@ -176,5 +217,11 @@ mergeInto(LibraryManager.library, {
skwasm_resolveAndPostImages__deps: ['$skwasm_support_setup'],
skwasm_createGlTextureFromTextureSource: function () {},
skwasm_createGlTextureFromTextureSource__deps: ['$skwasm_support_setup'],
skwasm_dispatchDisposeSurface: function() {},
skwasm_dispatchDisposeSurface__deps: ['$skwasm_support_setup'],
skwasm_dispatchRasterizeImage: function() {},
skwasm_dispatchRasterizeImage__deps: ['$skwasm_support_setup'],
skwasm_postRasterizeResult: function() {},
skwasm_postRasterizeResult__deps: ['$skwasm_support_setup'],
});

15 changes: 11 additions & 4 deletions lib/web_ui/skwasm/skwasm_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@

#include <emscripten/threading.h>
#include <cinttypes>
#include "surface.h"
#include "third_party/skia/include/core/SkPicture.h"

namespace Skwasm {
class Surface;
}

using SkwasmObject = __externref_t;

extern "C" {
Expand Down Expand Up @@ -43,6 +40,16 @@ extern unsigned int skwasm_createGlTextureFromTextureSource(
SkwasmObject textureSource,
int width,
int height);
extern void skwasm_dispatchDisposeSurface(unsigned long threadId,
Skwasm::Surface* surface);
extern void skwasm_dispatchRasterizeImage(unsigned long threadId,
Skwasm::Surface* surface,
SkImage* image,
Skwasm::ImageByteFormat format,
uint32_t callbackId);
extern void skwasm_postRasterizeResult(Skwasm::Surface* surface,
SkData* data,
uint32_t callbackId);
}

#endif // FLUTTER_LIB_WEB_UI_SKWASM_SKWASM_SUPPORT_H_
101 changes: 63 additions & 38 deletions lib/web_ui/skwasm/surface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#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"
Expand Down Expand Up @@ -34,12 +35,9 @@ Surface::Surface() {
skwasm_syncTimeOriginForThread(_thread);
}

// Main thread only
// Worker thread only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important distinction! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantics here actually changed as part of this PR. dispose used to be called on the main thread, and then that dispatched to the web worker which called _dispose. Now there's just one method, dispose and that does what _dispose used to do.

void Surface::dispose() {
assert(emscripten_is_main_browser_thread());
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VI,
reinterpret_cast<void*>(fDispose), nullptr,
this);
delete this;
}

// Main thread only
Expand All @@ -65,9 +63,7 @@ uint32_t Surface::rasterizeImage(SkImage* image, ImageByteFormat format) {
uint32_t callbackId = ++_currentCallbackId;
image->ref();

emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIIII,
reinterpret_cast<void*>(fRasterizeImage),
nullptr, this, image, format, callbackId);
skwasm_dispatchRasterizeImage(_thread, this, image, format, callbackId);
return callbackId;
}

Expand Down Expand Up @@ -122,11 +118,6 @@ void Surface::_init() {
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) {
Expand Down Expand Up @@ -175,9 +166,10 @@ void Surface::renderPicturesOnWorker(sk_sp<SkPicture>* pictures,
skwasm_resolveAndPostImages(this, imagePromiseArray, rasterStart, callbackId);
}

void Surface::_rasterizeImage(SkImage* image,
ImageByteFormat format,
uint32_t 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);
Expand All @@ -192,17 +184,35 @@ void Surface::_rasterizeImage(SkImage* image,
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);

// 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(SkData* data, uint32_t callbackId) {
void Surface::onRasterizeComplete(uint32_t callbackId, SkData* data) {
_callbackHandler(callbackId, data, __builtin_wasm_ref_null_extern());
}

Expand All @@ -212,22 +222,18 @@ void Surface::onRenderComplete(uint32_t callbackId, SkwasmObject imageBitmap) {
_callbackHandler(callbackId, nullptr, imageBitmap);
}

void Surface::fDispose(Surface* surface) {
surface->_dispose();
TextureSourceWrapper::TextureSourceWrapper(unsigned long threadId,
SkwasmObject textureSource)
: _rasterThreadId(threadId) {
skwasm_setAssociatedObjectOnThread(_rasterThreadId, this, textureSource);
}

void Surface::fOnRasterizeComplete(Surface* surface,
SkData* imageData,
uint32_t callbackId) {
surface->_onRasterizeComplete(imageData, callbackId);
TextureSourceWrapper::~TextureSourceWrapper() {
skwasm_disposeAssociatedObjectOnThread(_rasterThreadId, this);
}

void Surface::fRasterizeImage(Surface* surface,
SkImage* image,
ImageByteFormat format,
uint32_t callbackId) {
surface->_rasterizeImage(image, format, callbackId);
image->unref();
SkwasmObject TextureSourceWrapper::getTextureSource() {
return skwasm_getAssociatedObject(this);
}

SKWASM_EXPORT Surface* surface_create() {
Expand All @@ -245,6 +251,12 @@ SKWASM_EXPORT void surface_setCallbackHandler(
}

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();
}

Expand Down Expand Up @@ -272,10 +284,23 @@ SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface,
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) {
return surface->onRenderComplete(callbackId, imageBitmap);
surface->onRenderComplete(callbackId, imageBitmap);
}

SKWASM_EXPORT void surface_onRasterizeComplete(Surface* surface,
SkData* data,
uint32_t callbackId) {
surface->onRasterizeComplete(callbackId, data);
}
34 changes: 7 additions & 27 deletions lib/web_ui/skwasm/surface.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#include <webgl/webgl1.h>
#include <cassert>
#include "export.h"
#include "skwasm_support.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"
Expand All @@ -36,16 +35,10 @@ enum class ImageByteFormat {

class TextureSourceWrapper {
public:
TextureSourceWrapper(unsigned long threadId, SkwasmObject textureSource)
: _rasterThreadId(threadId) {
skwasm_setAssociatedObjectOnThread(_rasterThreadId, this, textureSource);
}
TextureSourceWrapper(unsigned long threadId, SkwasmObject textureSource);
~TextureSourceWrapper();

~TextureSourceWrapper() {
skwasm_disposeAssociatedObjectOnThread(_rasterThreadId, this);
}

SkwasmObject getTextureSource() { return skwasm_getAssociatedObject(this); }
SkwasmObject getTextureSource();

private:
unsigned long _rasterThreadId;
Expand All @@ -66,6 +59,7 @@ class Surface {
uint32_t rasterizeImage(SkImage* image, ImageByteFormat format);
void setCallbackHandler(CallbackHandler* callbackHandler);
void onRenderComplete(uint32_t callbackId, SkwasmObject imageBitmap);
void onRasterizeComplete(uint32_t callbackId, SkData* data);

// Any thread
std::unique_ptr<TextureSourceWrapper> createTextureSourceWrapper(
Expand All @@ -76,17 +70,15 @@ class Surface {
int pictureCount,
uint32_t callbackId,
double rasterStart);
void rasterizeImageOnWorker(SkImage* image,
ImageByteFormat format,
uint32_t callbackId);

private:
void _runWorker();
void _init();
void _dispose();
void _resizeCanvasToFit(int width, int height);
void _recreateSurface();
void _rasterizeImage(SkImage* image,
ImageByteFormat format,
uint32_t callbackId);
void _onRasterizeComplete(SkData* data, uint32_t callbackId);

std::string _canvasID;
CallbackHandler* _callbackHandler = nullptr;
Expand All @@ -103,18 +95,6 @@ class Surface {
GrGLint _stencil;

pthread_t _thread;

static void fDispose(Surface* surface);
static void fOnRenderComplete(Surface* surface,
uint32_t callbackId,
SkwasmObject imageBitmap);
static void fRasterizeImage(Surface* surface,
SkImage* image,
ImageByteFormat format,
uint32_t callbackId);
static void fOnRasterizeComplete(Surface* surface,
SkData* imageData,
uint32_t callbackId);
};
} // namespace Skwasm

Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/skwasm/wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Skwasm {

using SkwasmObject = __externref_t;

struct SurfaceWrapper {
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
sk_sp<GrDirectContext> grContext;
Expand Down
3 changes: 3 additions & 0 deletions lib/web_ui/test/ui/image_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ Future<void> testMain() async {
final ByteData? rgbaData = await image.toByteData();
expect(rgbaData, isNotNull);
expect(rgbaData!.lengthInBytes, isNonZero);

// Make sure it isn't all zeros.
expect(rgbaData.buffer.asUint8List().any((int byte) => byte != 0), true);
});

test('toByteData_png', () async {
Expand Down