blob: a0bfd4780da080036a182f154200949c3fe44ea1 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:js_interop';
import 'dart:math' as math;
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;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
class SkwasmRenderer implements Renderer {
late SkwasmSurface surface;
EngineSceneView? _sceneView;
@override
final SkwasmFontCollection fontCollection = SkwasmFontCollection();
@override
ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) {
return SkwasmPath.combine(op, path1 as SkwasmPath, path2 as SkwasmPath);
}
@override
ui.Path copyPath(ui.Path src) {
return SkwasmPath.from(src as SkwasmPath);
}
@override
ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) {
return SkwasmCanvas(recorder as SkwasmPictureRecorder, cullRect ?? ui.Rect.largest);
}
@override
ui.Gradient createConicalGradient(
ui.Offset focal,
double focalRadius,
ui.Offset center,
double radius,
List<ui.Color> colors, [
List<double>? colorStops,
ui.TileMode tileMode = ui.TileMode.clamp,
Float32List? matrix]) => SkwasmGradient.conical(
focal: focal,
focalRadius: focalRadius,
center: center,
centerRadius: radius,
colors: colors,
colorStops: colorStops,
tileMode: tileMode,
matrix4: matrix,
);
@override
ui.ImageFilter createBlurImageFilter({
double sigmaX = 0.0,
double sigmaY = 0.0,
ui.TileMode tileMode = ui.TileMode.clamp
}) => SkwasmImageFilter.blur(
sigmaX: sigmaX,
sigmaY: sigmaY,
tileMode: tileMode
);
@override
ui.ImageFilter createDilateImageFilter({
double radiusX = 0.0,
double radiusY = 0.0
}) => SkwasmImageFilter.dilate(
radiusX: radiusX,
radiusY: radiusY,
);
@override
ui.ImageFilter createErodeImageFilter({
double radiusX = 0.0,
double radiusY = 0.0
}) => SkwasmImageFilter.erode(
radiusX: radiusX,
radiusY: radiusY,
);
@override
ui.ImageFilter composeImageFilters({
required ui.ImageFilter outer,
required ui.ImageFilter inner
}) => SkwasmImageFilter.compose(
SkwasmImageFilter.fromUiFilter(outer),
SkwasmImageFilter.fromUiFilter(inner),
);
@override
ui.ImageFilter createMatrixImageFilter(
Float64List matrix4, {
ui.FilterQuality filterQuality = ui.FilterQuality.low
}) => SkwasmImageFilter.matrix(
matrix4,
filterQuality: filterQuality
);
@override
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(
ui.Offset from,
ui.Offset to,
List<ui.Color> colors, [
List<double>? colorStops,
ui.TileMode tileMode = ui.TileMode.clamp,
Float32List? matrix4
]) => SkwasmGradient.linear(
from: from,
to: to,
colors: colors,
colorStops: colorStops,
tileMode: tileMode,
matrix4: matrix4,
);
@override
ui.Paint createPaint() => SkwasmPaint();
@override
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) =>
SkwasmParagraphBuilder(style as SkwasmParagraphStyle, fontCollection);
@override
ui.ParagraphStyle createParagraphStyle({
ui.TextAlign? textAlign,
ui.TextDirection? textDirection,
int? maxLines, String? fontFamily,
double? fontSize,
double? height,
ui.TextHeightBehavior? textHeightBehavior,
ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle,
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale
}) => SkwasmParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
maxLines: maxLines,
fontFamily: fontFamily,
fontSize: fontSize,
height: height,
textHeightBehavior: textHeightBehavior,
fontWeight: fontWeight,
fontStyle: fontStyle,
strutStyle: strutStyle,
ellipsis: ellipsis,
locale: locale,
);
@override
ui.Path createPath() => SkwasmPath();
@override
ui.PictureRecorder createPictureRecorder() => SkwasmPictureRecorder();
@override
ui.Gradient createRadialGradient(
ui.Offset center,
double radius,
List<ui.Color> colors, [
List<double>? colorStops,
ui.TileMode tileMode = ui.TileMode.clamp,
Float32List? matrix4
]) => SkwasmGradient.radial(
center: center,
radius: radius,
colors: colors,
colorStops: colorStops,
tileMode: tileMode,
matrix4: matrix4
);
@override
ui.SceneBuilder createSceneBuilder() => EngineSceneBuilder();
@override
ui.StrutStyle createStrutStyle({
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? height,
ui.TextLeadingDistribution? leadingDistribution,
double? leading,
ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle,
bool? forceStrutHeight
}) => SkwasmStrutStyle(
fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback,
fontSize: fontSize,
height: height,
leadingDistribution: leadingDistribution,
leading: leading,
fontWeight: fontWeight,
fontStyle: fontStyle,
forceStrutHeight: forceStrutHeight,
);
@override
ui.Gradient createSweepGradient(
ui.Offset center,
List<ui.Color> colors, [
List<double>? colorStops,
ui.TileMode tileMode = ui.TileMode.clamp,
double startAngle = 0.0,
double endAngle = math.pi * 2,
Float32List? matrix4
]) => SkwasmGradient.sweep(
center: center,
colors: colors,
colorStops: colorStops,
tileMode: tileMode,
startAngle: startAngle,
endAngle: endAngle,
matrix4: matrix4
);
@override
ui.TextStyle createTextStyle({
ui.Color? color,
ui.TextDecoration? decoration,
ui.Color? decorationColor,
ui.TextDecorationStyle? decorationStyle,
double? decorationThickness,
ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle,
ui.TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
ui.TextLeadingDistribution? leadingDistribution,
ui.Locale? locale,
ui.Paint? background,
ui.Paint? foreground,
List<ui.Shadow>? shadows,
List<ui.FontFeature>? fontFeatures,
List<ui.FontVariation>? fontVariations
}) => SkwasmTextStyle(
color: color,
decoration: decoration,
decorationColor: decorationColor,
decorationStyle: decorationStyle,
decorationThickness: decorationThickness,
fontWeight: fontWeight,
fontStyle: fontStyle,
textBaseline: textBaseline,
fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback,
fontSize: fontSize,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
height: height,
leadingDistribution: leadingDistribution,
locale: locale,
background: background,
foreground: foreground,
shadows: shadows,
fontFeatures: fontFeatures,
fontVariations: fontVariations,
);
@override
ui.Vertices createVertices(
ui.VertexMode mode,
List<ui.Offset> positions,
{
List<ui.Offset>? textureCoordinates,
List<ui.Color>? colors,
List<int>? indices
}) =>
SkwasmVertices(
mode,
positions,
textureCoordinates: textureCoordinates,
colors: colors,
indices: indices
);
@override
ui.Vertices createVerticesRaw(
ui.VertexMode mode,
Float32List positions,
{
Float32List? textureCoordinates,
Int32List? colors,
Uint16List? indices
}) =>
SkwasmVertices.raw(
mode,
positions,
textureCoordinates: textureCoordinates,
colors: colors,
indices: indices
);
@override
void decodeImageFromPixels(
Uint8List pixels,
int width,
int height,
ui.PixelFormat format,
ui.ImageDecoderCallback callback, {
int? rowBytes,
int? targetWidth,
int? targetHeight,
bool allowUpscaling = true
}) {
final SkwasmImage pixelImage = SkwasmImage.fromPixels(
pixels,
width,
height,
format
);
final ui.Image scaledImage = scaleImageIfNeeded(
pixelImage,
targetWidth: targetWidth,
targetHeight: targetHeight,
allowUpscaling: allowUpscaling,
);
callback(scaledImage);
}
@override
FutureOr<void> initialize() {
surface = SkwasmSurface();
}
@override
Future<ui.Codec> instantiateImageCodec(
Uint8List list, {
int? targetWidth,
int? targetHeight,
bool allowUpscaling = true
}) async {
final String? contentType = detectContentType(list);
if (contentType == null) {
throw Exception('Could not determine content type of image from data');
}
final SkwasmImageDecoder baseDecoder = SkwasmImageDecoder(
contentType: contentType,
dataSource: list.toJS,
debugSource: 'encoded image bytes',
);
await baseDecoder.initialize();
if (targetWidth == null && targetHeight == null) {
return baseDecoder;
}
return ResizingCodec(
baseDecoder,
targetWidth: targetWidth,
targetHeight: targetHeight,
allowUpscaling: allowUpscaling
);
}
@override
Future<ui.Codec> instantiateImageCodecFromUrl(
Uri uri, {
ui_web.ImageCodecChunkCallback? chunkCallback
}) async {
final DomResponse response = await rawHttpGet(uri.toString());
final String? contentType = response.headers.get('Content-Type');
if (contentType == null) {
throw Exception('Could not determine content type of image at url $uri');
}
final SkwasmImageDecoder decoder = SkwasmImageDecoder(
contentType: contentType,
dataSource: response.body as JSObject,
debugSource: uri.toString(),
);
await decoder.initialize();
return decoder;
}
// TODO(harryterkelsen): Add multiview support,
// https://github.com/flutter/flutter/issues/137073.
@override
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) {
final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null;
recorder?.recordBuildFinish();
view as EngineFlutterView;
assert(view is EngineFlutterWindow, 'Skwasm does not support multi-view mode yet');
final EngineSceneView sceneView = _getSceneViewForView(view);
return sceneView.renderScene(scene as EngineScene, recorder);
}
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
// TODO(mdebbar): Support multi-view mode.
if (_sceneView == null) {
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface));
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
implicitView.dom.setScene(_sceneView!.sceneElement);
}
return _sceneView!;
}
@override
String get rendererTag => 'skwasm';
static final Map<String, Future<ui.FragmentProgram>> _programs = <String, Future<ui.FragmentProgram>>{};
@override
void clearFragmentProgramCache() {
_programs.clear();
}
@override
Future<ui.FragmentProgram> createFragmentProgram(String assetKey) {
if (_programs.containsKey(assetKey)) {
return _programs[assetKey]!;
}
return _programs[assetKey] = ui_web.assetManager.load(assetKey).then((ByteData data) {
return SkwasmFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List());
});
}
@override
ui.LineMetrics createLineMetrics({
required bool hardBreak,
required double ascent,
required double descent,
required double unscaledAscent,
required double height,
required double width,
required double left,
required double baseline,
required int lineNumber
}) => SkwasmLineMetrics(
hardBreak: hardBreak,
ascent: ascent,
descent: descent,
unscaledAscent: unscaledAscent,
height: height,
width: width,
left: left,
baseline: baseline,
lineNumber: lineNumber
);
@override
ui.Image createImageFromImageBitmap(DomImageBitmap imageSource) {
return SkwasmImage(imageCreateFromTextureSource(
imageSource as JSObject,
imageSource.width.toDartInt,
imageSource.height.toDartInt,
surface.handle,
));
}
}
class SkwasmPictureRenderer implements PictureRenderer {
SkwasmPictureRenderer(this.surface);
SkwasmSurface surface;
@override
FutureOr<RenderResult> renderPictures(List<ScenePicture> pictures) =>
surface.renderPictures(pictures.cast<SkwasmPicture>());
}