From e212cd12fdadf35ac56d7b68e400f48bc818fb0c Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 13:24:00 -0700 Subject: [PATCH 01/10] Initial pass at picture to image. --- lib/web_ui/lib/painting.dart | 1 + .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../src/engine/skwasm/skwasm_impl/canvas.dart | 48 +++- .../src/engine/skwasm/skwasm_impl/image.dart | 23 +- .../src/engine/skwasm/skwasm_impl/paint.dart | 28 +-- .../engine/skwasm/skwasm_impl/picture.dart | 10 +- .../skwasm/skwasm_impl/raw/raw_canvas.dart | 55 ++++- .../skwasm/skwasm_impl/raw/raw_image.dart | 33 +++ .../skwasm/skwasm_impl/scene_builder.dart | 4 - lib/web_ui/skwasm/BUILD.gn | 1 + lib/web_ui/skwasm/canvas.cpp | 209 +++++++++++------- lib/web_ui/skwasm/image.cpp | 32 +++ lib/web_ui/skwasm/picture.cpp | 4 +- lib/web_ui/skwasm/wrappers.h | 5 - 14 files changed, 315 insertions(+), 139 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart create mode 100644 lib/web_ui/skwasm/image.cpp diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 59e3773ceabce..5ee674fe15701 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -398,6 +398,7 @@ class MaskFilter { String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})'; } +// This needs to be kept in sync with the "_FilterQuality" enum in skwasm's canvas.cpp enum FilterQuality { none, low, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index c1c5eade1687e..613c524417c56 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -21,6 +21,7 @@ export 'skwasm_impl/picture.dart'; export 'skwasm_impl/raw/raw_canvas.dart'; export 'skwasm_impl/raw/raw_fonts.dart'; export 'skwasm_impl/raw/raw_geometry.dart'; +export 'skwasm_impl/raw/raw_image.dart'; export 'skwasm_impl/raw/raw_memory.dart'; export 'skwasm_impl/raw/raw_paint.dart'; export 'skwasm_impl/raw/raw_path.dart'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart index ddf1326effef2..ec1d7afd50289 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart @@ -183,21 +183,51 @@ class SkwasmCanvas implements ui.Canvas { } @override - void drawImage(ui.Image uiImage, ui.Offset offset, ui.Paint uiPaint) { - throw UnimplementedError(); - } + void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) => + canvasDrawImage( + _handle, + (image as SkwasmImage).handle, + offset.dx, + offset.dy, + (paint as SkwasmPaint).handle, + paint.filterQuality.index, + ); @override void drawImageRect( - ui.Image uiImage, ui.Rect src, ui.Rect dst, ui.Paint uiPaint) { - throw UnimplementedError(); - } + ui.Image image, + ui.Rect src, + ui.Rect dst, + ui.Paint paint) => withStackScope((StackScope scope) { + final Pointer sourceRect = scope.convertRectToNative(src); + final Pointer destRect = scope.convertRectToNative(dst); + canvasDrawImageRect( + _handle, + (image as SkwasmImage).handle, + sourceRect, + destRect, + (paint as SkwasmPaint).handle, + paint.filterQuality.index, + ); + }); @override void drawImageNine( - ui.Image uiImage, ui.Rect center, ui.Rect dst, ui.Paint uiPaint) { - throw UnimplementedError(); - } + ui.Image image, + ui.Rect center, + ui.Rect dst, + ui.Paint paint) => withStackScope((StackScope scope) { + final Pointer centerRect = scope.convertIRectToNative(center); + final Pointer destRect = scope.convertRectToNative(dst); + canvasDrawImageNine( + _handle, + (image as SkwasmImage).handle, + centerRect, + destRect, + (paint as SkwasmPaint).handle, + paint.filterQuality.index, + ); + }); @override void drawPicture(ui.Picture picture) { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index a019591f3dde3..48bb100e5e4ce 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -4,18 +4,20 @@ import 'dart:typed_data'; +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; class SkwasmImage implements ui.Image { + SkwasmImage(this.handle); + + final ImageHandle handle; + bool _isDisposed = false; + @override - int get width { - throw UnimplementedError(); - } + int get width => imageGetWidth(handle); @override - int get height { - throw UnimplementedError(); - } + int get height => imageGetHeight(handle); @override Future toByteData( @@ -28,13 +30,14 @@ class SkwasmImage implements ui.Image { @override void dispose() { - throw UnimplementedError(); + if (!_isDisposed) { + imageDispose(handle); + _isDisposed = true; + } } @override - bool get debugDisposed { - throw UnimplementedError(); - } + bool get debugDisposed => _isDisposed; @override SkwasmImage clone() => this; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart index 3c953df026394..5b070d835c6f5 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart @@ -76,31 +76,31 @@ class SkwasmPaint implements ui.Paint { @override set strokeMiterLimit(double limit) => paintSetMiterLimit(_handle, limit); - // Unimplemented stuff below @override - ui.ColorFilter? colorFilter; + ui.Shader? get shader => _shader; @override - ui.FilterQuality filterQuality = ui.FilterQuality.none; + set shader(ui.Shader? uiShader) { + final SkwasmShader? skwasmShader = uiShader as SkwasmShader?; + _shader = skwasmShader; + final ShaderHandle shaderHandle = + skwasmShader != null ? skwasmShader.handle : nullptr; + paintSetShader(_handle, shaderHandle); + } @override - ui.ImageFilter? imageFilter; + ui.FilterQuality filterQuality = ui.FilterQuality.none; + // Unimplemented stuff below @override - bool invertColors = false; + ui.ColorFilter? colorFilter; @override - ui.MaskFilter? maskFilter; + ui.ImageFilter? imageFilter; @override - ui.Shader? get shader => _shader; + bool invertColors = false; @override - set shader(ui.Shader? uiShader) { - final SkwasmShader? skwasmShader = uiShader as SkwasmShader?; - _shader = skwasmShader; - final ShaderHandle shaderHandle = - skwasmShader != null ? skwasmShader.handle : nullptr; - paintSetShader(_handle, shaderHandle); - } + ui.MaskFilter? maskFilter; } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart index 00959d06e702a..2fbb2043f7f79 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart @@ -12,9 +12,7 @@ class SkwasmPicture implements ui.Picture { PictureHandle get handle => _handle; @override - Future toImage(int width, int height) { - throw UnimplementedError(); - } + Future toImage(int width, int height) async => toImageSync(width, height); @override void dispose() { @@ -30,10 +28,8 @@ class SkwasmPicture implements ui.Picture { bool debugDisposed = false; @override - ui.Image toImageSync(int width, int height) { - // TODO(jacksongardner): implement toImageSync - throw UnimplementedError(); - } + ui.Image toImageSync(int width, int height) => + SkwasmImage(imageCreateFromPicture(handle, width, height)); ui.Rect get cullRect { return withStackScope((StackScope s) { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart index 8571c000f7338..a98f6cdf74c4e 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart @@ -9,9 +9,9 @@ import 'dart:ffi'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; -final class CanvasWrapper extends Opaque {} +final class RawCanvas extends Opaque {} -typedef CanvasHandle = Pointer; +typedef CanvasHandle = Pointer; @Native(symbol: 'canvas_destroy', isLeaf: true) external void canvasDestroy(CanvasHandle canvas); @@ -126,6 +126,57 @@ external void canvasDrawPath( symbol: 'canvas_drawPicture', isLeaf: true) external void canvasDrawPicture(CanvasHandle canvas, PictureHandle picture); +@Native(symbol: 'canvas_drawImage', isLeaf: true) +external void canvasDrawImage( + CanvasHandle handle, + ImageHandle image, + double offsetX, + double offsetY, + PaintHandle paint, + int filterQuality, +); + +@Native, + Pointer, + PaintHandle, + Int, +)>(symbol: 'canvas_drawImageRect', isLeaf: true) +external void canvasDrawImageRect( + CanvasHandle handle, + ImageHandle image, + Pointer sourceRect, + Pointer destRect, + PaintHandle paint, + int filterQuality, +); + +@Native, + Pointer, + PaintHandle, + Int, +)>(symbol: 'canvas_drawImageNine', isLeaf: true) +external void canvasDrawImageNine( + CanvasHandle handle, + ImageHandle image, + Pointer centerRect, + Pointer destRect, + PaintHandle paint, + int filterQuality, +); + @Native( symbol: 'canvas_drawShadow', isLeaf: true) external void canvasDrawShadow( diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart new file mode 100644 index 0000000000000..88878fc8173ea --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart @@ -0,0 +1,33 @@ +// 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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawImage extends Opaque {} +typedef ImageHandle = Pointer; + +@Native(symbol: 'image_createFromPicture', isLeaf: true) +external ImageHandle imageCreateFromPicture( + PictureHandle handle, + int width, + int height, +); + +@Native(symbol: 'image_dispose', isLeaf: true) +external void imageDispose(ImageHandle handle); + +@Native(symbol: 'image_getWidth', isLeaf: true) +external int imageGetWidth(ImageHandle handle); + +@Native(symbol: 'image_getHeight', isLeaf: true) +external int imageGetHeight(ImageHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart index f1ce1e0ff1b2b..c1da8c7f5de23 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart @@ -187,12 +187,10 @@ class SkwasmSceneBuilder implements ui.SceneBuilder { @override void setCheckerboardOffscreenLayers(bool checkerboard) { - // TODO(jacksongardner): implement setCheckerboardOffscreenLayers } @override void setCheckerboardRasterCacheImages(bool checkerboard) { - // TODO(jacksongardner): implement setCheckerboardRasterCacheImages } @override @@ -205,12 +203,10 @@ class SkwasmSceneBuilder implements ui.SceneBuilder { double insetLeft, bool focusable ) { - // TODO(jacksongardner): implement setProperties } @override void setRasterizerTracingThreshold(int frameInterval) { - // TODO(jacksongardner): implement setRasterizerTracingThreshold } @override diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 362a75a1a7b84..2aa5aaafaeaa6 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -12,6 +12,7 @@ wasm_lib("skwasm") { "export.h", "fonts.cpp", "helpers.h", + "image.cpp", "paint.cpp", "path.cpp", "picture.cpp", diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index bad7efee94ec9..722fb32e99886 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -27,169 +27,180 @@ constexpr SkScalar kShadowLightRadius = 1.1; constexpr SkScalar kShadowLightHeight = 600.0; constexpr SkScalar kShadowLightXOffset = 0; constexpr SkScalar kShadowLightYOffset = -450; -} // namespace -SKWASM_EXPORT void canvas_destroy(CanvasWrapper* wrapper) { - delete wrapper; +// This needs to be kept in sync with the "FilterQuality" enum in dart:ui +enum class _FilterQuality { + none, + low, + medium, + high, +}; + +SkFilterMode _filterModeForQuality(_FilterQuality quality) { + switch (quality) { + case _FilterQuality::none: + case _FilterQuality::low: + return SkFilterMode::kNearest; + case _FilterQuality::medium: + case _FilterQuality::high: + return SkFilterMode::kLinear; + } +} + +SkSamplingOptions _samplingOptionsForQuality(_FilterQuality quality) { + switch (quality) { + case _FilterQuality::none: + return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); + case _FilterQuality::low: + return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNearest); + case _FilterQuality::medium: + return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); + case _FilterQuality::high: + // Cubic equation coefficients recommended by Mitchell & Netravali + // in their paper on cubic interpolation. + return SkSamplingOptions(SkCubicResampler::Mitchell()); + } } +} // namespace -SKWASM_EXPORT void canvas_saveLayer(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_saveLayer(SkCanvas* canvas, SkRect* rect, SkPaint* paint) { - wrapper->canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, 0)); + canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, 0)); } -SKWASM_EXPORT void canvas_save(CanvasWrapper* wrapper) { - wrapper->canvas->save(); +SKWASM_EXPORT void canvas_save(SkCanvas* canvas) { + canvas->save(); } -SKWASM_EXPORT void canvas_restore(CanvasWrapper* wrapper) { - wrapper->canvas->restore(); +SKWASM_EXPORT void canvas_restore(SkCanvas* canvas) { + canvas->restore(); } -SKWASM_EXPORT void canvas_restoreToCount(CanvasWrapper* wrapper, int count) { - wrapper->canvas->restoreToCount(count); +SKWASM_EXPORT void canvas_restoreToCount(SkCanvas* canvas, int count) { + canvas->restoreToCount(count); } -SKWASM_EXPORT int canvas_getSaveCount(CanvasWrapper* wrapper) { - return wrapper->canvas->getSaveCount(); +SKWASM_EXPORT int canvas_getSaveCount(SkCanvas* canvas) { + return canvas->getSaveCount(); } -SKWASM_EXPORT void canvas_translate(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_translate(SkCanvas* canvas, SkScalar dx, SkScalar dy) { - wrapper->canvas->translate(dx, dy); + canvas->translate(dx, dy); } -SKWASM_EXPORT void canvas_scale(CanvasWrapper* wrapper, - SkScalar sx, - SkScalar sy) { - wrapper->canvas->scale(sx, sy); +SKWASM_EXPORT void canvas_scale(SkCanvas* canvas, SkScalar sx, SkScalar sy) { + canvas->scale(sx, sy); } -SKWASM_EXPORT void canvas_rotate(CanvasWrapper* wrapper, SkScalar degrees) { - wrapper->canvas->rotate(degrees); +SKWASM_EXPORT void canvas_rotate(SkCanvas* canvas, SkScalar degrees) { + canvas->rotate(degrees); } -SKWASM_EXPORT void canvas_skew(CanvasWrapper* wrapper, - SkScalar sx, - SkScalar sy) { - wrapper->canvas->skew(sx, sy); +SKWASM_EXPORT void canvas_skew(SkCanvas* canvas, SkScalar sx, SkScalar sy) { + canvas->skew(sx, sy); } -SKWASM_EXPORT void canvas_transform(CanvasWrapper* wrapper, - const SkM44* matrix44) { - wrapper->canvas->concat(*matrix44); +SKWASM_EXPORT void canvas_transform(SkCanvas* canvas, const SkM44* matrix44) { + canvas->concat(*matrix44); } -SKWASM_EXPORT void canvas_clipRect(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_clipRect(SkCanvas* canvas, const SkRect* rect, SkClipOp op, bool antialias) { - wrapper->canvas->clipRect(*rect, op, antialias); + canvas->clipRect(*rect, op, antialias); } -SKWASM_EXPORT void canvas_clipRRect(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_clipRRect(SkCanvas* canvas, const SkScalar* rrectValues, bool antialias) { - wrapper->canvas->clipRRect(createRRect(rrectValues), antialias); + canvas->clipRRect(createRRect(rrectValues), antialias); } -SKWASM_EXPORT void canvas_clipPath(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_clipPath(SkCanvas* canvas, SkPath* path, bool antialias) { - wrapper->canvas->clipPath(*path, antialias); + canvas->clipPath(*path, antialias); } -SKWASM_EXPORT void canvas_drawColor(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawColor(SkCanvas* canvas, SkColor color, SkBlendMode blendMode) { - makeCurrent(wrapper->context); - wrapper->canvas->drawColor(color, blendMode); + canvas->drawColor(color, blendMode); } -SKWASM_EXPORT void canvas_drawLine(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawLine(SkCanvas* canvas, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawLine(x1, y1, x2, y2, *paint); + canvas->drawLine(x1, y1, x2, y2, *paint); } -SKWASM_EXPORT void canvas_drawPaint(CanvasWrapper* wrapper, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawPaint(*paint); +SKWASM_EXPORT void canvas_drawPaint(SkCanvas* canvas, SkPaint* paint) { + canvas->drawPaint(*paint); } -SKWASM_EXPORT void canvas_drawRect(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawRect(SkCanvas* canvas, SkRect* rect, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawRect(*rect, *paint); + canvas->drawRect(*rect, *paint); } -SKWASM_EXPORT void canvas_drawRRect(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawRRect(SkCanvas* canvas, const SkScalar* rrectValues, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawRRect(createRRect(rrectValues), *paint); + canvas->drawRRect(createRRect(rrectValues), *paint); } -SKWASM_EXPORT void canvas_drawDRRect(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawDRRect(SkCanvas* canvas, const SkScalar* outerRrectValues, const SkScalar* innerRrectValues, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawDRRect(createRRect(outerRrectValues), - createRRect(innerRrectValues), *paint); + canvas->drawDRRect(createRRect(outerRrectValues), + createRRect(innerRrectValues), *paint); } -SKWASM_EXPORT void canvas_drawOval(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawOval(SkCanvas* canvas, const SkRect* rect, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawOval(*rect, *paint); + canvas->drawOval(*rect, *paint); } -SKWASM_EXPORT void canvas_drawCircle(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawCircle(SkCanvas* canvas, SkScalar x, SkScalar y, SkScalar radius, SkPaint* paint) { - makeCurrent(wrapper->context); - - wrapper->canvas->drawCircle(x, y, radius, *paint); + canvas->drawCircle(x, y, radius, *paint); } -SKWASM_EXPORT void canvas_drawArc(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawArc(SkCanvas* canvas, const SkRect* rect, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees, bool useCenter, SkPaint* paint) { - makeCurrent(wrapper->context); - wrapper->canvas->drawArc(*rect, startAngleDegrees, sweepAngleDegrees, - useCenter, *paint); + canvas->drawArc(*rect, startAngleDegrees, sweepAngleDegrees, useCenter, + *paint); } -SKWASM_EXPORT void canvas_drawPath(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawPath(SkCanvas* canvas, SkPath* path, SkPaint* paint) { - makeCurrent(wrapper->context); - - wrapper->canvas->drawPath(*path, *paint); + canvas->drawPath(*path, *paint); } -SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawShadow(SkCanvas* canvas, SkPath* path, SkScalar elevation, SkScalar devicePixelRatio, SkColor color, bool transparentOccluder) { - makeCurrent(wrapper->context); - SkColor inAmbient = SkColorSetA(color, kShadowAmbientAlpha * SkColorGetA(color)); SkColor inSpot = SkColorSetA(color, kShadowSpotAlpha * SkColorGetA(color)); @@ -201,38 +212,64 @@ SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper, : SkShadowFlags::kNone_ShadowFlag; flags |= SkShadowFlags::kDirectionalLight_ShadowFlag; SkShadowUtils::DrawShadow( - wrapper->canvas, *path, - SkPoint3::Make(0.0f, 0.0f, elevation * devicePixelRatio), + canvas, *path, SkPoint3::Make(0.0f, 0.0f, elevation * devicePixelRatio), SkPoint3::Make(kShadowLightXOffset, kShadowLightYOffset, kShadowLightHeight * devicePixelRatio), devicePixelRatio * kShadowLightRadius, outAmbient, outSpot, flags); } -SKWASM_EXPORT void canvas_drawParagraph(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_drawParagraph(SkCanvas* canvas, Paragraph* paragraph, SkScalar x, SkScalar y) { - paragraph->paint(wrapper->canvas, x, y); + paragraph->paint(canvas, x, y); +} + +SKWASM_EXPORT void canvas_drawPicture(SkCanvas* canvas, SkPicture* picture) { + canvas->drawPicture(picture); } -SKWASM_EXPORT void canvas_drawPicture(CanvasWrapper* wrapper, - SkPicture* picture) { - makeCurrent(wrapper->context); +SKWASM_EXPORT void canvas_drawImage(SkCanvas* canvas, + SkImage* image, + SkScalar offsetX, + SkScalar offsetY, + SkPaint* paint, + _FilterQuality quality) { + canvas->drawImage(image, offsetX, offsetY, + _samplingOptionsForQuality(quality), paint); +} + +SKWASM_EXPORT void canvas_drawImageRect(SkCanvas* canvas, + SkImage* image, + SkRect* sourceRect, + SkRect* destRect, + SkPaint* paint, + _FilterQuality quality) { + canvas->drawImageRect(image, *sourceRect, *destRect, + _samplingOptionsForQuality(quality), paint, + SkCanvas::kStrict_SrcRectConstraint); +} - wrapper->canvas->drawPicture(picture); +SKWASM_EXPORT void canvas_drawImageNine(SkCanvas* canvas, + SkImage* image, + SkIRect* centerRect, + SkRect* destinationRect, + SkPaint* paint, + _FilterQuality quality) { + canvas->drawImageNine(image, *centerRect, *destinationRect, + _filterModeForQuality(quality), paint); } -SKWASM_EXPORT void canvas_getTransform(CanvasWrapper* wrapper, - SkM44* outTransform) { - *outTransform = wrapper->canvas->getLocalToDevice(); +SKWASM_EXPORT void canvas_getTransform(SkCanvas* canvas, SkM44* outTransform) { + *outTransform = canvas->getLocalToDevice(); } -SKWASM_EXPORT void canvas_getLocalClipBounds(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_getLocalClipBounds(SkCanvas* canvas, SkRect* outRect) { - *outRect = wrapper->canvas->getLocalClipBounds(); + *outRect = canvas->getLocalClipBounds(); } -SKWASM_EXPORT void canvas_getDeviceClipBounds(CanvasWrapper* wrapper, +SKWASM_EXPORT void canvas_getDeviceClipBounds(SkCanvas* canvas, SkIRect* outRect) { - *outRect = wrapper->canvas->getDeviceClipBounds(); + *outRect = canvas->getDeviceClipBounds(); } diff --git a/lib/web_ui/skwasm/image.cpp b/lib/web_ui/skwasm/image.cpp new file mode 100644 index 0000000000000..d0547f4cb59dd --- /dev/null +++ b/lib/web_ui/skwasm/image.cpp @@ -0,0 +1,32 @@ +// 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 "export.h" + +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkPicture.h" + +using namespace SkImages; + +SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, + int32_t width, + int32_t height) { + picture->ref(); + return DeferredFromPicture(sk_sp(picture), {width, height}, + nullptr, nullptr, BitDepth::kU8, nullptr) + .release(); +} + +SKWASM_EXPORT void image_dispose(SkImage* image) { + image->unref(); +} + +SKWASM_EXPORT int image_getWidth(SkImage* image) { + return image->width(); +} + +SKWASM_EXPORT int image_getHeight(SkImage* image) { + return image->height(); +} diff --git a/lib/web_ui/skwasm/picture.cpp b/lib/web_ui/skwasm/picture.cpp index c12d7d42fc53b..ec72b4cf68897 100644 --- a/lib/web_ui/skwasm/picture.cpp +++ b/lib/web_ui/skwasm/picture.cpp @@ -19,10 +19,10 @@ SKWASM_EXPORT void pictureRecorder_dispose(SkPictureRecorder* recorder) { delete recorder; } -SKWASM_EXPORT CanvasWrapper* pictureRecorder_beginRecording( +SKWASM_EXPORT SkCanvas* pictureRecorder_beginRecording( SkPictureRecorder* recorder, const SkRect* cullRect) { - return new CanvasWrapper{0, recorder->beginRecording(*cullRect, &bbhFactory)}; + return recorder->beginRecording(*cullRect, &bbhFactory); } SKWASM_EXPORT SkPicture* pictureRecorder_endRecording( diff --git a/lib/web_ui/skwasm/wrappers.h b/lib/web_ui/skwasm/wrappers.h index ffb356bba9c67..db4b31cf86301 100644 --- a/lib/web_ui/skwasm/wrappers.h +++ b/lib/web_ui/skwasm/wrappers.h @@ -18,11 +18,6 @@ struct SurfaceWrapper { sk_sp surface; }; -struct CanvasWrapper { - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; - SkCanvas* canvas; -}; - inline void makeCurrent(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle) { if (!handle) return; From 7f8db301687e30ecb0c56fcd79538c4896943614 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 15:28:28 -0700 Subject: [PATCH 02/10] Added a unit test and a fix. --- lib/web_ui/skwasm/image.cpp | 2 +- lib/web_ui/test/ui/image_golden_test.dart | 98 +++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 lib/web_ui/test/ui/image_golden_test.dart diff --git a/lib/web_ui/skwasm/image.cpp b/lib/web_ui/skwasm/image.cpp index d0547f4cb59dd..16097738e03c6 100644 --- a/lib/web_ui/skwasm/image.cpp +++ b/lib/web_ui/skwasm/image.cpp @@ -15,7 +15,7 @@ SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, int32_t height) { picture->ref(); return DeferredFromPicture(sk_sp(picture), {width, height}, - nullptr, nullptr, BitDepth::kU8, nullptr) + nullptr, nullptr, BitDepth::kU8, SkColorSpace::MakeSRGB()) .release(); } diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart new file mode 100644 index 0000000000000..86524c14c0193 --- /dev/null +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -0,0 +1,98 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; + +import 'package:ui/ui.dart' as ui; +import 'package:web_engine_tester/golden_tester.dart'; + +import '../common/test_initialization.dart'; +import 'utils.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests( + setUpTestViewDimensions: false, + ); + + const ui.Rect drawRegion = ui.Rect.fromLTWH(0, 0, 300, 300); + const ui.Rect imageRegion = ui.Rect.fromLTWH(0, 0, 150, 150); + + // Emits a set of rendering tests for an image + // `imageGenerator` should produce an image that is 150x150 pixels. + void emitImageTests(String name, Future Function() imageGenerator) { + group(name, () { + test('drawImage', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); + canvas.drawImage(image, const ui.Offset(100, 100), ui.Paint()); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + + await matchGoldenFile('${name}_canvas_drawImage.png', region: drawRegion); + }); + + test('drawImageRect', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); + canvas.drawImageRect( + image, + const ui.Rect.fromLTRB(50, 50, 100, 100), + const ui.Rect.fromLTRB(100, 100, 150, 150), + ui.Paint() + ); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + + await matchGoldenFile('${name}_canvas_drawImageRect.png', region: drawRegion); + }); + + test('drawImageNine', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); + canvas.drawImageNine( + image, + const ui.Rect.fromLTRB(50, 50, 100, 100), + drawRegion, + ui.Paint() + ); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + + await matchGoldenFile('${name}_canvas_drawImageNine.png', region: drawRegion); + }); + }); + } + + emitImageTests('picture_toImage', () { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, imageRegion); + for (int y = 0; y < 15; y++) { + for (int x = 0; x < 15; x++) { + final ui.Offset center = ui.Offset(x * 10 + 5, y * 10 + 5); + final ui.Color color = ui.Color.fromRGBO( + (center.dx * 256 / 150).round(), + (center.dy * 256 / 150).round(), 0, 1); + canvas.drawCircle(center, 5, ui.Paint()..color = color); + } + } + return recorder.endRecording().toImage(150, 150); + }); +} From 85fe3465e5909a74a584108f0e34750d2cf21203 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 22:26:52 -0700 Subject: [PATCH 03/10] Add support for image shaders and binding to fragment shaders. --- .../skwasm/skwasm_impl/raw/raw_shaders.dart | 15 ++++ .../engine/skwasm/skwasm_impl/renderer.dart | 16 +++- .../engine/skwasm/skwasm_impl/shaders.dart | 87 +++++++++++++++---- lib/web_ui/skwasm/canvas.cpp | 46 ++-------- lib/web_ui/skwasm/helpers.h | 34 ++++++++ lib/web_ui/skwasm/shaders.cpp | 25 ++++++ .../test/common/fake_asset_manager.dart | 11 +-- lib/web_ui/test/ui/fragment_shader_test.dart | 13 ++- lib/web_ui/test/ui/image_golden_test.dart | 78 +++++++++++++++++ 9 files changed, 255 insertions(+), 70 deletions(-) diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart index a999ddad28b30..9bda0b4e08e33 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_shaders.dart @@ -121,3 +121,18 @@ external ShaderHandle shaderCreateRuntimeEffectShader( Pointer childShaders, int childCount ); + +@Native(symbol: 'shader_createFromImage', isLeaf: true) +external ShaderHandle shaderCreateFromImage( + ImageHandle handle, + int tileModeX, + int tileModeY, + int quality, + RawMatrix33 localMatrix, +); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 59594feaf68ca..3c56696121f47 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -75,9 +75,19 @@ class SkwasmRenderer implements Renderer { } @override - ui.ImageShader createImageShader(ui.Image image, ui.TileMode tmx, ui.TileMode tmy, Float64List matrix4, ui.FilterQuality? filterQuality) { - throw UnimplementedError('createImageShader not yet implemented'); - } + ui.ImageShader createImageShader( + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality + ) => SkwasmImageShader.imageShader( + image as SkwasmImage, + tmx, + tmy, + matrix4, + filterQuality + ); @override ui.Gradient createLinearGradient( diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart index ef2bf5ce2f1c5..58b1cb15967ef 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart @@ -155,8 +155,43 @@ class SkwasmGradient extends SkwasmShader implements ui.Gradient { } } +class SkwasmImageShader extends SkwasmShader implements ui.ImageShader { + SkwasmImageShader._(this.handle); + + factory SkwasmImageShader.imageShader( + SkwasmImage image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality, + ) => withStackScope((StackScope scope) { + final RawMatrix33 localMatrix = scope.convertMatrix4toSkMatrix(matrix4); + return SkwasmImageShader._(shaderCreateFromImage( + image.handle, + tmx.index, + tmy.index, + (filterQuality ?? ui.FilterQuality.medium).index, + localMatrix, + )); + }); + + @override + ShaderHandle handle; + + @override + void dispose() { + super.dispose(); + handle = nullptr; + } +} + class SkwasmFragmentProgram implements ui.FragmentProgram { - SkwasmFragmentProgram._(this.name, this.handle); + SkwasmFragmentProgram._( + this.name, + this.handle, + this.floatUniformCount, + this.childShaderCount, + ); factory SkwasmFragmentProgram.fromBytes(String name, Uint8List bytes) { final ShaderData shaderData = ShaderData.fromBytes(bytes); @@ -171,15 +206,21 @@ class SkwasmFragmentProgram implements ui.FragmentProgram { } final RuntimeEffectHandle handle = runtimeEffectCreate(sourceString); skStringFree(sourceString); - return SkwasmFragmentProgram._(name, handle); + return SkwasmFragmentProgram._( + name, + handle, + shaderData.floatCount, + shaderData.textureCount + ); } RuntimeEffectHandle handle; String name; + int floatUniformCount; + int childShaderCount; @override - ui.FragmentShader fragmentShader() => - SkwasmFragmentShader(this); + ui.FragmentShader fragmentShader() => SkwasmFragmentShader(this); int get uniformSize => runtimeEffectGetUniformSize(handle); @@ -190,31 +231,29 @@ class SkwasmFragmentProgram implements ui.FragmentProgram { class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader { SkwasmFragmentShader( - SkwasmFragmentProgram program, { - List? childShaders, - }) : _program = program, + SkwasmFragmentProgram program + ) : _program = program, _uniformData = skDataCreate(program.uniformSize), - _childShaders = childShaders; + _floatUniformCount = program.floatUniformCount, + _childShaders = List.filled(program.childShaderCount, null); @override ShaderHandle get handle { if (_handle == nullptr) { _handle = withStackScope((StackScope s) { Pointer childShaders = nullptr; - final int childCount = _childShaders != null ? _childShaders!.length : 0; - if (childCount != 0) { - childShaders = s.allocPointerArray(childCount) + if (_childShaders.isNotEmpty) { + childShaders = s.allocPointerArray(_childShaders.length) .cast(); - final List shaders = _childShaders!; - for (int i = 0; i < childCount; i++) { - childShaders[i] = shaders[i].handle; + for (int i = 0; i < _childShaders.length; i++) { + childShaders[i] = _childShaders[i]!.handle; } } return shaderCreateRuntimeEffectShader( _program.handle, _uniformData, childShaders, - childCount, + _childShaders.length, ); }); } @@ -224,7 +263,8 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader { ShaderHandle _handle = nullptr; final SkwasmFragmentProgram _program; SkDataHandle _uniformData; - final List? _childShaders; + final int _floatUniformCount; + final List _childShaders; @override void setFloat(int index, double value) { @@ -240,7 +280,20 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader { @override void setImageSampler(int index, ui.Image image) { - // TODO(jacksongardner): implement this when images are implemented + final SkwasmImageShader shader = SkwasmImageShader.imageShader( + image as SkwasmImage, + ui.TileMode.clamp, + ui.TileMode.clamp, + toMatrix64(Matrix4.identity().storage), + ui.FilterQuality.none, + ); + final SkwasmShader? oldShader = _childShaders[index]; + _childShaders[index] = shader; + oldShader?.dispose(); + + final Pointer dataPointer = skDataGetPointer(_uniformData).cast(); + dataPointer[_floatUniformCount + index * 2] = image.width.toDouble(); + dataPointer[_floatUniformCount + index * 2 + 1] = image.height.toDouble(); } @override diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 722fb32e99886..0d1adf96304c8 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -27,40 +27,6 @@ constexpr SkScalar kShadowLightRadius = 1.1; constexpr SkScalar kShadowLightHeight = 600.0; constexpr SkScalar kShadowLightXOffset = 0; constexpr SkScalar kShadowLightYOffset = -450; - -// This needs to be kept in sync with the "FilterQuality" enum in dart:ui -enum class _FilterQuality { - none, - low, - medium, - high, -}; - -SkFilterMode _filterModeForQuality(_FilterQuality quality) { - switch (quality) { - case _FilterQuality::none: - case _FilterQuality::low: - return SkFilterMode::kNearest; - case _FilterQuality::medium: - case _FilterQuality::high: - return SkFilterMode::kLinear; - } -} - -SkSamplingOptions _samplingOptionsForQuality(_FilterQuality quality) { - switch (quality) { - case _FilterQuality::none: - return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); - case _FilterQuality::low: - return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNearest); - case _FilterQuality::medium: - return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); - case _FilterQuality::high: - // Cubic equation coefficients recommended by Mitchell & Netravali - // in their paper on cubic interpolation. - return SkSamplingOptions(SkCubicResampler::Mitchell()); - } -} } // namespace SKWASM_EXPORT void canvas_saveLayer(SkCanvas* canvas, @@ -234,9 +200,9 @@ SKWASM_EXPORT void canvas_drawImage(SkCanvas* canvas, SkScalar offsetX, SkScalar offsetY, SkPaint* paint, - _FilterQuality quality) { + FilterQuality quality) { canvas->drawImage(image, offsetX, offsetY, - _samplingOptionsForQuality(quality), paint); + samplingOptionsForQuality(quality), paint); } SKWASM_EXPORT void canvas_drawImageRect(SkCanvas* canvas, @@ -244,9 +210,9 @@ SKWASM_EXPORT void canvas_drawImageRect(SkCanvas* canvas, SkRect* sourceRect, SkRect* destRect, SkPaint* paint, - _FilterQuality quality) { + FilterQuality quality) { canvas->drawImageRect(image, *sourceRect, *destRect, - _samplingOptionsForQuality(quality), paint, + samplingOptionsForQuality(quality), paint, SkCanvas::kStrict_SrcRectConstraint); } @@ -255,9 +221,9 @@ SKWASM_EXPORT void canvas_drawImageNine(SkCanvas* canvas, SkIRect* centerRect, SkRect* destinationRect, SkPaint* paint, - _FilterQuality quality) { + FilterQuality quality) { canvas->drawImageNine(image, *centerRect, *destinationRect, - _filterModeForQuality(quality), paint); + filterModeForQuality(quality), paint); } SKWASM_EXPORT void canvas_getTransform(SkCanvas* canvas, SkM44* outTransform) { diff --git a/lib/web_ui/skwasm/helpers.h b/lib/web_ui/skwasm/helpers.h index f32055f9e44cc..c30122f6b3ab4 100644 --- a/lib/web_ui/skwasm/helpers.h +++ b/lib/web_ui/skwasm/helpers.h @@ -6,6 +6,7 @@ #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkSamplingOptions.h" namespace Skwasm { @@ -23,4 +24,37 @@ inline SkRRect createRRect(const SkScalar* f) { return rr; } +// This needs to be kept in sync with the "FilterQuality" enum in dart:ui +enum class FilterQuality { + none, + low, + medium, + high, +}; + +inline SkFilterMode filterModeForQuality(FilterQuality quality) { + switch (quality) { + case FilterQuality::none: + case FilterQuality::low: + return SkFilterMode::kNearest; + case FilterQuality::medium: + case FilterQuality::high: + return SkFilterMode::kLinear; + } +} + +inline SkSamplingOptions samplingOptionsForQuality(FilterQuality quality) { + switch (quality) { + case FilterQuality::none: + return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); + case FilterQuality::low: + return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNearest); + case FilterQuality::medium: + return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); + case FilterQuality::high: + // Cubic equation coefficients recommended by Mitchell & Netravali + // in their paper on cubic interpolation. + return SkSamplingOptions(SkCubicResampler::Mitchell()); + } +} } // namespace Skwasm diff --git a/lib/web_ui/skwasm/shaders.cpp b/lib/web_ui/skwasm/shaders.cpp index 5a521f8184512..8af24df1d5318 100644 --- a/lib/web_ui/skwasm/shaders.cpp +++ b/lib/web_ui/skwasm/shaders.cpp @@ -4,6 +4,7 @@ #include "export.h" #include "helpers.h" +#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "third_party/skia/include/effects/SkRuntimeEffect.h" #include "wrappers.h" @@ -136,3 +137,27 @@ SKWASM_EXPORT SkShader* shader_createRuntimeEffectShader( childPointers.data(), childCount, nullptr) .release(); } + +SKWASM_EXPORT SkShader* shader_createFromImage( + SkImage *image, + SkTileMode tileModeX, + SkTileMode tileModeY, + FilterQuality quality, + SkScalar* matrix33 +) { + if (matrix33) { + SkMatrix localMatrix = createMatrix(matrix33); + return image->makeShader( + tileModeX, + tileModeY, + samplingOptionsForQuality(quality), + &localMatrix + ).release(); + } else { + return image->makeShader( + tileModeX, + tileModeY, + samplingOptionsForQuality(quality) + ).release(); + } +} diff --git a/lib/web_ui/test/common/fake_asset_manager.dart b/lib/web_ui/test/common/fake_asset_manager.dart index 2aea72c30315d..8b62c1ead6854 100644 --- a/lib/web_ui/test/common/fake_asset_manager.dart +++ b/lib/web_ui/test/common/fake_asset_manager.dart @@ -18,11 +18,11 @@ class FakeAssetManager implements AssetManager { @override Future load(String assetKey) async { - final ByteData? data = _assetMap[assetKey]; - if (data == null) { + final ByteData? assetData = await _currentScope?.getAssetData(assetKey); + if (assetData == null) { throw HttpFetchNoPayloadError(assetKey, status: 404); } - return data; + return assetData; } @override @@ -55,12 +55,7 @@ class FakeAssetManager implements AssetManager { _currentScope = scope._parent; } - void setAsset(String assetKey, ByteData assetData) { - _assetMap[assetKey] = assetData; - } - FakeAssetScope? _currentScope; - final Map _assetMap = {}; } class FakeAssetScope { diff --git a/lib/web_ui/test/ui/fragment_shader_test.dart b/lib/web_ui/test/ui/fragment_shader_test.dart index da69653d0a237..fc456dc68a794 100644 --- a/lib/web_ui/test/ui/fragment_shader_test.dart +++ b/lib/web_ui/test/ui/fragment_shader_test.dart @@ -48,11 +48,20 @@ Future testMain() async { const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300); - test('fragment shader', () async { - fakeAssetManager.setAsset( + late FakeAssetScope assetScope; + setUp(() { + assetScope = fakeAssetManager.pushAssetScope(); + assetScope.setAsset( 'voronoi_shader', Uint8List.fromList(utf8.encode(kVoronoiShaderSksl)).buffer.asByteData() ); + }); + + tearDown(() { + fakeAssetManager.popAssetScope(assetScope); + }); + + test('fragment shader', () async { final ui.FragmentProgram program = await renderer.createFragmentProgram('voronoi_shader'); final ui.FragmentShader shader = program.fragmentShader(); diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 86524c14c0193..0de5f2830664b 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -2,15 +2,57 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; +import 'dart:typed_data'; + import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; +import '../common/fake_asset_manager.dart'; import '../common/test_initialization.dart'; import 'utils.dart'; +const String kGlitchShaderSksl = r''' +{ + "sksl": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 uResolution;\nuniform float uTime;\nuniform shader uTex;\nuniform half2 uTex_size;\n\nvec4 oColor;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nfloat FLT_flutter_local_cubicPulse(float c, float w, inout float x)\n{\n x = abs(x - c);\n if (x > w)\n {\n return 0.0;\n }\n x /= w;\n return 1.0 - ((x * x) * (3.0 - (2.0 * x)));\n}\n\nfloat FLT_flutter_local_twoSin(inout float x)\n{\n x = (6.4899997711181640625 * x) - 0.64999997615814208984375;\n float t = ((-0.699999988079071044921875) * sin(6.80000019073486328125 * x)) + (1.39999997615814208984375 * sin(2.900000095367431640625 * x));\n t = (t / 4.099999904632568359375) + 0.5;\n return t;\n}\n\nfloat FLT_flutter_local_hash_1d(float v)\n{\n float u = 50.0 * sin(v * 3000.0);\n return (2.0 * fract((2.0 * u) * u)) - 1.0;\n}\n\nvoid FLT_main()\n{\n vec2 uv = vec2(FLT_flutter_local_FlutterFragCoord()) / uResolution;\n float param = 0.5;\n float param_1 = 0.0500000007450580596923828125;\n float param_2 = fract(uTime / 4.0);\n float _127 = FLT_flutter_local_cubicPulse(param, param_1, param_2);\n float t_2 = _127;\n float param_3 = fract(uTime / 5.0);\n float _134 = FLT_flutter_local_twoSin(param_3);\n float t_1 = _134;\n float glitchScale = mix(0.0, 8.0, t_1 + t_2);\n float aberrationSize = mix(0.0, 5.0, t_1 + t_2);\n float param_4 = uv.y;\n float h = FLT_flutter_local_hash_1d(param_4);\n float hs = sign(h);\n h = max(h, 0.0);\n h *= h;\n h = floor(h + float(0.5)) * hs;\n uv += (vec2(h * glitchScale, 0.0) / uResolution);\n vec2 redOffset = vec2(aberrationSize, 0.0) / uResolution;\n vec2 greenOffset = vec2(0.0) / uResolution;\n vec2 blueOffset = vec2(-aberrationSize, 0.0) / uResolution;\n vec2 redUv = uv + redOffset;\n vec2 greenUv = uv + greenOffset;\n vec2 blueUv = uv + blueOffset;\n vec2 ra = uTex.eval(uTex_size * redUv).xw;\n vec2 ga = uTex.eval(uTex_size * greenUv).yw;\n vec2 ba = uTex.eval(uTex_size * blueUv).zw;\n ra.x /= ra.y;\n ga.x /= ga.y;\n ba.x /= ba.y;\n float alpha = max(ra.y, max(ga.y, ba.y));\n oColor = vec4(ra.x, ga.x, ba.x, 1.0) * alpha;\n}\n\nhalf4 main(float2 iFragCoord)\n{\n flutter_FragCoord = float4(iFragCoord, 0, 0);\n FLT_main();\n return oColor;\n}\n", + "stage": 1, + "target_platform": 2, + "uniforms": [ + { + "array_elements": 0, + "bit_width": 32, + "columns": 1, + "location": 0, + "name": "uResolution", + "rows": 2, + "type": 10 + }, + { + "array_elements": 0, + "bit_width": 32, + "columns": 1, + "location": 1, + "name": "uTime", + "rows": 1, + "type": 10 + }, + { + "array_elements": 0, + "bit_width": 0, + "columns": 1, + "location": 2, + "name": "uTex", + "rows": 1, + "type": 12 + } + ] +} +'''; + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -20,6 +62,19 @@ Future testMain() async { setUpTestViewDimensions: false, ); + late FakeAssetScope assetScope; + setUp(() { + assetScope = fakeAssetManager.pushAssetScope(); + assetScope.setAsset( + 'glitch_shader', + Uint8List.fromList(utf8.encode(kGlitchShaderSksl)).buffer.asByteData() + ); + }); + + tearDown(() { + fakeAssetManager.popAssetScope(assetScope); + }); + const ui.Rect drawRegion = ui.Rect.fromLTWH(0, 0, 300, 300); const ui.Rect imageRegion = ui.Rect.fromLTWH(0, 0, 150, 150); @@ -78,6 +133,29 @@ Future testMain() async { await matchGoldenFile('${name}_canvas_drawImageNine.png', region: drawRegion); }); + + test('fragment_shader_sampler', () async { + final ui.FragmentProgram program = await renderer.createFragmentProgram('glitch_shader'); + final ui.FragmentShader shader = program.fragmentShader(); + + // Resolution + shader.setFloat(0, 300); + shader.setFloat(1, 300); + + // Time + shader.setFloat(2, 2); + + // Image + shader.setImageSampler(0, await imageGenerator()); + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); + canvas.drawCircle(const ui.Offset(150, 150), 100, ui.Paint()..shader = shader); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + + await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion); + }, skip: isHtml); // HTML doesn't support fragment shaders }); } From 7f56e89143ad4450b112ed6318498159efada6c4 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 22:38:49 -0700 Subject: [PATCH 04/10] Add test for image shader. --- lib/web_ui/test/ui/image_golden_test.dart | 31 ++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 0de5f2830664b..8a6bdf399fc96 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:convert'; +import 'dart:math'; import 'dart:typed_data'; import 'package:test/bootstrap/browser.dart'; @@ -134,7 +135,35 @@ Future testMain() async { await matchGoldenFile('${name}_canvas_drawImageNine.png', region: drawRegion); }); + test('image_shader_cubic_rotated', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + + final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64(); + final ui.ImageShader shader = ui.ImageShader( + image, + ui.TileMode.repeated, + ui.TileMode.repeated, + matrix, + filterQuality: ui.FilterQuality.high, + ); + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); + canvas.drawOval( + const ui.Rect.fromLTRB(0, 50, 300, 250), + ui.Paint()..shader = shader + ); + + await drawPictureUsingCurrentRenderer(recorder.endRecording()); + await matchGoldenFile('${name}_image_shader_cubic_rotated.png', region: drawRegion); + }); + test('fragment_shader_sampler', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + final ui.FragmentProgram program = await renderer.createFragmentProgram('glitch_shader'); final ui.FragmentShader shader = program.fragmentShader(); @@ -146,7 +175,7 @@ Future testMain() async { shader.setFloat(2, 2); // Image - shader.setImageSampler(0, await imageGenerator()); + shader.setImageSampler(0, image); final ui.PictureRecorder recorder = ui.PictureRecorder(); final ui.Canvas canvas = ui.Canvas(recorder, drawRegion); From 112281457504e7177115f3599a0292f3fd8509e9 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Wed, 10 May 2023 14:17:48 -0700 Subject: [PATCH 05/10] Partway through image rasterization. --- .../engine/skwasm/skwasm_impl/surface.dart | 12 +-- lib/web_ui/skwasm/surface.cpp | 76 +++++++++++++------ 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart index 9e98c8cbf090b..9b5eae7aad407 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart @@ -22,7 +22,7 @@ class SkwasmSurface { SkwasmSurface._fromHandle(this._handle); final SurfaceHandle _handle; OnRenderCallbackHandle _callbackHandle = nullptr; - final Map> _pendingRenders = >{}; + final Map> _pendingCallbacks = >{}; void _initialize() { _callbackHandle = @@ -39,15 +39,15 @@ class SkwasmSurface { surfaceSetCanvasSize(_handle, width, height); Future renderPicture(SkwasmPicture picture) { - final int renderId = surfaceRenderPicture(_handle, picture.handle); + final int callbackId = surfaceRenderPicture(_handle, picture.handle); final Completer completer = Completer(); - _pendingRenders[renderId] = completer; + _pendingCallbacks[callbackId] = completer; return completer.future; } - void _onRender(JSNumber jsRenderId) { - final int renderId = jsRenderId.toDart.toInt(); - final Completer completer = _pendingRenders.remove(renderId)!; + void _onRender(JSNumber jsCallbackId) { + final int callbackId = jsCallbackId.toDart.toInt(); + final Completer completer = _pendingCallbacks.remove(callbackId)!; completer.complete(); } diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index 4c03ca61d15d7..a514970bcadc6 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -20,18 +20,25 @@ using namespace Skwasm; -using OnRenderCompleteCallback = void(uint32_t); - namespace { 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 renderId); -void fOnRenderComplete(Surface* surface, uint32_t renderId); +void fNotifyRenderComplete(Surface* surface, uint32_t callbackId); +void fOnRenderComplete(Surface* surface, uint32_t callbackId); +void fRasterizeImage( + Surface *surface, + SkImage* image, + int width, + int height, + SkColorType colorType, + uint32_t callbackId); class Surface { public: + using CallbackHandler = void(uint32_t, void*); + // Main thread only Surface(const char* canvasID) : _canvasID(canvasID) { pthread_attr_t attr; @@ -64,7 +71,7 @@ class Surface { // Main thread only uint32_t renderPicture(SkPicture* picture) { - uint32_t renderId = ++_currentRenderId; + uint32_t callbackId = ++_currentCallbackId; picture->ref(); emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII, reinterpret_cast(fRenderPicture), @@ -77,13 +84,22 @@ class Surface { emscripten_dispatch_to_thread( _thread, EM_FUNC_SIG_VII, reinterpret_cast(fNotifyRenderComplete), nullptr, this, - renderId); - return renderId; + callbackId); + return callbackId; + } + + uint32_t rasterizeImage(SkImage *image) { + uint32_t callbackId = ++_currentCallbackId; + image->ref(); + + emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII, + reinterpret_cast(fRasterizeImage), + nullptr, this, image); } // Main thread only - void setOnRenderCallback(OnRenderCompleteCallback* callback) { - _onRenderCompleteCallback = callback; + void setCallbackHandler(CallbackHandler* callbackHandler) { + _callbackHandler = callbackHandler; } private: @@ -176,20 +192,26 @@ class Surface { _surface->flush(); } + void _rasterizeImage(SkImage *image, uint32_t callbackId) { + image->readPixels( + _grContext.get(), + ); + } + // Worker thread only - void _notifyRenderComplete(uint32_t renderId) { + void _notifyRenderComplete(uint32_t callbackId) { emscripten_sync_run_in_main_runtime_thread( - EM_FUNC_SIG_VII, fOnRenderComplete, this, renderId); + EM_FUNC_SIG_VII, fOnRenderComplete, this, callbackId); } // Main thread only - void _onRenderComplete(uint32_t renderId) { - _onRenderCompleteCallback(renderId); + void _onRenderComplete(uint32_t callbackId) { + _callbackHandler(callbackId, nullptr); } std::string _canvasID; - OnRenderCompleteCallback* _onRenderCompleteCallback = nullptr; - uint32_t _currentRenderId = 0; + CallbackHandler* _callbackHandler = nullptr; + uint32_t _currentCallbackId = 0; int _canvasWidth = 0; int _canvasHeight = 0; @@ -206,8 +228,9 @@ class Surface { 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 renderId); - friend void fOnRenderComplete(Surface* surface, uint32_t renderId); + friend void fNotifyRenderComplete(Surface* surface, uint32_t callbackId); + friend void fOnRenderComplete(Surface* surface, uint32_t callbackId); + friend void fRasterizeImage(Surface *surface, SkImage* image, uint32_t callbackId); }; void fDispose(Surface* surface) { @@ -223,12 +246,17 @@ void fRenderPicture(Surface* surface, SkPicture* picture) { picture->unref(); } -void fNotifyRenderComplete(Surface* surface, uint32_t renderId) { - surface->_notifyRenderComplete(renderId); +void fNotifyRenderComplete(Surface* surface, uint32_t callbackId) { + surface->_notifyRenderComplete(callbackId); +} + +void fOnRenderComplete(Surface* surface, uint32_t callbackId) { + surface->_onRenderComplete(callbackId); } -void fOnRenderComplete(Surface* surface, uint32_t renderId) { - surface->_onRenderComplete(renderId); +void fRasterizeImage(Surface *surface, SkImage* image, uint32_t callbackId) { + surface->_rasterizeImage(image, callbackId); + image->unref(); } } // namespace @@ -236,10 +264,10 @@ SKWASM_EXPORT Surface* surface_createFromCanvas(const char* canvasID) { return new Surface(canvasID); } -SKWASM_EXPORT void surface_setOnRenderCallback( +SKWASM_EXPORT void surface_setCallbackHandler( Surface* surface, - OnRenderCompleteCallback* callback) { - surface->setOnRenderCallback(callback); + Surface::CallbackHandler* callbackHandler) { + surface->setCallbackHandler(callbackHandler); } SKWASM_EXPORT void surface_destroy(Surface* surface) { From 0544f10e7545c6eb4213edbb157e74458395eb34 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Wed, 10 May 2023 22:36:20 -0700 Subject: [PATCH 06/10] Implement toByteData on images. --- lib/web_ui/lib/painting.dart | 1 + .../src/engine/skwasm/skwasm_impl/image.dart | 3 +- .../skwasm/skwasm_impl/raw/raw_skdata.dart | 6 ++ .../skwasm/skwasm_impl/raw/raw_surface.dart | 15 ++- .../engine/skwasm/skwasm_impl/surface.dart | 40 ++++++-- lib/web_ui/skwasm/canvas.cpp | 4 +- lib/web_ui/skwasm/data.cpp | 8 ++ lib/web_ui/skwasm/image.cpp | 3 +- lib/web_ui/skwasm/shaders.cpp | 30 +++--- lib/web_ui/skwasm/surface.cpp | 95 +++++++++++++++---- lib/web_ui/test/ui/image_golden_test.dart | 14 +++ 11 files changed, 169 insertions(+), 50 deletions(-) diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 5ee674fe15701..a7960c1b4399f 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -439,6 +439,7 @@ enum ColorSpace { extendedSRGB, } +// This must be kept in sync with the `ImageByteFormat` enum in Skwasm's surface.cpp. enum ImageByteFormat { rawRgba, rawStraightRgba, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index 48bb100e5e4ce..740482472f921 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; +import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; @@ -22,7 +23,7 @@ class SkwasmImage implements ui.Image { @override Future toByteData( {ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) { - throw UnimplementedError(); + return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format); } @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart index d212fa56968e9..8d25154f870df 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skdata.dart @@ -16,5 +16,11 @@ external SkDataHandle skDataCreate(int size); @Native Function(SkDataHandle)>(symbol: 'skData_getPointer', isLeaf: true) external Pointer skDataGetPointer(SkDataHandle handle); +@Native Function(SkDataHandle)>(symbol: 'skData_getConstPointer', isLeaf: true) +external Pointer skDataGetConstPointer(SkDataHandle handle); + +@Native(symbol: 'skData_getSize', isLeaf: true) +external int skDataGetSize(SkDataHandle handle); + @Native(symbol: 'skData_dispose', isLeaf: true) external void skDataDispose(SkDataHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart index 13ba6ac6078c1..e9c58b1f21864 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart @@ -22,9 +22,9 @@ external SurfaceHandle surfaceCreateFromCanvas( ); @Native( - symbol: 'surface_setOnRenderCallback', + symbol: 'surface_setCallbackHandler', isLeaf: true) -external void surfaceSetOnRenderCallback( +external void surfaceSetCallbackHandler( SurfaceHandle surface, OnRenderCallbackHandle callback, ); @@ -47,3 +47,14 @@ external void surfaceSetCanvasSize( symbol: 'surface_renderPicture', isLeaf: true) external int surfaceRenderPicture(SurfaceHandle surface, PictureHandle picture); + +@Native(symbol: 'surface_rasterizeImage', isLeaf: true) +external int surfaceRasterizeImage( + SurfaceHandle handle, + ImageHandle image, + int format, +); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart index 9b5eae7aad407..79fa506a9d099 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart @@ -5,8 +5,10 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:js_interop'; +import 'dart:typed_data'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; +import 'package:ui/ui.dart' as ui; class SkwasmSurface { factory SkwasmSurface(String canvasQuerySelector) { @@ -22,17 +24,17 @@ class SkwasmSurface { SkwasmSurface._fromHandle(this._handle); final SurfaceHandle _handle; OnRenderCallbackHandle _callbackHandle = nullptr; - final Map> _pendingCallbacks = >{}; + final Map> _pendingCallbacks = >{}; void _initialize() { _callbackHandle = OnRenderCallbackHandle.fromAddress( skwasmInstance.addFunction( - _onRender.toJS, - 'vi'.toJS + _callbackHandler.toJS, + 'vii'.toJS ).toDart.toInt() ); - surfaceSetOnRenderCallback(_handle, _callbackHandle); + surfaceSetCallbackHandler(_handle, _callbackHandle); } void setSize(int width, int height) => @@ -40,15 +42,37 @@ class SkwasmSurface { Future renderPicture(SkwasmPicture picture) { final int callbackId = surfaceRenderPicture(_handle, picture.handle); - final Completer completer = Completer(); + return _registerCallback(callbackId); + } + + Future rasterizeImage(SkwasmImage image, ui.ImageByteFormat format) async { + final int callbackId = surfaceRasterizeImage( + _handle, + image.handle, + format.index, + ); + final int context = await _registerCallback(callbackId); + final SkDataHandle dataHandle = SkDataHandle.fromAddress(context); + final int byteCount = skDataGetSize(dataHandle); + final Pointer dataPointer = skDataGetConstPointer(dataHandle).cast(); + final Uint8List output = Uint8List(byteCount); + for (int i = 0; i < byteCount; i++) { + output[i] = dataPointer[i]; + } + skDataDispose(dataHandle); + return ByteData.sublistView(output); + } + + Future _registerCallback(int callbackId) { + final Completer completer = Completer(); _pendingCallbacks[callbackId] = completer; return completer.future; } - void _onRender(JSNumber jsCallbackId) { + void _callbackHandler(JSNumber jsCallbackId, JSNumber jsPointer) { final int callbackId = jsCallbackId.toDart.toInt(); - final Completer completer = _pendingCallbacks.remove(callbackId)!; - completer.complete(); + final Completer completer = _pendingCallbacks.remove(callbackId)!; + completer.complete(jsPointer.toDart.toInt()); } void dispose() { diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 0d1adf96304c8..d07295259aaa7 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -201,8 +201,8 @@ SKWASM_EXPORT void canvas_drawImage(SkCanvas* canvas, SkScalar offsetY, SkPaint* paint, FilterQuality quality) { - canvas->drawImage(image, offsetX, offsetY, - samplingOptionsForQuality(quality), paint); + canvas->drawImage(image, offsetX, offsetY, samplingOptionsForQuality(quality), + paint); } SKWASM_EXPORT void canvas_drawImageRect(SkCanvas* canvas, diff --git a/lib/web_ui/skwasm/data.cpp b/lib/web_ui/skwasm/data.cpp index 9c5e860d6d635..1f1b9b748aadc 100644 --- a/lib/web_ui/skwasm/data.cpp +++ b/lib/web_ui/skwasm/data.cpp @@ -14,6 +14,14 @@ SKWASM_EXPORT void* skData_getPointer(SkData* data) { return data->writable_data(); } +SKWASM_EXPORT const void* skData_getConstPointer(SkData* data) { + return data->data(); +} + +SKWASM_EXPORT size_t skData_getSize(SkData* data) { + return data->size(); +} + SKWASM_EXPORT void skData_dispose(SkData* data) { return data->unref(); } diff --git a/lib/web_ui/skwasm/image.cpp b/lib/web_ui/skwasm/image.cpp index 16097738e03c6..1d3ae57a096f7 100644 --- a/lib/web_ui/skwasm/image.cpp +++ b/lib/web_ui/skwasm/image.cpp @@ -15,7 +15,8 @@ SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, int32_t height) { picture->ref(); return DeferredFromPicture(sk_sp(picture), {width, height}, - nullptr, nullptr, BitDepth::kU8, SkColorSpace::MakeSRGB()) + nullptr, nullptr, BitDepth::kU8, + SkColorSpace::MakeSRGB()) .release(); } diff --git a/lib/web_ui/skwasm/shaders.cpp b/lib/web_ui/skwasm/shaders.cpp index 8af24df1d5318..d1afdadaea64b 100644 --- a/lib/web_ui/skwasm/shaders.cpp +++ b/lib/web_ui/skwasm/shaders.cpp @@ -138,26 +138,20 @@ SKWASM_EXPORT SkShader* shader_createRuntimeEffectShader( .release(); } -SKWASM_EXPORT SkShader* shader_createFromImage( - SkImage *image, - SkTileMode tileModeX, - SkTileMode tileModeY, - FilterQuality quality, - SkScalar* matrix33 -) { +SKWASM_EXPORT SkShader* shader_createFromImage(SkImage* image, + SkTileMode tileModeX, + SkTileMode tileModeY, + FilterQuality quality, + SkScalar* matrix33) { if (matrix33) { SkMatrix localMatrix = createMatrix(matrix33); - return image->makeShader( - tileModeX, - tileModeY, - samplingOptionsForQuality(quality), - &localMatrix - ).release(); + return image + ->makeShader(tileModeX, tileModeY, samplingOptionsForQuality(quality), + &localMatrix) + .release(); } else { - return image->makeShader( - tileModeX, - tileModeY, - samplingOptionsForQuality(quality) - ).release(); + return image + ->makeShader(tileModeX, tileModeY, samplingOptionsForQuality(quality)) + .release(); } } diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index a514970bcadc6..43882936a729b 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -13,6 +13,7 @@ #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/gl/GrGLInterface.h" #include "third_party/skia/include/gpu/gl/GrGLTypes.h" @@ -21,19 +22,28 @@ 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, - int width, - int height, - SkColorType colorType, - 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: @@ -88,13 +98,14 @@ class Surface { return callbackId; } - uint32_t rasterizeImage(SkImage *image) { + uint32_t rasterizeImage(SkImage* image, ImageByteFormat format) { uint32_t callbackId = ++_currentCallbackId; image->ref(); - emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII, - reinterpret_cast(fRasterizeImage), - nullptr, this, image); + emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIIII, + reinterpret_cast(fRasterizeImage), + nullptr, this, image, format, callbackId); + return callbackId; } // Main thread only @@ -192,10 +203,37 @@ class Surface { _surface->flush(); } - void _rasterizeImage(SkImage *image, uint32_t callbackId) { - image->readPixels( - _grContext.get(), - ); + 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 @@ -230,7 +268,13 @@ class Surface { 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, 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) { @@ -254,8 +298,17 @@ void fOnRenderComplete(Surface* surface, uint32_t callbackId) { surface->_onRenderComplete(callbackId); } -void fRasterizeImage(Surface *surface, SkImage* image, uint32_t callbackId) { - surface->_rasterizeImage(image, 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 @@ -284,3 +337,9 @@ 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); +} diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 8a6bdf399fc96..6d8448f761256 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -186,6 +186,20 @@ Future testMain() async { await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion); }, skip: isHtml); // HTML doesn't support fragment shaders }); + + test('toByteData', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); + + final ByteData? rgbaData = await image.toByteData(); + expect(rgbaData, isNotNull); + expect(rgbaData!.lengthInBytes, isNonZero); + + final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png); + expect(pngData, isNotNull); + expect(pngData!.lengthInBytes, isNonZero); + }); } emitImageTests('picture_toImage', () { From fcec0ae01c12e82dc218431db0c68721aa1a4edb Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 11 May 2023 15:52:10 -0700 Subject: [PATCH 07/10] Image from pixel data. --- .../src/engine/skwasm/skwasm_impl/image.dart | 24 ++++++ .../skwasm/skwasm_impl/raw/raw_image.dart | 15 ++++ .../engine/skwasm/skwasm_impl/renderer.dart | 80 ++++++++++++++++++- lib/web_ui/skwasm/image.cpp | 18 +++++ lib/web_ui/test/ui/image_golden_test.dart | 76 +++++++++++++++++- 5 files changed, 209 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index 740482472f921..938d663bf85dc 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ffi'; import 'dart:typed_data'; import 'package:ui/src/engine.dart'; @@ -11,6 +12,29 @@ import 'package:ui/ui.dart' as ui; class SkwasmImage implements ui.Image { SkwasmImage(this.handle); + factory SkwasmImage.fromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, { + int? rowBytes, + }) { + final SkDataHandle dataHandle = skDataCreate(pixels.length); + final Pointer dataPointer = skDataGetPointer(dataHandle).cast(); + for (int i = 0; i < pixels.length; i++) { + dataPointer[i] = pixels[i]; + } + final ImageHandle imageHandle = imageCreateFromPixels( + dataHandle, + width, + height, + format == ui.PixelFormat.bgra8888, + rowBytes ?? 4 * width, + ); + skDataDispose(dataHandle); + return SkwasmImage(imageHandle); + } + final ImageHandle handle; bool _isDisposed = false; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart index 88878fc8173ea..ad9c2fa9d1a0b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart @@ -23,6 +23,21 @@ external ImageHandle imageCreateFromPicture( int height, ); +@Native(symbol: 'image_createFromPixels', isLeaf: true) +external ImageHandle imageCreateFromPixels( + SkDataHandle pixelData, + int width, + int height, + bool isBgra, + int rowByteCount, +); + @Native(symbol: 'image_dispose', isLeaf: true) external void imageDispose(ImageHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 3c56696121f47..07fb457393c6b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -295,9 +295,85 @@ class SkwasmRenderer implements Renderer { indices: indices ); + ui.Size? _scaledSize( + int width, + int height, + int? targetWidth, + int? targetHeight, + ) { + if (targetWidth == null && targetHeight == null) { + return null; + } + if (targetWidth == width && targetHeight == height) { + // Not scaled + return null; + } + if (targetWidth == null) { + if (targetHeight == height) { + // Not scaled. + return null; + } + targetWidth = (width * targetHeight! / height).round(); + } else if (targetHeight == null) { + if (targetWidth == targetWidth) { + // Not scaled. + return null; + } + targetHeight = (height * targetWidth / width).round(); + } + return ui.Size(targetWidth.toDouble(), targetHeight.toDouble()); + } + @override - void decodeImageFromPixels(Uint8List pixels, int width, int height, ui.PixelFormat format, ui.ImageDecoderCallback callback, {int? rowBytes, int? targetWidth, int? targetHeight, bool allowUpscaling = true}) { - throw UnimplementedError('decodeImageFromPixels not yet implemented'); + void decodeImageFromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, + ui.ImageDecoderCallback callback, { + int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) { + final ui.Size? scaledSize = _scaledSize( + width, + height, + targetWidth, + targetHeight + ); + if (!allowUpscaling && scaledSize != null && + (scaledSize.width > width || scaledSize.height > height)) { + domWindow.console.warn('Cannot apply targetWidth/targetHeight when allowUpscaling is false.'); + return; + } + final SkwasmImage pixelImage = SkwasmImage.fromPixels( + pixels, + width, + height, + format + ); + if (scaledSize == null) { + callback(pixelImage); + return; + } + + final ui.Rect outputRect = ui.Rect.fromLTWH(0, 0, scaledSize.width, scaledSize.height); + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder, outputRect); + + canvas.drawImageRect( + pixelImage, + ui.Rect.fromLTWH(0, 0, width.toDouble(), width.toDouble()), + outputRect, + ui.Paint(), + ); + final ui.Image finalImage = recorder.endRecording().toImageSync( + scaledSize.width.round(), + scaledSize.height.round() + ); + pixelImage.dispose(); + callback(finalImage); } @override diff --git a/lib/web_ui/skwasm/image.cpp b/lib/web_ui/skwasm/image.cpp index 1d3ae57a096f7..cafd2f67f58eb 100644 --- a/lib/web_ui/skwasm/image.cpp +++ b/lib/web_ui/skwasm/image.cpp @@ -5,7 +5,9 @@ #include "export.h" #include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkPicture.h" using namespace SkImages; @@ -20,6 +22,22 @@ SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, .release(); } +SKWASM_EXPORT SkImage* image_createFromPixels(SkData* data, + int width, + int height, + bool isBgra, + size_t rowByteCount) { + data->ref(); + return SkImages::RasterFromData( + SkImageInfo::Make(width, height, + isBgra ? SkColorType::kBGRA_8888_SkColorType + : SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType, + SkColorSpace::MakeSRGB()), + sk_sp(data), rowByteCount) + .release(); +} + SKWASM_EXPORT void image_dispose(SkImage* image) { image->unref(); } diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart index 6d8448f761256..71884e6eebe6e 100644 --- a/lib/web_ui/test/ui/image_golden_test.dart +++ b/lib/web_ui/test/ui/image_golden_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; @@ -185,9 +186,8 @@ Future testMain() async { await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion); }, skip: isHtml); // HTML doesn't support fragment shaders - }); - test('toByteData', () async { + test('toByteData_rgba', () async { final ui.Image image = await imageGenerator(); expect(image.width, 150); expect(image.height, 150); @@ -195,10 +195,17 @@ Future testMain() async { final ByteData? rgbaData = await image.toByteData(); expect(rgbaData, isNotNull); expect(rgbaData!.lengthInBytes, isNonZero); + }); + + test('toByteData_rgba', () async { + final ui.Image image = await imageGenerator(); + expect(image.width, 150); + expect(image.height, 150); final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png); expect(pngData, isNotNull); expect(pngData!.lengthInBytes, isNonZero); + }, skip: isHtml); // https://github.com/flutter/flutter/issues/126611 }); } @@ -216,4 +223,69 @@ Future testMain() async { } return recorder.endRecording().toImage(150, 150); }); + + Uint8List generatePixelData( + int width, + int height, + ui.Color Function(double, double) generator + ) { + final Uint8List data = Uint8List(width * height * 4); + int outputIndex = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + final ui.Color pixelColor = generator( + (2.0 * x / width) - 1.0, + (2.0 * y / height) - 1.0, + ); + data[outputIndex++] = pixelColor.red; + data[outputIndex++] = pixelColor.green; + data[outputIndex++] = pixelColor.blue; + data[outputIndex++] = pixelColor.alpha; + } + } + return data; + } + + emitImageTests('decodeImageFromPixels_unscaled', () { + final Uint8List pixels = generatePixelData(150, 150, (double x, double y) { + final double r = sqrt(x * x + y * y); + final double theta = atan2(x, y); + return ui.Color.fromRGBO( + (255 * (sin(r * 10.0) + 1.0) / 2.0).round(), + (255 * (sin(theta * 10.0) + 1.0) / 2.0).round(), + 0, + 1, + ); + }); + final Completer completer = Completer(); + ui.decodeImageFromPixels(pixels, 150, 150, ui.PixelFormat.rgba8888, completer.complete); + return completer.future; + }); + + // https://github.com/flutter/flutter/issues/126603 + if (!isHtml) { + emitImageTests('decodeImageFromPixels_scaled', () { + final Uint8List pixels = generatePixelData(50, 50, (double x, double y) { + final double r = sqrt(x * x + y * y); + final double theta = atan2(x, y); + return ui.Color.fromRGBO( + (255 * (sin(r * 10.0) + 1.0) / 2.0).round(), + (255 * (sin(theta * 10.0) + 1.0) / 2.0).round(), + 0, + 1, + ); + }); + final Completer completer = Completer(); + ui.decodeImageFromPixels( + pixels, + 50, + 50, + ui.PixelFormat.rgba8888, + completer.complete, + targetWidth: 150, + targetHeight: 150, + ); + return completer.future; + }); + } } From 7009a8338bd78177fcd7f4fa80680b56991ff03c Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sat, 13 May 2023 11:32:10 -0700 Subject: [PATCH 08/10] Update licenses golden. --- ci/licenses_golden/licenses_flutter | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8e31f539c527f..a8d951a137732 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2007,6 +2007,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.da ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path.dart + ../../../flutter/LICENSE @@ -2077,6 +2078,7 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/data.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/export.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/fonts.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/helpers.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/image.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/paint.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/path.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/picture.cpp + ../../../flutter/LICENSE @@ -4626,6 +4628,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path.dart @@ -4696,6 +4699,7 @@ FILE: ../../../flutter/lib/web_ui/skwasm/data.cpp FILE: ../../../flutter/lib/web_ui/skwasm/export.h FILE: ../../../flutter/lib/web_ui/skwasm/fonts.cpp FILE: ../../../flutter/lib/web_ui/skwasm/helpers.h +FILE: ../../../flutter/lib/web_ui/skwasm/image.cpp FILE: ../../../flutter/lib/web_ui/skwasm/paint.cpp FILE: ../../../flutter/lib/web_ui/skwasm/path.cpp FILE: ../../../flutter/lib/web_ui/skwasm/picture.cpp From 4a2f1e50315e219ccc1a52b200697c0ca75664a3 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 15 May 2023 17:34:58 -0700 Subject: [PATCH 09/10] Add support for rgbaFloat32 pixel format. --- lib/web_ui/lib/painting.dart | 4 +++ .../src/engine/skwasm/skwasm_impl/image.dart | 2 +- .../skwasm/skwasm_impl/raw/raw_image.dart | 4 +-- lib/web_ui/skwasm/image.cpp | 34 ++++++++++++++++--- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index a7960c1b4399f..e4b79417d42ad 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -447,9 +447,11 @@ enum ImageByteFormat { png, } +// This must be kept in sync with the `PixelFormat` enum in Skwasm's image.cpp. enum PixelFormat { rgba8888, bgra8888, + rgbaFloat32, } typedef ImageDecoderCallback = void Function(Image result); @@ -563,6 +565,8 @@ Future createBmp( swapRedBlue = true; case PixelFormat.rgba8888: swapRedBlue = false; + case PixelFormat.rgbaFloat32: + throw UnimplementedError('RGB conversion from rgbaFloat32 data is not implemented'); } // See https://en.wikipedia.org/wiki/BMP_file_format for format examples. diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index 938d663bf85dc..2045248d7730b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -28,7 +28,7 @@ class SkwasmImage implements ui.Image { dataHandle, width, height, - format == ui.PixelFormat.bgra8888, + format.index, rowBytes ?? 4 * width, ); skDataDispose(dataHandle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart index ad9c2fa9d1a0b..11db8650cdcd5 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart @@ -27,14 +27,14 @@ external ImageHandle imageCreateFromPicture( SkDataHandle, Int, Int, - Bool, + Int, Size )>(symbol: 'image_createFromPixels', isLeaf: true) external ImageHandle imageCreateFromPixels( SkDataHandle pixelData, int width, int height, - bool isBgra, + int pixelFormat, int rowByteCount, ); diff --git a/lib/web_ui/skwasm/image.cpp b/lib/web_ui/skwasm/image.cpp index cafd2f67f58eb..31c219eb014e9 100644 --- a/lib/web_ui/skwasm/image.cpp +++ b/lib/web_ui/skwasm/image.cpp @@ -12,6 +12,33 @@ using namespace SkImages; +enum class PixelFormat { + rgba8888, + bgra8888, + rgbaFloat32, +}; + +SkColorType colorTypeForPixelFormat(PixelFormat format) { + switch (format) { + case PixelFormat::rgba8888: + return SkColorType::kRGBA_8888_SkColorType; + case PixelFormat::bgra8888: + return SkColorType::kBGRA_8888_SkColorType; + case PixelFormat::rgbaFloat32: + return SkColorType::kRGBA_F32_SkColorType; + } +} + +SkAlphaType alphaTypeForPixelFormat(PixelFormat format) { + switch (format) { + case PixelFormat::rgba8888: + case PixelFormat::bgra8888: + return SkAlphaType::kPremul_SkAlphaType; + case PixelFormat::rgbaFloat32: + return SkAlphaType::kUnpremul_SkAlphaType; + } +} + SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, int32_t width, int32_t height) { @@ -25,14 +52,13 @@ SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture, SKWASM_EXPORT SkImage* image_createFromPixels(SkData* data, int width, int height, - bool isBgra, + PixelFormat pixelFormat, size_t rowByteCount) { data->ref(); return SkImages::RasterFromData( SkImageInfo::Make(width, height, - isBgra ? SkColorType::kBGRA_8888_SkColorType - : SkColorType::kRGBA_8888_SkColorType, - SkAlphaType::kPremul_SkAlphaType, + colorTypeForPixelFormat(pixelFormat), + alphaTypeForPixelFormat(pixelFormat), SkColorSpace::MakeSRGB()), sk_sp(data), rowByteCount) .release(); From fb6c2a69d151866757f8573aa835177c8e9f616b Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 15 May 2023 18:09:56 -0700 Subject: [PATCH 10/10] Addressed more of Yegor's comments. --- lib/web_ui/lib/painting.dart | 14 +++++- .../lib/src/engine/canvaskit/image.dart | 4 +- .../engine/skwasm/skwasm_impl/renderer.dart | 12 ++--- .../engine/skwasm/skwasm_impl/shaders.dart | 50 ++++++++++++------- lib/web_ui/skwasm/surface.cpp | 8 +++ 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index e4b79417d42ad..b974b4868968d 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -743,9 +743,19 @@ class Shadow { } abstract class ImageShader implements Shader { - factory ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4, { + factory ImageShader( + Image image, + TileMode tmx, + TileMode tmy, + Float64List matrix4, { FilterQuality? filterQuality, - }) => engine.renderer.createImageShader(image, tmx, tmy, matrix4, filterQuality); + }) => engine.renderer.createImageShader( + image, + tmx, + tmy, + matrix4, + filterQuality + ); @override void dispose(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index d100f5a83c5c1..c7dcd80d431f3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -62,9 +62,7 @@ void skiaDecodeImageFromPixels( } if (targetWidth != null || targetHeight != null) { - if (!validUpscale(allowUpscaling, targetWidth, targetHeight, width, height)) { - domWindow.console.warn('Cannot apply targetWidth/targetHeight when allowUpscaling is false.'); - } else { + if (validUpscale(allowUpscaling, targetWidth, targetHeight, width, height)) { return callback(scaleImage(skImage, targetWidth, targetHeight)); } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 07fb457393c6b..4a232ca5cfcae 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -301,19 +301,16 @@ class SkwasmRenderer implements Renderer { int? targetWidth, int? targetHeight, ) { - if (targetWidth == null && targetHeight == null) { - return null; - } if (targetWidth == width && targetHeight == height) { // Not scaled return null; } if (targetWidth == null) { - if (targetHeight == height) { + if (targetHeight == null || targetHeight == height) { // Not scaled. return null; } - targetWidth = (width * targetHeight! / height).round(); + targetWidth = (width * targetHeight / height).round(); } else if (targetHeight == null) { if (targetWidth == targetWidth) { // Not scaled. @@ -336,7 +333,7 @@ class SkwasmRenderer implements Renderer { int? targetHeight, bool allowUpscaling = true }) { - final ui.Size? scaledSize = _scaledSize( + ui.Size? scaledSize = _scaledSize( width, height, targetWidth, @@ -344,8 +341,7 @@ class SkwasmRenderer implements Renderer { ); if (!allowUpscaling && scaledSize != null && (scaledSize.width > width || scaledSize.height > height)) { - domWindow.console.warn('Cannot apply targetWidth/targetHeight when allowUpscaling is false.'); - return; + scaledSize = null; } final SkwasmImage pixelImage = SkwasmImage.fromPixels( pixels, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart index 58b1cb15967ef..08a867e13f423 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart @@ -162,18 +162,30 @@ class SkwasmImageShader extends SkwasmShader implements ui.ImageShader { SkwasmImage image, ui.TileMode tmx, ui.TileMode tmy, - Float64List matrix4, + Float64List? matrix4, ui.FilterQuality? filterQuality, - ) => withStackScope((StackScope scope) { - final RawMatrix33 localMatrix = scope.convertMatrix4toSkMatrix(matrix4); - return SkwasmImageShader._(shaderCreateFromImage( - image.handle, - tmx.index, - tmy.index, - (filterQuality ?? ui.FilterQuality.medium).index, - localMatrix, - )); - }); + ) { + if (matrix4 != null) { + return withStackScope((StackScope scope) { + final RawMatrix33 localMatrix = scope.convertMatrix4toSkMatrix(matrix4); + return SkwasmImageShader._(shaderCreateFromImage( + image.handle, + tmx.index, + tmy.index, + (filterQuality ?? ui.FilterQuality.medium).index, + localMatrix, + )); + }); + } else { + return SkwasmImageShader._(shaderCreateFromImage( + image.handle, + tmx.index, + tmy.index, + (filterQuality ?? ui.FilterQuality.medium).index, + nullptr, + )); + } + } @override ShaderHandle handle; @@ -214,10 +226,11 @@ class SkwasmFragmentProgram implements ui.FragmentProgram { ); } - RuntimeEffectHandle handle; - String name; - int floatUniformCount; - int childShaderCount; + final RuntimeEffectHandle handle; + final String name; + final int floatUniformCount; + final int childShaderCount; + bool _isDisposed = false; @override ui.FragmentShader fragmentShader() => SkwasmFragmentShader(this); @@ -225,7 +238,10 @@ class SkwasmFragmentProgram implements ui.FragmentProgram { int get uniformSize => runtimeEffectGetUniformSize(handle); void dispose() { - runtimeEffectDispose(handle); + if (!_isDisposed) { + runtimeEffectDispose(handle); + _isDisposed = true; + } } } @@ -284,7 +300,7 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader { image as SkwasmImage, ui.TileMode.clamp, ui.TileMode.clamp, - toMatrix64(Matrix4.identity().storage), + null, ui.FilterQuality.none, ); final SkwasmShader? oldShader = _childShaders[index]; diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index 43882936a729b..bc3e484d5ec86 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "export.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColorSpace.h" @@ -51,6 +52,8 @@ class Surface { // 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); @@ -67,6 +70,7 @@ class Surface { // 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); @@ -74,6 +78,7 @@ class Surface { // 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); @@ -81,6 +86,7 @@ class Surface { // 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, @@ -110,6 +116,7 @@ class Surface { // Main thread only void setCallbackHandler(CallbackHandler* callbackHandler) { + assert(emscripten_is_main_browser_thread()); _callbackHandler = callbackHandler; } @@ -244,6 +251,7 @@ class Surface { // Main thread only void _onRenderComplete(uint32_t callbackId) { + assert(emscripten_is_main_browser_thread()); _callbackHandler(callbackId, nullptr); }