diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d8405fe3ba39d..2cd98cbbf7ff4 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -36,6 +36,8 @@ FILE: ../../../flutter/flow/compositor_context.cc FILE: ../../../flutter/flow/compositor_context.h FILE: ../../../flutter/flow/diff_context.cc FILE: ../../../flutter/flow/diff_context.h +FILE: ../../../flutter/flow/display_list_interpreter.cc +FILE: ../../../flutter/flow/display_list_interpreter.h FILE: ../../../flutter/flow/embedded_view_params_unittests.cc FILE: ../../../flutter/flow/embedded_views.cc FILE: ../../../flutter/flow/embedded_views.h @@ -69,6 +71,8 @@ FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/container_layer.cc FILE: ../../../flutter/flow/layers/container_layer.h FILE: ../../../flutter/flow/layers/container_layer_unittests.cc +FILE: ../../../flutter/flow/layers/display_list_layer.cc +FILE: ../../../flutter/flow/layers/display_list_layer.h FILE: ../../../flutter/flow/layers/fuchsia_layer_unittests.cc FILE: ../../../flutter/flow/layers/image_filter_layer.cc FILE: ../../../flutter/flow/layers/image_filter_layer.h @@ -341,6 +345,8 @@ FILE: ../../../flutter/lib/ui/painting/codec.cc FILE: ../../../flutter/lib/ui/painting/codec.h FILE: ../../../flutter/lib/ui/painting/color_filter.cc FILE: ../../../flutter/lib/ui/painting/color_filter.h +FILE: ../../../flutter/lib/ui/painting/display_list.cc +FILE: ../../../flutter/lib/ui/painting/display_list.h FILE: ../../../flutter/lib/ui/painting/engine_layer.cc FILE: ../../../flutter/lib/ui/painting/engine_layer.h FILE: ../../../flutter/lib/ui/painting/gradient.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 1efd32e6cb622..656c2988af8f9 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -12,6 +12,8 @@ source_set("flow") { "compositor_context.h", "diff_context.cc", "diff_context.h", + "display_list_interpreter.cc", + "display_list_interpreter.h", "embedded_views.cc", "embedded_views.h", "frame_timings.cc", @@ -30,6 +32,8 @@ source_set("flow") { "layers/color_filter_layer.h", "layers/container_layer.cc", "layers/container_layer.h", + "layers/display_list_layer.cc", + "layers/display_list_layer.h", "layers/image_filter_layer.cc", "layers/image_filter_layer.h", "layers/layer.cc", diff --git a/flow/display_list_interpreter.cc b/flow/display_list_interpreter.cc new file mode 100644 index 0000000000000..dfdb7bbe3fa86 --- /dev/null +++ b/flow/display_list_interpreter.cc @@ -0,0 +1,537 @@ +// 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 "flutter/flow/display_list_interpreter.h" +#include "flutter/fml/logging.h" + +#include "flutter/flow/layers/physical_shape_layer.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace flutter { + +#ifndef NDEBUG + +#define CANVAS_OP_TAKE_STRING(name, arg) #name, +const std::vector DisplayListInterpreter::opNames = { + FOR_EACH_CANVAS_OP(CANVAS_OP_TAKE_STRING) // +}; + +#endif // NDEBUG + +#define CANVAS_OP_TAKE_ARGS(name, args) CANVAS_OP_ARGS_##args, +const std::vector DisplayListInterpreter::opArguments = { + FOR_EACH_CANVAS_OP(CANVAS_OP_TAKE_ARGS) // +}; + +DisplayListInterpreter::DisplayListInterpreter(const DisplayListData& data) + : ops_vector_(data.ops_vector), + data_vector_(data.data_vector), + ref_vector_(data.ref_vector) {} + +DisplayListInterpreter::DisplayListInterpreter( + std::shared_ptr> ops_vector, + std::shared_ptr> data_vector, + std::shared_ptr> ref_vector) + : ops_vector_(std::move(ops_vector)), + data_vector_(std::move(data_vector)), + ref_vector_(std::move(ref_vector)) {} + +#ifndef NDEBUG + +void DisplayListInterpreter::Describe() { + Iterator it(this); + FML_LOG(INFO) << "Starting ops: " << (it.ops_end - it.ops) + << ", data: " << (it.data_end - it.data) + << ", refs: " << (it.refs_end - it.refs); + while (it.HasOp()) { + FML_LOG(INFO) << DescribeOneOp(it); + } + FML_LOG(INFO) << "Remaining ops: " << (it.ops_end - it.ops) + << ", data: " << (it.data_end - it.data) + << ", refs: " << (it.refs_end - it.refs); +} + +std::string DisplayListInterpreter::DescribeNextOp(const Iterator& it) { + Iterator it_copy(it); + return DescribeOneOp(it_copy); +} + +std::string DisplayListInterpreter::DescribeOneOp(Iterator& it) { + if (!it.HasOp()) { + return "END-OF-LIST"; + } + std::stringstream ss; + CanvasOp op = it.GetOp(); + ss << opNames[op] << "("; + bool first = true; + for (uint32_t arg_types = opArguments[op]; arg_types != 0; + arg_types >>= CANVAS_OP_ARG_SHIFT) { + if (first) { + first = false; + } else { + ss << ", "; + } + CanvasOpArg arg_type = + static_cast(arg_types & CANVAS_OP_ARG_MASK); + switch (arg_type) { + case empty: + FML_DCHECK(false); + ss << "?none?"; + break; + case scalar: + ss << it.GetScalar(); + break; + case color: + ss << "Color(" << std::hex << "0x" << it.GetUint32() << ")"; + break; + case blend_mode: + ss << "BlendMode(" << std::dec << it.GetUint32() << ")"; + break; + case angle: + ss << it.GetDegrees(); + break; + case point: + ss << "Point(x: " << it.GetScalar() << ", y: " << it.GetScalar() << ")"; + break; + case rect: + ss << "Rect(l: " << it.GetScalar() << ", t: " << it.GetScalar() + << ", r: " << it.GetScalar() << ", b: " << it.GetScalar() << ")"; + break; + case round_rect: + ss << "RoundRect(" + << "Rect(l: " << it.GetScalar() << ", t: " << it.GetScalar() + << ", r: " << it.GetScalar() << ", b: " << it.GetScalar() << "), " + << "ul: (h: " << it.GetScalar() << ", v: " << it.GetScalar() << "), " + << "ur: (h: " << it.GetScalar() << ", v: " << it.GetScalar() << "), " + << "lr: (h: " << it.GetScalar() << ", v: " << it.GetScalar() << "), " + << "ll: (h: " << it.GetScalar() << ", v: " << it.GetScalar() << "))"; + break; + case uint32_list: + case scalar_list: { + uint32_t len = it.GetUint32(); + ss << std::dec << "(len=" << len << ")[" << std::hex; + for (uint32_t i = 0; i < len; i++) { + if (i > 0) { + ss << ", "; + if (len > 8 && i == 4) { + it.skipData(len - 8); + i += (len - 8); + ss << "..., "; + } + } + if (arg_type == scalar_list) { + ss << it.GetScalar(); + } else { + ss << it.GetUint32(); + } + } + ss << "]"; + break; + } + case matrix_row3: + ss << "[" << it.GetScalar() << ", " << it.GetScalar() << ", " + << it.GetScalar() << "]"; + break; + case image: + ss << "[Image]"; + break; + case path: + ss << "[Path]"; + break; + case vertices: + ss << "[Vertices]"; + break; + case skpicture: + ss << "[SkPicture]"; + break; + case display_list: + ss << "[DisplayList]"; + break; + case shader: + ss << "[Shader]"; + break; + case color_filter: + ss << "[ColorFilter]"; + break; + case image_filter: + ss << "[ImageFilter]"; + break; + } + } + ss << ")"; + return ss.str(); +} + +#endif // NDEBUG + +// clang-format off +constexpr float invert_colors[20] = { + -1.0, 0, 0, 1.0, 0, + 0, -1.0, 0, 1.0, 0, + 0, 0, -1.0, 1.0, 0, + 1.0, 1.0, 1.0, 1.0, 0 +}; +// clang-format on + +sk_sp DisplayListInterpreter::makeColorFilter( + RasterizeContext& context) { + if (!context.invertColors) { + return context.colorFilter; + } + sk_sp invert_filter = SkColorFilters::Matrix(invert_colors); + if (context.colorFilter) { + invert_filter = invert_filter->makeComposed(context.colorFilter); + } + return invert_filter; +} + +#define CANVAS_OP_DISPATCH_OP(name, args) \ + case cops_##name: \ + execute_##name(context, it); \ + break; + +void DisplayListInterpreter::Rasterize(SkCanvas* canvas) { + RasterizeContext context; + context.canvas = canvas; + context.filterMode = NearestSampling.filter; + context.sampling = NearestSampling; + int entrySaveCount = canvas->getSaveCount(); + Iterator it(this); + while (it.HasOp()) { + switch (it.GetOp()) { FOR_EACH_CANVAS_OP(CANVAS_OP_DISPATCH_OP) } + } + FML_DCHECK(it.ops == it.ops_end); + FML_DCHECK(it.data == it.data_end); + FML_DCHECK(it.refs == it.refs_end); + FML_DCHECK(canvas->getSaveCount() == entrySaveCount); +} + +#define CANVAS_OP_DEFINE_OP(name) \ + void DisplayListInterpreter::execute_##name(RasterizeContext& context, \ + Iterator& it) + +CANVAS_OP_DEFINE_OP(setAA) { + context.paint.setAntiAlias(true); +} +CANVAS_OP_DEFINE_OP(clearAA) { + context.paint.setAntiAlias(false); +} +CANVAS_OP_DEFINE_OP(setDither) { + context.paint.setDither(true); +} +CANVAS_OP_DEFINE_OP(clearDither) { + context.paint.setDither(false); +} + +CANVAS_OP_DEFINE_OP(setInvertColors) { + context.invertColors = true; + context.paint.setColorFilter(makeColorFilter(context)); +} +CANVAS_OP_DEFINE_OP(clearInvertColors) { + context.invertColors = false; + context.paint.setColorFilter(context.colorFilter); +} +CANVAS_OP_DEFINE_OP(clearColorFilter) { + context.colorFilter = nullptr; + context.paint.setColorFilter(makeColorFilter(context)); +} +CANVAS_OP_DEFINE_OP(setColorFilter) { + context.colorFilter = it.GetColorFilterRef(); + context.paint.setColorFilter(makeColorFilter(context)); +} + +CANVAS_OP_DEFINE_OP(setColor) { + context.paint.setColor(it.GetColor()); +} +CANVAS_OP_DEFINE_OP(setFillStyle) { + context.paint.setStyle(SkPaint::Style::kFill_Style); +} +CANVAS_OP_DEFINE_OP(setStrokeStyle) { + context.paint.setStyle(SkPaint::Style::kStroke_Style); +} +CANVAS_OP_DEFINE_OP(setStrokeWidth) { + context.paint.setStrokeWidth(it.GetScalar()); +} +CANVAS_OP_DEFINE_OP(setMiterLimit) { + context.paint.setStrokeMiter(it.GetScalar()); +} + +#define CANVAS_CAP_DEFINE_OP(cap) \ + CANVAS_OP_DEFINE_OP(setCaps##cap) { \ + context.paint.setStrokeCap(SkPaint::Cap::k##cap##_Cap); \ + } +CANVAS_CAP_DEFINE_OP(Butt) +CANVAS_CAP_DEFINE_OP(Round) +CANVAS_CAP_DEFINE_OP(Square) + +#define CANVAS_JOIN_DEFINE_OP(join) \ + CANVAS_OP_DEFINE_OP(setJoins##join) { \ + context.paint.setStrokeJoin(SkPaint::Join::k##join##_Join); \ + } +CANVAS_JOIN_DEFINE_OP(Miter) +CANVAS_JOIN_DEFINE_OP(Round) +CANVAS_JOIN_DEFINE_OP(Bevel) + +CANVAS_OP_DEFINE_OP(clearShader) { + context.paint.setShader(nullptr); +} +CANVAS_OP_DEFINE_OP(setShader) { + context.paint.setShader(it.GetShaderRef()); +} + +#define CANVAS_MASK_DEFINE_OP(type) \ + CANVAS_OP_DEFINE_OP(setMaskFilter##type) { \ + SkBlurStyle style = SkBlurStyle::k##type##_SkBlurStyle; \ + SkScalar sigma = it.GetScalar(); \ + context.paint.setMaskFilter(SkMaskFilter::MakeBlur(style, sigma)); \ + } +CANVAS_OP_DEFINE_OP(clearMaskFilter) { + context.paint.setMaskFilter(nullptr); +} +CANVAS_MASK_DEFINE_OP(Inner) +CANVAS_MASK_DEFINE_OP(Outer) +CANVAS_MASK_DEFINE_OP(Solid) +CANVAS_MASK_DEFINE_OP(Normal) + +CANVAS_OP_DEFINE_OP(clearImageFilter) { + context.paint.setImageFilter(nullptr); +} +CANVAS_OP_DEFINE_OP(setImageFilter) { + context.paint.setImageFilter(it.GetImageFilterRef()); +} + +const SkSamplingOptions DisplayListInterpreter::NearestSampling = + SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); +const SkSamplingOptions DisplayListInterpreter::LinearSampling = + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone); +const SkSamplingOptions DisplayListInterpreter::MipmapSampling = + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); +const SkSamplingOptions DisplayListInterpreter::CubicSampling = + SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}); + +#define CANVAS_OP_DEFINE_FQ(op_type, enum_type, filter_mode) \ + CANVAS_OP_DEFINE_OP(setFilterQuality##op_type) { \ + context.filterMode = SkFilterMode::filter_mode; \ + context.sampling = op_type##Sampling; \ + context.paint.setFilterQuality( \ + SkFilterQuality::k##enum_type##_SkFilterQuality); \ + } +CANVAS_OP_DEFINE_FQ(Nearest, None, kNearest) +CANVAS_OP_DEFINE_FQ(Linear, Low, kLinear) +CANVAS_OP_DEFINE_FQ(Mipmap, Medium, kLinear) +CANVAS_OP_DEFINE_FQ(Cubic, High, kLinear) + +CANVAS_OP_DEFINE_OP(setBlendMode) { + context.paint.setBlendMode(it.GetBlendMode()); +} + +CANVAS_OP_DEFINE_OP(save) { + context.canvas->save(); +} +CANVAS_OP_DEFINE_OP(saveLayer) { + context.canvas->saveLayer(nullptr, &context.paint); +} +CANVAS_OP_DEFINE_OP(saveLayerBounds) { + context.canvas->saveLayer(it.GetRect(), &context.paint); +} +CANVAS_OP_DEFINE_OP(restore) { + context.canvas->restore(); +} + +CANVAS_OP_DEFINE_OP(clipRect) { + context.canvas->clipRect(it.GetRect()); +} +CANVAS_OP_DEFINE_OP(clipRectAA) { + context.canvas->clipRect(it.GetRect(), true); +} +CANVAS_OP_DEFINE_OP(clipRectDiff) { + context.canvas->clipRect(it.GetRect(), SkClipOp::kDifference); +} +CANVAS_OP_DEFINE_OP(clipRectAADiff) { + context.canvas->clipRect(it.GetRect(), SkClipOp::kDifference, true); +} + +CANVAS_OP_DEFINE_OP(clipRRect) { + context.canvas->clipRRect(it.GetRoundRect()); +} +CANVAS_OP_DEFINE_OP(clipRRectAA) { + context.canvas->clipRRect(it.GetRoundRect(), true); +} +CANVAS_OP_DEFINE_OP(clipPath) { + SkPath path; + it.GetPath(path); + context.canvas->clipPath(path); +} +CANVAS_OP_DEFINE_OP(clipPathAA) { + SkPath path; + it.GetPath(path); + context.canvas->clipPath(path, true); +} + +CANVAS_OP_DEFINE_OP(translate) { + context.canvas->translate(it.GetScalar(), it.GetScalar()); +} +CANVAS_OP_DEFINE_OP(scale) { + context.canvas->scale(it.GetScalar(), it.GetScalar()); +} +CANVAS_OP_DEFINE_OP(rotate) { + context.canvas->rotate(it.GetDegrees()); +} +CANVAS_OP_DEFINE_OP(skew) { + context.canvas->skew(it.GetScalar(), it.GetScalar()); +} +CANVAS_OP_DEFINE_OP(transform2x3) { + // clang-format off + context.canvas->concat( + SkMatrix::MakeAll(it.GetScalar(), it.GetScalar(), it.GetScalar(), + it.GetScalar(), it.GetScalar(), it.GetScalar(), + 0.0, 0.0, 1.0)); + // clang-format on +} +CANVAS_OP_DEFINE_OP(transform3x3) { + context.canvas->concat( + SkMatrix::MakeAll(it.GetScalar(), it.GetScalar(), it.GetScalar(), + it.GetScalar(), it.GetScalar(), it.GetScalar(), + it.GetScalar(), it.GetScalar(), it.GetScalar())); +} + +CANVAS_OP_DEFINE_OP(drawColor) { + context.canvas->drawColor(it.GetColor(), it.GetBlendMode()); +} +CANVAS_OP_DEFINE_OP(drawPaint) { + context.canvas->drawPaint(context.paint); +} + +CANVAS_OP_DEFINE_OP(drawRect) { + context.canvas->drawRect(it.GetRect(), context.paint); +} +CANVAS_OP_DEFINE_OP(drawOval) { + context.canvas->drawOval(it.GetRect(), context.paint); +} +CANVAS_OP_DEFINE_OP(drawRRect) { + context.canvas->drawRRect(it.GetRoundRect(), context.paint); +} +CANVAS_OP_DEFINE_OP(drawDRRect) { + context.canvas->drawDRRect(it.GetRoundRect(), it.GetRoundRect(), + context.paint); +} +CANVAS_OP_DEFINE_OP(drawCircle) { + context.canvas->drawCircle(it.GetPoint(), it.GetScalar(), context.paint); +} +CANVAS_OP_DEFINE_OP(drawArc) { + context.canvas->drawArc(it.GetRect(), it.GetDegrees(), it.GetDegrees(), false, + context.paint); +} +CANVAS_OP_DEFINE_OP(drawArcCenter) { + context.canvas->drawArc(it.GetRect(), it.GetDegrees(), it.GetDegrees(), true, + context.paint); +} +CANVAS_OP_DEFINE_OP(drawLine) { + context.canvas->drawLine(it.GetPoint(), it.GetPoint(), context.paint); +} +CANVAS_OP_DEFINE_OP(drawPath) { + SkPath path; + it.GetPath(path); + context.canvas->drawPath(path, context.paint); +} + +#define CANVAS_OP_DEFINE_POINT_OP(mode) \ + CANVAS_OP_DEFINE_OP(draw##mode) { \ + SkScalar* flt_ptr; \ + uint32_t len = it.GetFloatList(&flt_ptr); \ + const SkPoint* pt_ptr = reinterpret_cast(flt_ptr); \ + context.canvas->drawPoints(SkCanvas::PointMode::k##mode##_PointMode, \ + len / 2, pt_ptr, context.paint); \ + } +CANVAS_OP_DEFINE_POINT_OP(Points) +CANVAS_OP_DEFINE_POINT_OP(Lines) +CANVAS_OP_DEFINE_POINT_OP(Polygon) + +CANVAS_OP_DEFINE_OP(drawVertices) { + context.canvas->drawVertices(it.GetVertices(), it.GetBlendMode(), + context.paint); +} + +CANVAS_OP_DEFINE_OP(drawImage) { + const SkImage* image = it.GetImage(); + SkPoint point = it.GetPoint(); + context.canvas->drawImage(image, point.fX, point.fY, context.sampling, + &context.paint); +} +CANVAS_OP_DEFINE_OP(drawImageRect) { + const SkImage* image = it.GetImage(); + SkRect src = it.GetRect(); + SkRect dst = it.GetRect(); + context.canvas->drawImageRect(image, src, dst, context.sampling, + &context.paint, + SkCanvas::kFast_SrcRectConstraint); +} +CANVAS_OP_DEFINE_OP(drawImageNine) { + const SkImage* image = it.GetImage(); + SkRect center = it.GetRect(); + SkRect dst = it.GetRect(); + context.canvas->drawImageNine(image, center.round(), dst, context.filterMode); +} + +#define CANVAS_OP_DEFINE_ATLAS(op_type, has_colors, has_rect) \ + CANVAS_OP_DEFINE_OP(op_type) { \ + const SkImage* image = it.GetImage(); \ + SkBlendMode blendMode = it.GetBlendMode(); \ + SkScalar* rst_ptr; \ + int nrstscalars = it.GetFloatList(&rst_ptr); \ + SkScalar* rect_ptr; \ + int nrectscalars = it.GetFloatList(&rect_ptr); \ + int numrects = nrectscalars / 4; \ + uint32_t* clr_ptr = nullptr; \ + int ncolorints = numrects; \ + if (has_colors) { \ + ncolorints = it.GetIntList(&clr_ptr); \ + } \ + SkRect* pCullRect = nullptr; \ + SkRect cull_rect; \ + if (has_rect) { \ + cull_rect = it.GetRect(); \ + pCullRect = &cull_rect; \ + } \ + if (nrectscalars != numrects * 4 || nrstscalars != numrects * 4 || \ + ncolorints != numrects) { \ + FML_LOG(ERROR) << "Mismatched Atlas array lengths"; \ + return; \ + } \ + context.canvas->drawAtlas( \ + image, reinterpret_cast(rst_ptr), \ + reinterpret_cast(rect_ptr), \ + reinterpret_cast(clr_ptr), numrects, blendMode, \ + context.sampling, pCullRect, &context.paint); \ + } +CANVAS_OP_DEFINE_ATLAS(drawAtlas, false, false) +CANVAS_OP_DEFINE_ATLAS(drawAtlasColored, true, false) +CANVAS_OP_DEFINE_ATLAS(drawAtlasCulled, false, true) +CANVAS_OP_DEFINE_ATLAS(drawAtlasColoredCulled, true, true) + +CANVAS_OP_DEFINE_OP(drawDisplayList) { + DisplayListInterpreter(it.GetDisplayList()).Rasterize(context.canvas); +} +CANVAS_OP_DEFINE_OP(drawSkPicture) { + context.canvas->drawPicture(it.GetSkPicture()); +} + +#define CANVAS_OP_DEFINE_SHADOW_OP(optype, occludes) \ + CANVAS_OP_DEFINE_OP(optype) { \ + SkPath path; \ + it.GetPath(path); \ + SkColor color = it.GetColor(); \ + SkScalar elevation = it.GetScalar(); \ + /* TODO(flar): How to deal with dpr */ \ + SkScalar dpr = 1.0; \ + flutter::PhysicalShapeLayer::DrawShadow(context.canvas, path, color, \ + elevation, occludes, dpr); \ + } +CANVAS_OP_DEFINE_SHADOW_OP(drawShadow, false) +CANVAS_OP_DEFINE_SHADOW_OP(drawShadowOccluded, true) + +} // namespace flutter diff --git a/flow/display_list_interpreter.h b/flow/display_list_interpreter.h new file mode 100644 index 0000000000000..ee57dd80ddb76 --- /dev/null +++ b/flow/display_list_interpreter.h @@ -0,0 +1,359 @@ +// 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. + +#ifndef FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_INTERPRETER_H_ +#define FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_INTERPRETER_H_ + +#include + +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/core/SkVertices.h" + +namespace flutter { + +enum CanvasOpArg { + empty, // Forces all following enum values to be non-zero + scalar, + color, + blend_mode, + angle, + point, + rect, + round_rect, + uint32_list, + scalar_list, + matrix_row3, + image, + path, + vertices, + skpicture, + display_list, + shader, + color_filter, + image_filter, +}; +#define CANVAS_OP_ARG_SHIFT 5 +#define CANVAS_OP_ARG_MASK ((1 << CANVAS_OP_ARG_SHIFT) - 1) + +#define CANVAS_OP_APPEND_ARG(arg_list, arg) \ + (((arg_list) << CANVAS_OP_ARG_SHIFT) | CanvasOpArg::arg) +#define CANVAS_OP_ARGS1(arg) (CanvasOpArg::arg) +#define CANVAS_OP_ARGS2(arg1, arg2) \ + CANVAS_OP_APPEND_ARG(CANVAS_OP_ARGS1(arg2), arg1) +#define CANVAS_OP_ARGS3(arg1, arg2, arg3) \ + CANVAS_OP_APPEND_ARG(CANVAS_OP_ARGS2(arg2, arg3), arg1) +#define CANVAS_OP_ARGS4(arg1, arg2, arg3, arg4) \ + CANVAS_OP_APPEND_ARG(CANVAS_OP_ARGS3(arg2, arg3, arg4), arg1) +#define CANVAS_OP_ARGS5(arg1, arg2, arg3, arg4, arg5) \ + CANVAS_OP_APPEND_ARG(CANVAS_OP_ARGS4(arg2, arg3, arg4, arg5), arg1) +#define CANVAS_OP_ARGS6(arg1, arg2, arg3, arg4, arg5, arg6) \ + CANVAS_OP_APPEND_ARG(CANVAS_OP_ARGS5(arg2, arg3, arg4, arg5, arg6), arg1) + +// clang-format off +#define CANVAS_OP_ARGS__ 0 +#define CANVAS_OP_ARGS_Scalar CANVAS_OP_ARGS1(scalar) +#define CANVAS_OP_ARGS_2Scalars CANVAS_OP_ARGS2(scalar, scalar) +#define CANVAS_OP_ARGS_Angle CANVAS_OP_ARGS1(angle) +#define CANVAS_OP_ARGS_Color CANVAS_OP_ARGS1(color) +#define CANVAS_OP_ARGS_BlendMode CANVAS_OP_ARGS1(blend_mode) +#define CANVAS_OP_ARGS_Color_BlendMode CANVAS_OP_ARGS2(color, blend_mode) +#define CANVAS_OP_ARGS_Point_Scalar CANVAS_OP_ARGS2(point, scalar) +#define CANVAS_OP_ARGS_2Points CANVAS_OP_ARGS2(point, point) +#define CANVAS_OP_ARGS_Rect CANVAS_OP_ARGS1(rect) +#define CANVAS_OP_ARGS_Rect_2Angles CANVAS_OP_ARGS3(rect, angle, angle) +#define CANVAS_OP_ARGS_RoundRect CANVAS_OP_ARGS1(round_rect) +#define CANVAS_OP_ARGS_2RoundRects CANVAS_OP_ARGS2(round_rect, round_rect) +#define CANVAS_OP_ARGS_ScalarList CANVAS_OP_ARGS1(scalar_list) +#define CANVAS_OP_ARGS_Matrix2x3 CANVAS_OP_ARGS2(matrix_row3, matrix_row3) +#define CANVAS_OP_ARGS_Matrix3x3 CANVAS_OP_ARGS3(matrix_row3, matrix_row3, matrix_row3) +#define CANVAS_OP_ARGS_Path CANVAS_OP_ARGS1(path) +#define CANVAS_OP_ARGS_Path_Scalar_Scalar CANVAS_OP_ARGS3(path, scalar, scalar) +#define CANVAS_OP_ARGS_Vertices_BlendMode CANVAS_OP_ARGS2(vertices, blend_mode) +#define CANVAS_OP_ARGS_Image_Point CANVAS_OP_ARGS2(image, point) +#define CANVAS_OP_ARGS_Image_2Rects CANVAS_OP_ARGS3(image, rect, rect) +#define CANVAS_OP_ARGS_Atlas CANVAS_OP_ARGS4(image, blend_mode, scalar_list, scalar_list) +#define CANVAS_OP_ARGS_Atlas_Colors CANVAS_OP_ARGS5(image, blend_mode, scalar_list, scalar_list, uint32_list) +#define CANVAS_OP_ARGS_Atlas_Rect CANVAS_OP_ARGS5(image, blend_mode, scalar_list, scalar_list, rect) +#define CANVAS_OP_ARGS_Atlas_Colors_Rect CANVAS_OP_ARGS6(image, blend_mode, scalar_list, scalar_list, uint32_list, rect) +#define CANVAS_OP_ARGS_SkPicture CANVAS_OP_ARGS1(skpicture) +#define CANVAS_OP_ARGS_DisplayList CANVAS_OP_ARGS1(display_list) +#define CANVAS_OP_ARGS_Shader CANVAS_OP_ARGS1(shader) +#define CANVAS_OP_ARGS_ColorFilter CANVAS_OP_ARGS1(color_filter) +#define CANVAS_OP_ARGS_ImageFilter CANVAS_OP_ARGS1(image_filter) + +// opName (matches Dart enum name), argListDescriptor +#define FOR_EACH_CANVAS_OP(V) \ + V(setAA, _) \ + V(clearAA, _) \ + V(setDither, _) \ + V(clearDither, _) \ + V(setInvertColors, _) \ + V(clearInvertColors, _) \ + V(setFillStyle, _) \ + V(setStrokeStyle, _) \ + \ + V(setCapsButt, _) \ + V(setCapsRound, _) \ + V(setCapsSquare, _) \ + V(setJoinsBevel, _) \ + V(setJoinsMiter, _) \ + V(setJoinsRound, _) \ + \ + V(setStrokeWidth, Scalar) \ + V(setMiterLimit, Scalar) \ + \ + V(setFilterQualityNearest, _) \ + V(setFilterQualityLinear, _) \ + V(setFilterQualityMipmap, _) \ + V(setFilterQualityCubic, _) \ + \ + V(setColor, Color) \ + V(setBlendMode, BlendMode) \ + \ + V(setShader, Shader) \ + V(clearShader, _) \ + V(setColorFilter, ColorFilter) \ + V(clearColorFilter, _) \ + V(setImageFilter, ImageFilter) \ + V(clearImageFilter, _) \ + \ + V(clearMaskFilter, _) \ + V(setMaskFilterNormal, Scalar) \ + V(setMaskFilterSolid, Scalar) \ + V(setMaskFilterOuter, Scalar) \ + V(setMaskFilterInner, Scalar) \ + \ + V(save, _) \ + V(saveLayer, _) \ + V(saveLayerBounds, Rect) \ + V(restore, _) \ + \ + V(translate, 2Scalars) \ + V(scale, 2Scalars) \ + V(rotate, Angle) \ + V(skew, 2Scalars) \ + V(transform2x3, Matrix2x3) \ + V(transform3x3, Matrix3x3) \ + \ + V(clipRect, Rect) \ + V(clipRectAA, Rect) \ + V(clipRectDiff, Rect) \ + V(clipRectAADiff, Rect) \ + V(clipRRect, RoundRect) \ + V(clipRRectAA, RoundRect) \ + V(clipPath, Path) \ + V(clipPathAA, Path) \ + \ + V(drawPaint, _) \ + V(drawColor, Color_BlendMode) \ + \ + V(drawLine, 2Points) \ + V(drawRect, Rect) \ + V(drawOval, Rect) \ + V(drawCircle, Point_Scalar) \ + V(drawRRect, RoundRect) \ + V(drawDRRect, 2RoundRects) \ + V(drawArc, Rect_2Angles) \ + V(drawArcCenter, Rect_2Angles) \ + V(drawPath, Path) \ + \ + V(drawPoints, ScalarList) \ + V(drawLines, ScalarList) \ + V(drawPolygon, ScalarList) \ + V(drawVertices, Vertices_BlendMode) \ + \ + V(drawImage, Image_Point) \ + V(drawImageRect, Image_2Rects) \ + V(drawImageNine, Image_2Rects) \ + V(drawAtlas, Atlas) \ + V(drawAtlasColored, Atlas_Colors) \ + V(drawAtlasCulled, Atlas_Rect) \ + V(drawAtlasColoredCulled, Atlas_Colors_Rect) \ + \ + V(drawSkPicture, SkPicture) \ + V(drawDisplayList, DisplayList) \ + V(drawShadow, Path_Scalar_Scalar) \ + V(drawShadowOccluded, Path_Scalar_Scalar) +// clang-format on + +#define CANVAS_OP_MAKE_ENUM(name, args) cops_##name, +enum CanvasOp { FOR_EACH_CANVAS_OP(CANVAS_OP_MAKE_ENUM) }; + +class DisplayListRefHolder; + +struct DisplayListData { + std::shared_ptr> ops_vector; + std::shared_ptr> data_vector; + std::shared_ptr> ref_vector; +}; + +class DisplayListRefHolder { + public: + sk_sp colorFilter; + sk_sp imageFilter; + sk_sp image; + sk_sp vertices; + sk_sp shader; + sk_sp picture; + sk_sp pathData; + DisplayListData displayList; +}; + +class DisplayListInterpreter { + public: + DisplayListInterpreter(const DisplayListData& data); + + DisplayListInterpreter( + std::shared_ptr> ops, + std::shared_ptr> data, + std::shared_ptr> refs); + + void Rasterize(SkCanvas* canvas); + +#ifndef NDEBUG + void Describe(); + + static const std::vector opNames; +#endif + + static const std::vector opArguments; + + static const SkSamplingOptions NearestSampling; + static const SkSamplingOptions LinearSampling; + static const SkSamplingOptions MipmapSampling; + static const SkSamplingOptions CubicSampling; + + private: + std::shared_ptr> ops_vector_; + std::shared_ptr> data_vector_; + std::shared_ptr> ref_vector_; + + class Iterator { + public: + Iterator(const DisplayListInterpreter* interpreter) + : ops(interpreter->ops_vector_->begin()), + ops_end(interpreter->ops_vector_->end()), + data(interpreter->data_vector_->begin()), + data_end(interpreter->data_vector_->end()), + refs(interpreter->ref_vector_->begin()), + refs_end(interpreter->ref_vector_->end()) {} + + Iterator(const Iterator& iterator) + : ops(iterator.ops), + ops_end(iterator.ops_end), + data(iterator.data), + data_end(iterator.data_end), + refs(iterator.refs), + refs_end(iterator.refs_end) {} + + bool HasOp() { return ops < ops_end; } + CanvasOp GetOp() { return static_cast(*ops++); } + + void skipData(int n) { data += n; } + SkScalar GetScalar() { return static_cast(*data++); } + uint32_t GetUint32() { + union { + float f; + uint32_t i; + } u; + u.f = *data++; + return u.i; + } + + SkScalar GetDegrees() { return GetScalar() * 180 / SK_DoublePI; } + SkBlendMode GetBlendMode() { return static_cast(GetUint32()); } + SkColor GetColor() { return static_cast(GetUint32()); } + + SkPoint GetPoint() { return SkPoint::Make(GetScalar(), GetScalar()); } + SkRect GetRect() { + return SkRect::MakeLTRB(GetScalar(), GetScalar(), GetScalar(), + GetScalar()); + } + SkRRect GetRoundRect() { + SkRect rect = GetRect(); + SkVector radii[4] = { + // SkRRect Radii order is UL, UR, LR, LL + // as per SkRRect::Corner indices + {GetScalar(), GetScalar()}, + {GetScalar(), GetScalar()}, + {GetScalar(), GetScalar()}, + {GetScalar(), GetScalar()}, + }; + + SkRRect rrect; + rrect.setRectRadii(rect, radii); + return rrect; + } + + uint32_t GetIntList(uint32_t** uint32_list_ptr) { + uint32_t len = GetUint32(); + *uint32_list_ptr = reinterpret_cast(&*data); + skipData(len); + return len; + } + + uint32_t GetFloatList(SkScalar** scalar_list_ptr) { + uint32_t len = GetUint32(); + *scalar_list_ptr = reinterpret_cast(&*data); + skipData(len); + return len; + } + + const sk_sp GetColorFilterRef() { + return (refs++)->colorFilter; + } + const sk_sp GetImageFilterRef() { + return (refs++)->imageFilter; + } + const sk_sp GetShaderRef() { return (refs++)->shader; } + + const SkImage* GetImage() { return (refs++)->image.get(); } + const SkVertices* GetVertices() { return (refs++)->vertices.get(); } + const SkPicture* GetSkPicture() { return (refs++)->picture.get(); } + const DisplayListData& GetDisplayList() { return (refs++)->displayList; } + void GetPath(SkPath& path) { + SkData* data = (refs++)->pathData.get(); + path.readFromMemory(data->data(), data->size()); + } + + std::vector::iterator ops; + const std::vector::iterator ops_end; + std::vector::iterator data; + const std::vector::iterator data_end; + std::vector::iterator refs; + const std::vector::iterator refs_end; + }; + + struct RasterizeContext { + SkCanvas* canvas; + SkPaint paint; + bool invertColors = false; + sk_sp colorFilter; + SkFilterMode filterMode; + SkSamplingOptions sampling; + }; + + static sk_sp makeColorFilter(RasterizeContext& context); + +#define CANVAS_OP_DECLARE_OP(name, args) \ + void execute_##name(RasterizeContext& context, Iterator& it); + + FOR_EACH_CANVAS_OP(CANVAS_OP_DECLARE_OP) + +#ifndef NDEBUG + std::string DescribeNextOp(const Iterator& it); + std::string DescribeOneOp(Iterator& it); +#endif +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_INTERPRETER_H_ diff --git a/flow/layers/display_list_layer.cc b/flow/layers/display_list_layer.cc new file mode 100644 index 0000000000000..65064d8c19950 --- /dev/null +++ b/flow/layers/display_list_layer.cc @@ -0,0 +1,93 @@ +// 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 "flutter/flow/layers/display_list_layer.h" + +#include "flutter/flow/display_list_interpreter.h" + +namespace flutter { + +DisplayListLayer::DisplayListLayer( + const SkPoint& offset, + const SkRect& cull_rect, + const SkRect& draw_rect, + std::shared_ptr> ops, + std::shared_ptr> data, + std::shared_ptr> refs, + bool is_complex, + bool will_change) + : offset_(offset), + cull_rect_(cull_rect), + draw_rect_(draw_rect), + ops_vector_(ops), + data_vector_(data), + ref_vector_(refs), + is_complex_(is_complex), + will_change_(will_change) {} + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +// TODO(flar) Implement display list comparisons and ::IsReplacing method +void DisplayListLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (offset_ != prev->offset_ || cull_rect_ != prev->cull_rect_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + context->PushTransform(SkMatrix::Translate(offset_.x(), offset_.y())); + SkRect bounds = draw_rect_; + if (!bounds.intersect(cull_rect_)) { + bounds.setEmpty(); + } + context->AddLayerBounds(bounds); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +void DisplayListLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "DisplayListLayer::Preroll"); + +#if defined(LEGACY_FUCHSIA_EMBEDDER) + CheckForChildLayerBelow(context); +#endif + + // TODO(flar): implement DisplayList raster caching + + SkRect bounds = draw_rect_; + if (true || bounds.intersect(cull_rect_)) { + bounds.offset(offset_.x(), offset_.y()); + } else { + bounds.setEmpty(); + } + set_paint_bounds(bounds); +} + +void DisplayListLayer::Paint(PaintContext& context) const { + TRACE_EVENT0("flutter", "DisplayListLayer::Paint"); + FML_DCHECK(needs_painting(context)); + + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); +#endif + + // TODO(flar): implement DisplayList raster caching + + DisplayListInterpreter interpreter(ops_vector_, data_vector_, ref_vector_); + interpreter.Rasterize(context.leaf_nodes_canvas); + + SkPaint paint; + paint.setColor(is_complex_ + ? (will_change_ ? SkColors::kRed : SkColors::kYellow) + : (will_change_ ? SkColors::kBlue : SkColors::kGreen)); +} + +} // namespace flutter diff --git a/flow/layers/display_list_layer.h b/flow/layers/display_list_layer.h new file mode 100644 index 0000000000000..739b818c528a0 --- /dev/null +++ b/flow/layers/display_list_layer.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ + +#include + +#include "flutter/flow/display_list_interpreter.h" +#include "flutter/flow/layers/layer.h" +#include "flutter/flow/raster_cache.h" +#include "flutter/flow/skia_gpu_object.h" + +namespace flutter { + +class DisplayListLayer : public Layer { + public: + DisplayListLayer(const SkPoint& offset, + const SkRect& cull_rect, + const SkRect& draw_rect, + std::shared_ptr> ops, + std::shared_ptr> data, + std::shared_ptr> refs, + bool is_complex, + bool will_change); + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + + void Preroll(PrerollContext* frame, const SkMatrix& matrix) override; + + void Paint(PaintContext& context) const override; + + private: + SkPoint offset_; + SkRect cull_rect_; + SkRect draw_rect_; + std::shared_ptr> ops_vector_; + std::shared_ptr> data_vector_; + std::shared_ptr> ref_vector_; + bool is_complex_ = false; + bool will_change_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(DisplayListLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 1d2ab90df406f..39144d3266a8f 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -28,6 +28,8 @@ source_set("ui") { "painting/codec.h", "painting/color_filter.cc", "painting/color_filter.h", + "painting/display_list.cc", + "painting/display_list.h", "painting/engine_layer.cc", "painting/engine_layer.h", "painting/gradient.cc", diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 5ad4693597d5d..3ca30bc65e36d 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -706,12 +706,38 @@ class SceneBuilder extends NativeFieldWrapperClass2 { bool willChangeHint = false, }) { final int hints = (isComplexHint ? 1 : 0) | (willChangeHint ? 2 : 0); - _addPicture(offset.dx, offset.dy, picture, hints); + if (picture is _SkiaPicture) { + _addPicture(offset.dx, offset.dy, picture, hints); + } else { + final _DisplayListPicture dlPicture = picture as _DisplayListPicture; + _addDisplayList( + offset.dx, offset.dy, + dlPicture._cullRect!.left, dlPicture._cullRect!.top, dlPicture._cullRect!.right, dlPicture._cullRect!.bottom, + dlPicture._drawBounds!.left, dlPicture._drawBounds!.top, dlPicture._drawBounds!.right, dlPicture._drawBounds!.bottom, + dlPicture, + hints, + ); + } } void _addPicture(double dx, double dy, Picture picture, int hints) native 'SceneBuilder_addPicture'; + void _addDisplayList( + double dx, + double dy, + double cullLeft, + double cullTop, + double cullRight, + double cullBottom, + double drawLeft, + double drawTop, + double drawRight, + double drawBottom, + _DisplayListPicture dlPicture, + int hints, + ) native 'SceneBuilder_addDisplayList'; + /// Adds a backend texture to the scene. /// /// The texture is scaled to the given size and rasterized at the given offset. diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 4acf0a3a1515c..6c18890fb2285 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -10,6 +10,7 @@ #include "flutter/flow/layers/clip_rrect_layer.h" #include "flutter/flow/layers/color_filter_layer.h" #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/display_list_layer.h" #include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/layers/layer_tree.h" @@ -29,6 +30,7 @@ #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" #include "third_party/tonic/dart_library_natives.h" +#include "third_party/tonic/typed_data/dart_byte_data.h" #if defined(LEGACY_FUCHSIA_EMBEDDER) #include "flutter/flow/layers/child_scene_layer.h" // nogncheck @@ -59,6 +61,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, SceneBuilder); V(SceneBuilder, addPlatformView) \ V(SceneBuilder, addRetained) \ V(SceneBuilder, addPicture) \ + V(SceneBuilder, addDisplayList) \ V(SceneBuilder, addTexture) \ V(SceneBuilder, addPerformanceOverlay) \ V(SceneBuilder, setRasterizerTracingThreshold) \ @@ -282,6 +285,27 @@ void SceneBuilder::addPicture(double dx, AddLayer(std::move(layer)); } +void SceneBuilder::addDisplayList(double dx, + double dy, + double cullLeft, + double cullTop, + double cullRight, + double cullBottom, + double drawLeft, + double drawTop, + double drawRight, + double drawBottom, + DisplayList* displayList, + int hints) { + auto layer = std::make_unique( + SkPoint::Make(dx, dy), + SkRect::MakeLTRB(cullLeft, cullTop, cullRight, cullBottom), + SkRect::MakeLTRB(drawLeft, drawTop, drawRight, drawBottom), + displayList->ops_vector(), displayList->data_vector(), + displayList->ref_vector(), !!(hints & 1), !!(hints & 2)); + AddLayer(std::move(layer)); +} + void SceneBuilder::addTexture(double dx, double dy, double width, diff --git a/lib/ui/compositing/scene_builder.h b/lib/ui/compositing/scene_builder.h index 70d37f6123559..0ed1fdee03778 100644 --- a/lib/ui/compositing/scene_builder.h +++ b/lib/ui/compositing/scene_builder.h @@ -13,12 +13,14 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/color_filter.h" +#include "flutter/lib/ui/painting/display_list.h" #include "flutter/lib/ui/painting/engine_layer.h" #include "flutter/lib/ui/painting/image_filter.h" #include "flutter/lib/ui/painting/path.h" #include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/painting/rrect.h" #include "flutter/lib/ui/painting/shader.h" +#include "third_party/tonic/typed_data/dart_byte_data.h" #include "third_party/tonic/typed_data/typed_list.h" #if defined(LEGACY_FUCHSIA_EMBEDDER) @@ -103,6 +105,19 @@ class SceneBuilder : public RefCountedDartWrappable { void addPicture(double dx, double dy, Picture* picture, int hints); + void addDisplayList(double dx, + double dy, + double cullLeft, + double cullTop, + double cullRight, + double cullBottom, + double drawLeft, + double drawTop, + double drawRight, + double drawBottom, + DisplayList* displayList, + int hints); + void addTexture(double dx, double dy, double width, diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index c22b26b17d3f6..1a70b869758ec 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -12,6 +12,7 @@ #include "flutter/lib/ui/painting/canvas.h" #include "flutter/lib/ui/painting/codec.h" #include "flutter/lib/ui/painting/color_filter.h" +#include "flutter/lib/ui/painting/display_list.h" #include "flutter/lib/ui/painting/engine_layer.h" #include "flutter/lib/ui/painting/gradient.h" #include "flutter/lib/ui/painting/image.h" @@ -67,6 +68,7 @@ void DartUI::InitForGlobal() { Codec::RegisterNatives(g_natives); ColorFilter::RegisterNatives(g_natives); DartRuntimeHooks::RegisterNatives(g_natives); + DisplayList::RegisterNatives(g_natives); EngineLayer::RegisterNatives(g_natives); FontCollection::RegisterNatives(g_natives); ImageDescriptor::RegisterNatives(g_natives); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 42df63d532386..8a18d35ead9ae 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3811,6 +3811,12 @@ class Vertices extends NativeFieldWrapperClass2 { Float32List? textureCoordinates, Int32List? colors, Uint16List? indices) native 'Vertices_init'; + + Rect _getBounds() { + final Float32List rect = _getBoundsF32(); + return Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]); + } + Float32List _getBoundsF32() native 'Vertices_getBounds'; } /// Defines how a list of points is interpreted when drawing a set of points. @@ -3856,6 +3862,9 @@ enum ClipOp { intersect, } +/// A flag to enable use of the new DisplayList format for Picture objects. +bool _useDisplayListPictures = false; + /// An interface for recording graphical operations. /// /// [Canvas] objects are used in creating [Picture] objects, which can @@ -3873,7 +3882,7 @@ enum ClipOp { /// /// The current transform and clip can be saved and restored using the stack /// managed by the [save], [saveLayer], and [restore] methods. -class Canvas extends NativeFieldWrapperClass2 { +abstract class Canvas { /// Creates a canvas for recording graphical operations into the /// given picture recorder. /// @@ -3886,25 +3895,8 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// To end the recording, call [PictureRecorder.endRecording] on the /// given recorder. - @pragma('vm:entry-point') - Canvas(PictureRecorder recorder, [ Rect? cullRect ]) : assert(recorder != null) { - if (recorder.isRecording) - throw ArgumentError('"recorder" must not already be associated with another Canvas.'); - _recorder = recorder; - _recorder!._canvas = this; - cullRect ??= Rect.largest; - _constructor(recorder, cullRect.left, cullRect.top, cullRect.right, cullRect.bottom); - } - void _constructor(PictureRecorder recorder, - double left, - double top, - double right, - double bottom) native 'Canvas_constructor'; - - // The underlying Skia SkCanvas is owned by the PictureRecorder used to create this Canvas. - // The Canvas holds a reference to the PictureRecorder to prevent the recorder from being - // garbage collected until PictureRecorder.endRecording is called. - PictureRecorder? _recorder; + factory Canvas(PictureRecorder recorder, [ Rect? cullRect ]) => + recorder._makeCanvas(cullRect); /// Saves a copy of the current transform and clip on the save stack. /// @@ -3914,7 +3906,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// * [saveLayer], which does the same thing but additionally also groups the /// commands done until the matching [restore]. - void save() native 'Canvas_save'; + void save(); /// Saves a copy of the current transform and clip on the save stack, and then /// creates a new group which subsequent calls will become a part of. When the @@ -4025,24 +4017,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// for subsequent commands. /// * [BlendMode], which discusses the use of [Paint.blendMode] with /// [saveLayer]. - void saveLayer(Rect? bounds, Paint paint) { - assert(paint != null); - if (bounds == null) { - _saveLayerWithoutBounds(paint._objects, paint._data); - } else { - assert(_rectIsValid(bounds)); - _saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, - paint._objects, paint._data); - } - } - void _saveLayerWithoutBounds(List? paintObjects, ByteData paintData) - native 'Canvas_saveLayerWithoutBounds'; - void _saveLayer(double left, - double top, - double right, - double bottom, - List? paintObjects, - ByteData paintData) native 'Canvas_saveLayer'; + void saveLayer(Rect? bounds, Paint paint); /// Pops the current save stack, if there is anything to pop. /// Otherwise, does nothing. @@ -4051,7 +4026,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// If the state was pushed with with [saveLayer], then this call will also /// cause the new layer to be composited into the previous layer. - void restore() native 'Canvas_restore'; + void restore(); /// Returns the number of items on the save stack, including the /// initial state. This means it returns 1 for a clean canvas, and @@ -4059,11 +4034,11 @@ class Canvas extends NativeFieldWrapperClass2 { /// each matching call to [restore] decrements it. /// /// This number cannot go below 1. - int getSaveCount() native 'Canvas_getSaveCount'; + int getSaveCount(); /// Add a translation to the current transform, shifting the coordinate space /// horizontally by the first argument and vertically by the second argument. - void translate(double dx, double dy) native 'Canvas_translate'; + void translate(double dx, double dy); /// Add an axis-aligned scale to the current transform, scaling by the first /// argument in the horizontal direction and the second in the vertical @@ -4071,28 +4046,20 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// If [sy] is unspecified, [sx] will be used for the scale in both /// directions. - void scale(double sx, [double? sy]) => _scale(sx, sy ?? sx); - - void _scale(double sx, double sy) native 'Canvas_scale'; + void scale(double sx, [double? sy]); /// Add a rotation to the current transform. The argument is in radians clockwise. - void rotate(double radians) native 'Canvas_rotate'; + void rotate(double radians); /// Add an axis-aligned skew to the current transform, with the first argument /// being the horizontal skew in rise over run units clockwise around the /// origin, and the second argument being the vertical skew in rise over run /// units clockwise around the origin. - void skew(double sx, double sy) native 'Canvas_skew'; + void skew(double sx, double sy); /// Multiply the current transform by the specified 4⨉4 transformation matrix /// specified as a list of values in column-major order. - void transform(Float64List matrix4) { - assert(matrix4 != null); - if (matrix4.length != 16) - throw ArgumentError('"matrix4" must have 16 entries.'); - _transform(matrix4); - } - void _transform(Float64List matrix4) native 'Canvas_transform'; + void transform(Float64List matrix4); /// Reduces the clip region to the intersection of the current clip and the /// given rectangle. @@ -4105,18 +4072,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// Use [ClipOp.difference] to subtract the provided rectangle from the /// current clip. - void clipRect(Rect rect, { ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true }) { - assert(_rectIsValid(rect)); - assert(clipOp != null); - assert(doAntiAlias != null); - _clipRect(rect.left, rect.top, rect.right, rect.bottom, clipOp.index, doAntiAlias); - } - void _clipRect(double left, - double top, - double right, - double bottom, - int clipOp, - bool doAntiAlias) native 'Canvas_clipRect'; + void clipRect(Rect rect, { ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true }); /// Reduces the clip region to the intersection of the current clip and the /// given rounded rectangle. @@ -4126,12 +4082,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// If multiple draw commands intersect with the clip boundary, this can result /// in incorrect blending at the clip boundary. See [saveLayer] for a /// discussion of how to address that and some examples of using [clipRRect]. - void clipRRect(RRect rrect, {bool doAntiAlias = true}) { - assert(_rrectIsValid(rrect)); - assert(doAntiAlias != null); - _clipRRect(rrect._value32, doAntiAlias); - } - void _clipRRect(Float32List rrect, bool doAntiAlias) native 'Canvas_clipRRect'; + void clipRRect(RRect rrect, {bool doAntiAlias = true}); /// Reduces the clip region to the intersection of the current clip and the /// given [Path]. @@ -4142,122 +4093,50 @@ class Canvas extends NativeFieldWrapperClass2 { /// multiple draw commands intersect with the clip boundary, this can result /// in incorrect blending at the clip boundary. See [saveLayer] for a /// discussion of how to address that. - void clipPath(Path path, {bool doAntiAlias = true}) { - assert(path != null); // path is checked on the engine side - assert(doAntiAlias != null); - _clipPath(path, doAntiAlias); - } - void _clipPath(Path path, bool doAntiAlias) native 'Canvas_clipPath'; + void clipPath(Path path, {bool doAntiAlias = true}); /// Paints the given [Color] onto the canvas, applying the given /// [BlendMode], with the given color being the source and the background /// being the destination. - void drawColor(Color color, BlendMode blendMode) { - assert(color != null); - assert(blendMode != null); - _drawColor(color.value, blendMode.index); - } - void _drawColor(int color, int blendMode) native 'Canvas_drawColor'; + void drawColor(Color color, BlendMode blendMode); /// Draws a line between the given points using the given paint. The line is /// stroked, the value of the [Paint.style] is ignored for this call. /// /// The `p1` and `p2` arguments are interpreted as offsets from the origin. - void drawLine(Offset p1, Offset p2, Paint paint) { - assert(_offsetIsValid(p1)); - assert(_offsetIsValid(p2)); - assert(paint != null); - _drawLine(p1.dx, p1.dy, p2.dx, p2.dy, paint._objects, paint._data); - } - void _drawLine(double x1, - double y1, - double x2, - double y2, - List? paintObjects, - ByteData paintData) native 'Canvas_drawLine'; + void drawLine(Offset p1, Offset p2, Paint paint); /// Fills the canvas with the given [Paint]. /// /// To fill the canvas with a solid color and blend mode, consider /// [drawColor] instead. - void drawPaint(Paint paint) { - assert(paint != null); - _drawPaint(paint._objects, paint._data); - } - void _drawPaint(List? paintObjects, ByteData paintData) native 'Canvas_drawPaint'; + void drawPaint(Paint paint); /// Draws a rectangle with the given [Paint]. Whether the rectangle is filled /// or stroked (or both) is controlled by [Paint.style]. - void drawRect(Rect rect, Paint paint) { - assert(_rectIsValid(rect)); - assert(paint != null); - _drawRect(rect.left, rect.top, rect.right, rect.bottom, - paint._objects, paint._data); - } - void _drawRect(double left, - double top, - double right, - double bottom, - List? paintObjects, - ByteData paintData) native 'Canvas_drawRect'; + void drawRect(Rect rect, Paint paint); /// Draws a rounded rectangle with the given [Paint]. Whether the rectangle is /// filled or stroked (or both) is controlled by [Paint.style]. - void drawRRect(RRect rrect, Paint paint) { - assert(_rrectIsValid(rrect)); - assert(paint != null); - _drawRRect(rrect._value32, paint._objects, paint._data); - } - void _drawRRect(Float32List rrect, - List? paintObjects, - ByteData paintData) native 'Canvas_drawRRect'; + void drawRRect(RRect rrect, Paint paint); /// Draws a shape consisting of the difference between two rounded rectangles /// with the given [Paint]. Whether this shape is filled or stroked (or both) /// is controlled by [Paint.style]. /// /// This shape is almost but not quite entirely unlike an annulus. - void drawDRRect(RRect outer, RRect inner, Paint paint) { - assert(_rrectIsValid(outer)); - assert(_rrectIsValid(inner)); - assert(paint != null); - _drawDRRect(outer._value32, inner._value32, paint._objects, paint._data); - } - void _drawDRRect(Float32List outer, - Float32List inner, - List? paintObjects, - ByteData paintData) native 'Canvas_drawDRRect'; + void drawDRRect(RRect outer, RRect inner, Paint paint); /// Draws an axis-aligned oval that fills the given axis-aligned rectangle /// with the given [Paint]. Whether the oval is filled or stroked (or both) is /// controlled by [Paint.style]. - void drawOval(Rect rect, Paint paint) { - assert(_rectIsValid(rect)); - assert(paint != null); - _drawOval(rect.left, rect.top, rect.right, rect.bottom, - paint._objects, paint._data); - } - void _drawOval(double left, - double top, - double right, - double bottom, - List? paintObjects, - ByteData paintData) native 'Canvas_drawOval'; + void drawOval(Rect rect, Paint paint); /// Draws a circle centered at the point given by the first argument and /// that has the radius given by the second argument, with the [Paint] given in /// the third argument. Whether the circle is filled or stroked (or both) is /// controlled by [Paint.style]. - void drawCircle(Offset c, double radius, Paint paint) { - assert(_offsetIsValid(c)); - assert(paint != null); - _drawCircle(c.dx, c.dy, radius, paint._objects, paint._data); - } - void _drawCircle(double x, - double y, - double radius, - List? paintObjects, - ByteData paintData) native 'Canvas_drawCircle'; + void drawCircle(Offset c, double radius, Paint paint); /// Draw an arc scaled to fit inside the given rectangle. /// @@ -4270,50 +4149,18 @@ class Canvas extends NativeFieldWrapperClass2 { /// not closed, forming a circle segment. /// /// This method is optimized for drawing arcs and should be faster than [Path.arcTo]. - void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) { - assert(_rectIsValid(rect)); - assert(paint != null); - _drawArc(rect.left, rect.top, rect.right, rect.bottom, startAngle, - sweepAngle, useCenter, paint._objects, paint._data); - } - void _drawArc(double left, - double top, - double right, - double bottom, - double startAngle, - double sweepAngle, - bool useCenter, - List? paintObjects, - ByteData paintData) native 'Canvas_drawArc'; + void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint); /// Draws the given [Path] with the given [Paint]. /// /// Whether this shape is filled or stroked (or both) is controlled by /// [Paint.style]. If the path is filled, then sub-paths within it are /// implicitly closed (see [Path.close]). - void drawPath(Path path, Paint paint) { - assert(path != null); // path is checked on the engine side - assert(paint != null); - _drawPath(path, paint._objects, paint._data); - } - void _drawPath(Path path, - List? paintObjects, - ByteData paintData) native 'Canvas_drawPath'; + void drawPath(Path path, Paint paint); /// Draws the given [Image] into the canvas with its top-left corner at the /// given [Offset]. The image is composited into the canvas using the given [Paint]. - void drawImage(Image image, Offset offset, Paint paint) { - assert(image != null); // image is checked on the engine side - assert(_offsetIsValid(offset)); - assert(paint != null); - _drawImage(image._image, offset.dx, offset.dy, paint._objects, paint._data, paint.filterQuality.index); - } - void _drawImage(_Image image, - double x, - double y, - List? paintObjects, - ByteData paintData, - int filterQualityIndex) native 'Canvas_drawImage'; + void drawImage(Image image, Offset offset, Paint paint); /// Draws the subset of the given image described by the `src` argument into /// the canvas in the axis-aligned rectangle given by the `dst` argument. @@ -4324,36 +4171,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// Multiple calls to this method with different arguments (from the same /// image) can be batched into a single call to [drawAtlas] to improve /// performance. - void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { - assert(image != null); // image is checked on the engine side - assert(_rectIsValid(src)); - assert(_rectIsValid(dst)); - assert(paint != null); - _drawImageRect(image._image, - src.left, - src.top, - src.right, - src.bottom, - dst.left, - dst.top, - dst.right, - dst.bottom, - paint._objects, - paint._data, - paint.filterQuality.index); - } - void _drawImageRect(_Image image, - double srcLeft, - double srcTop, - double srcRight, - double srcBottom, - double dstLeft, - double dstTop, - double dstRight, - double dstBottom, - List? paintObjects, - ByteData paintData, - int filterQualityIndex) native 'Canvas_drawImageRect'; + void drawImageRect(Image image, Rect src, Rect dst, Paint paint); /// Draws the given [Image] into the canvas using the given [Paint]. /// @@ -4368,44 +4186,11 @@ class Canvas extends NativeFieldWrapperClass2 { /// five regions are drawn by stretching them to fit such that they exactly /// cover the destination rectangle while maintaining their relative /// positions. - void drawImageNine(Image image, Rect center, Rect dst, Paint paint) { - assert(image != null); // image is checked on the engine side - assert(_rectIsValid(center)); - assert(_rectIsValid(dst)); - assert(paint != null); - _drawImageNine(image._image, - center.left, - center.top, - center.right, - center.bottom, - dst.left, - dst.top, - dst.right, - dst.bottom, - paint._objects, - paint._data, - paint.filterQuality.index); - } - void _drawImageNine(_Image image, - double centerLeft, - double centerTop, - double centerRight, - double centerBottom, - double dstLeft, - double dstTop, - double dstRight, - double dstBottom, - List? paintObjects, - ByteData paintData, - int filterQualityIndex) native 'Canvas_drawImageNine'; + void drawImageNine(Image image, Rect center, Rect dst, Paint paint); /// Draw the given picture onto the canvas. To create a picture, see /// [PictureRecorder]. - void drawPicture(Picture picture) { - assert(picture != null); // picture is checked on the engine side - _drawPicture(picture); - } - void _drawPicture(Picture picture) native 'Canvas_drawPicture'; + void drawPicture(Picture picture); /// Draws the text in the given [Paragraph] into this canvas at the given /// [Offset]. @@ -4427,11 +4212,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// If the text is centered, the centering axis will be at the position /// described by adding half of the [ParagraphConstraints.width] given to /// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate. - void drawParagraph(Paragraph paragraph, Offset offset) { - assert(paragraph != null); - assert(_offsetIsValid(offset)); - paragraph._paint(this, offset.dx, offset.dy); - } + void drawParagraph(Paragraph paragraph, Offset offset); /// Draws a sequence of points according to the given [PointMode]. /// @@ -4441,12 +4222,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// * [drawRawPoints], which takes `points` as a [Float32List] rather than a /// [List]. - void drawPoints(PointMode pointMode, List points, Paint paint) { - assert(pointMode != null); - assert(points != null); - assert(paint != null); - _drawPoints(paint._objects, paint._data, pointMode.index, _encodePointList(points)); - } + void drawPoints(PointMode pointMode, List points, Paint paint); /// Draws a sequence of points according to the given [PointMode]. /// @@ -4457,19 +4233,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// /// * [drawPoints], which takes `points` as a [List] rather than a /// [List]. - void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) { - assert(pointMode != null); - assert(points != null); - assert(paint != null); - if (points.length % 2 != 0) - throw ArgumentError('"points" must have an even number of values.'); - _drawPoints(paint._objects, paint._data, pointMode.index, points); - } - - void _drawPoints(List? paintObjects, - ByteData paintData, - int pointMode, - Float32List points) native 'Canvas_drawPoints'; + void drawRawPoints(PointMode pointMode, Float32List points, Paint paint); /// Draws the set of [Vertices] onto the canvas. /// @@ -4479,17 +4243,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// * [new Vertices], which creates a set of vertices to draw on the canvas. /// * [Vertices.raw], which creates the vertices using typed data lists /// rather than unencoded lists. - void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) { - - assert(vertices != null); // vertices is checked on the engine side - assert(paint != null); - assert(blendMode != null); - _drawVertices(vertices, blendMode.index, paint._objects, paint._data); - } - void _drawVertices(Vertices vertices, - int blendMode, - List? paintObjects, - ByteData paintData) native 'Canvas_drawVertices'; + void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint); /// Draws many parts of an image - the [atlas] - onto the canvas. /// @@ -4624,49 +4378,7 @@ class Canvas extends NativeFieldWrapperClass2 { List? colors, BlendMode? blendMode, Rect? cullRect, - Paint paint) { - assert(atlas != null); // atlas is checked on the engine side - assert(transforms != null); - assert(rects != null); - assert(colors == null || colors.isEmpty || blendMode != null); - assert(paint != null); - - final int rectCount = rects.length; - if (transforms.length != rectCount) - throw ArgumentError('"transforms" and "rects" lengths must match.'); - if (colors != null && colors.isNotEmpty && colors.length != rectCount) - throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".'); - - final Float32List rstTransformBuffer = Float32List(rectCount * 4); - final Float32List rectBuffer = Float32List(rectCount * 4); - - for (int i = 0; i < rectCount; ++i) { - final int index0 = i * 4; - final int index1 = index0 + 1; - final int index2 = index0 + 2; - final int index3 = index0 + 3; - final RSTransform rstTransform = transforms[i]; - final Rect rect = rects[i]; - assert(_rectIsValid(rect)); - rstTransformBuffer[index0] = rstTransform.scos; - rstTransformBuffer[index1] = rstTransform.ssin; - rstTransformBuffer[index2] = rstTransform.tx; - rstTransformBuffer[index3] = rstTransform.ty; - rectBuffer[index0] = rect.left; - rectBuffer[index1] = rect.top; - rectBuffer[index2] = rect.right; - rectBuffer[index3] = rect.bottom; - } - - final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors); - final Float32List? cullRectBuffer = cullRect?._value32; - final int qualityIndex = paint.filterQuality.index; - - _drawAtlas( - paint._objects, paint._data, qualityIndex, atlas._image, rstTransformBuffer, rectBuffer, - colorBuffer, (blendMode ?? BlendMode.src).index, cullRectBuffer - ); - } + Paint paint); /// Draws many parts of an image - the [atlas] - onto the canvas. /// @@ -4815,37 +4527,7 @@ class Canvas extends NativeFieldWrapperClass2 { Int32List? colors, BlendMode? blendMode, Rect? cullRect, - Paint paint) { - assert(atlas != null); // atlas is checked on the engine side - assert(rstTransforms != null); - assert(rects != null); - assert(colors == null || blendMode != null); - assert(paint != null); - - final int rectCount = rects.length; - if (rstTransforms.length != rectCount) - throw ArgumentError('"rstTransforms" and "rects" lengths must match.'); - if (rectCount % 4 != 0) - throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.'); - if (colors != null && colors.length * 4 != rectCount) - throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".'); - final int qualityIndex = paint.filterQuality.index; - - _drawAtlas( - paint._objects, paint._data, qualityIndex, atlas._image, rstTransforms, rects, - colors, (blendMode ?? BlendMode.src).index, cullRect?._value32 - ); - } - - void _drawAtlas(List? paintObjects, - ByteData paintData, - int filterQualityIndex, - _Image atlas, - Float32List rstTransforms, - Float32List rects, - Int32List? colors, - int blendMode, - Float32List? cullRect) native 'Canvas_drawAtlas'; + Paint paint); /// Draws a shadow for a [Path] representing the given material elevation. /// @@ -4853,16 +4535,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// is not opaque. /// /// The arguments must not be null. - void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) { - assert(path != null); // path is checked on the engine side - assert(color != null); - assert(transparentOccluder != null); - _drawShadow(path, color.value, elevation, transparentOccluder); - } - void _drawShadow(Path path, - int color, - double elevation, - bool transparentOccluder) native 'Canvas_drawShadow'; + void drawShadow(Path path, Color color, double elevation, bool transparentOccluder); } /// An object representing a sequence of recorded graphical operations. @@ -4872,15 +4545,7 @@ class Canvas extends NativeFieldWrapperClass2 { /// A [Picture] can be placed in a [Scene] using a [SceneBuilder], via /// the [SceneBuilder.addPicture] method. A [Picture] can also be /// drawn into a [Canvas], using the [Canvas.drawPicture] method. -@pragma('vm:entry-point') -class Picture extends NativeFieldWrapperClass2 { - /// This class is created by the engine, and should not be instantiated - /// or extended directly. - /// - /// To create a [Picture], use a [PictureRecorder]. - @pragma('vm:entry-point') - Picture._(); - +abstract class Picture { /// Creates an image from this picture. /// /// The returned image will be `width` pixels wide and `height` pixels high. @@ -4889,44 +4554,35 @@ class Picture extends NativeFieldWrapperClass2 { /// /// Although the image is returned synchronously, the picture is actually /// rasterized the first time the image is drawn and then cached. - Future toImage(int width, int height) { - if (width <= 0 || height <= 0) - throw Exception('Invalid image dimensions.'); - return _futurize( - (_Callback callback) => _toImage(width, height, (_Image? image) { - if (image == null) { - callback(null); - } else { - callback(Image._(image)); - } - }), - ); - } - - String? _toImage(int width, int height, _Callback<_Image?> callback) native 'Picture_toImage'; + Future toImage(int width, int height); /// Release the resources used by this object. The object is no longer usable /// after this method is called. - void dispose() native 'Picture_dispose'; + void dispose(); /// Returns the approximate number of bytes allocated for this object. /// /// The actual size of this picture may be larger, particularly if it contains /// references to image or other large objects. - int get approximateBytesUsed native 'Picture_GetAllocationSize'; + int get approximateBytesUsed; } /// Records a [Picture] containing a sequence of graphical operations. /// /// To begin recording, construct a [Canvas] to record the commands. /// To end recording, use the [PictureRecorder.endRecording] method. -class PictureRecorder extends NativeFieldWrapperClass2 { +abstract class PictureRecorder { /// Creates a new idle PictureRecorder. To associate it with a /// [Canvas] and begin recording, pass this [PictureRecorder] to the /// [Canvas] constructor. - @pragma('vm:entry-point') - PictureRecorder() { _constructor(); } - void _constructor() native 'PictureRecorder_constructor'; + factory PictureRecorder() { + return _useDisplayListPictures + ? _DisplayListPictureRecorder() + : _SkiaPictureRecorder(); + } + + // Return a canvas appropriate for this PictureRecorder type + Canvas _makeCanvas(Rect? cullRect); /// Whether this object is currently recording commands. /// @@ -4935,26 +4591,1652 @@ class PictureRecorder extends NativeFieldWrapperClass2 { /// call to [endRecording], and false if either this /// [PictureRecorder] has not yet been associated with a [Canvas], /// or the [endRecording] method has already been called. - bool get isRecording => _canvas != null; + bool get isRecording; /// Finishes recording graphical operations. /// /// Returns a picture containing the graphical operations that have been /// recorded thus far. After calling this function, both the picture recorder /// and the canvas objects are invalid and cannot be used further. - Picture endRecording() { - if (_canvas == null) - throw StateError('PictureRecorder did not start recording.'); - final Picture picture = Picture._(); - _endRecording(picture); - _canvas!._recorder = null; - _canvas = null; - return picture; + Picture endRecording(); +} + +class _SkiaCanvas extends NativeFieldWrapperClass2 implements Canvas { + @pragma('vm:entry-point') + _SkiaCanvas(_SkiaPictureRecorder recorder, [ Rect? cullRect ]) : assert(recorder != null) { + if (recorder.isRecording) + throw ArgumentError('"recorder" must not already be associated with another Canvas.'); + _recorder = recorder; + _recorder!._canvas = this; + cullRect ??= Rect.largest; + _constructor(recorder, cullRect.left, cullRect.top, cullRect.right, cullRect.bottom); } + void _constructor(PictureRecorder recorder, + double left, + double top, + double right, + double bottom) native 'Canvas_constructor'; - void _endRecording(Picture outPicture) native 'PictureRecorder_endRecording'; + _SkiaPictureRecorder? _recorder; + + @override + void save() native 'Canvas_save'; + + @override + void saveLayer(Rect? bounds, Paint paint) { + assert(paint != null); + if (bounds == null) { + _saveLayerWithoutBounds(paint._objects, paint._data); + } else { + assert(_rectIsValid(bounds)); + _saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, + paint._objects, paint._data); + } + } + void _saveLayerWithoutBounds(List? paintObjects, ByteData paintData) + native 'Canvas_saveLayerWithoutBounds'; + void _saveLayer(double left, + double top, + double right, + double bottom, + List? paintObjects, + ByteData paintData) native 'Canvas_saveLayer'; + + @override + void restore() native 'Canvas_restore'; + + @override + int getSaveCount() native 'Canvas_getSaveCount'; + + @override + void translate(double dx, double dy) native 'Canvas_translate'; + + @override + void scale(double sx, [double? sy]) => _scale(sx, sy ?? sx); + + void _scale(double sx, double sy) native 'Canvas_scale'; + + @override + void rotate(double radians) native 'Canvas_rotate'; + + @override + void skew(double sx, double sy) native 'Canvas_skew'; + + @override + void transform(Float64List matrix4) { + assert(matrix4 != null); + if (matrix4.length != 16) + throw ArgumentError('"matrix4" must have 16 entries.'); + _transform(matrix4); + } + void _transform(Float64List matrix4) native 'Canvas_transform'; + + @override + void clipRect(Rect rect, { ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true }) { + assert(_rectIsValid(rect)); + assert(clipOp != null); + assert(doAntiAlias != null); + _clipRect(rect.left, rect.top, rect.right, rect.bottom, clipOp.index, doAntiAlias); + } + void _clipRect(double left, + double top, + double right, + double bottom, + int clipOp, + bool doAntiAlias) native 'Canvas_clipRect'; + + @override + void clipRRect(RRect rrect, {bool doAntiAlias = true}) { + assert(_rrectIsValid(rrect)); + assert(doAntiAlias != null); + _clipRRect(rrect._value32, doAntiAlias); + } + void _clipRRect(Float32List rrect, bool doAntiAlias) native 'Canvas_clipRRect'; + + @override + void clipPath(Path path, {bool doAntiAlias = true}) { + assert(path != null); // path is checked on the engine side + assert(doAntiAlias != null); + _clipPath(path, doAntiAlias); + } + void _clipPath(Path path, bool doAntiAlias) native 'Canvas_clipPath'; + + @override + void drawColor(Color color, BlendMode blendMode) { + assert(color != null); + assert(blendMode != null); + _drawColor(color.value, blendMode.index); + } + void _drawColor(int color, int blendMode) native 'Canvas_drawColor'; + + @override + void drawLine(Offset p1, Offset p2, Paint paint) { + assert(_offsetIsValid(p1)); + assert(_offsetIsValid(p2)); + assert(paint != null); + _drawLine(p1.dx, p1.dy, p2.dx, p2.dy, paint._objects, paint._data); + } + void _drawLine(double x1, + double y1, + double x2, + double y2, + List? paintObjects, + ByteData paintData) native 'Canvas_drawLine'; + + @override + void drawPaint(Paint paint) { + assert(paint != null); + _drawPaint(paint._objects, paint._data); + } + void _drawPaint(List? paintObjects, ByteData paintData) native 'Canvas_drawPaint'; + + @override + void drawRect(Rect rect, Paint paint) { + assert(_rectIsValid(rect)); + assert(paint != null); + _drawRect(rect.left, rect.top, rect.right, rect.bottom, + paint._objects, paint._data); + } + void _drawRect(double left, + double top, + double right, + double bottom, + List? paintObjects, + ByteData paintData) native 'Canvas_drawRect'; + + @override + void drawRRect(RRect rrect, Paint paint) { + assert(_rrectIsValid(rrect)); + assert(paint != null); + _drawRRect(rrect._value32, paint._objects, paint._data); + } + void _drawRRect(Float32List rrect, + List? paintObjects, + ByteData paintData) native 'Canvas_drawRRect'; + + @override + void drawDRRect(RRect outer, RRect inner, Paint paint) { + assert(_rrectIsValid(outer)); + assert(_rrectIsValid(inner)); + assert(paint != null); + _drawDRRect(outer._value32, inner._value32, paint._objects, paint._data); + } + void _drawDRRect(Float32List outer, + Float32List inner, + List? paintObjects, + ByteData paintData) native 'Canvas_drawDRRect'; + + @override + void drawOval(Rect rect, Paint paint) { + assert(_rectIsValid(rect)); + assert(paint != null); + _drawOval(rect.left, rect.top, rect.right, rect.bottom, + paint._objects, paint._data); + } + void _drawOval(double left, + double top, + double right, + double bottom, + List? paintObjects, + ByteData paintData) native 'Canvas_drawOval'; + + @override + void drawCircle(Offset c, double radius, Paint paint) { + assert(_offsetIsValid(c)); + assert(paint != null); + _drawCircle(c.dx, c.dy, radius, paint._objects, paint._data); + } + void _drawCircle(double x, + double y, + double radius, + List? paintObjects, + ByteData paintData) native 'Canvas_drawCircle'; + + @override + void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) { + assert(_rectIsValid(rect)); + assert(paint != null); + _drawArc(rect.left, rect.top, rect.right, rect.bottom, startAngle, + sweepAngle, useCenter, paint._objects, paint._data); + } + void _drawArc(double left, + double top, + double right, + double bottom, + double startAngle, + double sweepAngle, + bool useCenter, + List? paintObjects, + ByteData paintData) native 'Canvas_drawArc'; + + @override + void drawPath(Path path, Paint paint) { + assert(path != null); // path is checked on the engine side + assert(paint != null); + _drawPath(path, paint._objects, paint._data); + } + void _drawPath(Path path, + List? paintObjects, + ByteData paintData) native 'Canvas_drawPath'; + + @override + void drawImage(Image image, Offset offset, Paint paint) { + assert(image != null); // image is checked on the engine side + assert(_offsetIsValid(offset)); + assert(paint != null); + _drawImage(image._image, offset.dx, offset.dy, paint._objects, paint._data, paint.filterQuality.index); + } + void _drawImage(_Image image, + double x, + double y, + List? paintObjects, + ByteData paintData, + int filterQualityIndex) native 'Canvas_drawImage'; + + @override + void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { + assert(image != null); // image is checked on the engine side + assert(_rectIsValid(src)); + assert(_rectIsValid(dst)); + assert(paint != null); + _drawImageRect(image._image, + src.left, + src.top, + src.right, + src.bottom, + dst.left, + dst.top, + dst.right, + dst.bottom, + paint._objects, + paint._data, + paint.filterQuality.index); + } + void _drawImageRect(_Image image, + double srcLeft, + double srcTop, + double srcRight, + double srcBottom, + double dstLeft, + double dstTop, + double dstRight, + double dstBottom, + List? paintObjects, + ByteData paintData, + int filterQualityIndex) native 'Canvas_drawImageRect'; + + @override + void drawImageNine(Image image, Rect center, Rect dst, Paint paint) { + assert(image != null); // image is checked on the engine side + assert(_rectIsValid(center)); + assert(_rectIsValid(dst)); + assert(paint != null); + _drawImageNine(image._image, + center.left, + center.top, + center.right, + center.bottom, + dst.left, + dst.top, + dst.right, + dst.bottom, + paint._objects, + paint._data, + paint.filterQuality.index); + } + void _drawImageNine(_Image image, + double centerLeft, + double centerTop, + double centerRight, + double centerBottom, + double dstLeft, + double dstTop, + double dstRight, + double dstBottom, + List? paintObjects, + ByteData paintData, + int filterQualityIndex) native 'Canvas_drawImageNine'; + + @override + void drawPicture(Picture picture) { + assert(picture != null); // picture is checked on the engine side + _drawPicture(picture); + } + void _drawPicture(Picture picture) native 'Canvas_drawPicture'; + + @override + void drawParagraph(Paragraph paragraph, Offset offset) { + assert(paragraph != null); + assert(_offsetIsValid(offset)); + paragraph._paint(this, offset.dx, offset.dy); + } + + @override + void drawPoints(PointMode pointMode, List points, Paint paint) { + assert(pointMode != null); + assert(points != null); + assert(paint != null); + _drawPoints(paint._objects, paint._data, pointMode.index, _encodePointList(points)); + } + + @override + void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) { + assert(pointMode != null); + assert(points != null); + assert(paint != null); + if (points.length % 2 != 0) + throw ArgumentError('"points" must have an even number of values.'); + _drawPoints(paint._objects, paint._data, pointMode.index, points); + } + + void _drawPoints(List? paintObjects, + ByteData paintData, + int pointMode, + Float32List points) native 'Canvas_drawPoints'; + + @override + void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) { + assert(vertices != null); // vertices is checked on the engine side + assert(paint != null); + assert(blendMode != null); + _drawVertices(vertices, blendMode.index, paint._objects, paint._data); + } + void _drawVertices(Vertices vertices, + int blendMode, + List? paintObjects, + ByteData paintData) native 'Canvas_drawVertices'; + + @override + void drawAtlas(Image atlas, + List transforms, + List rects, + List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint) { + assert(atlas != null); // atlas is checked on the engine side + assert(transforms != null); + assert(rects != null); + assert(colors == null || colors.isEmpty || blendMode != null); + assert(paint != null); + + final int rectCount = rects.length; + if (transforms.length != rectCount) + throw ArgumentError('"transforms" and "rects" lengths must match.'); + if (colors != null && colors.isNotEmpty && colors.length != rectCount) + throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".'); + + final Float32List rstTransformBuffer = Float32List(rectCount * 4); + final Float32List rectBuffer = Float32List(rectCount * 4); + + for (int i = 0; i < rectCount; ++i) { + final int index0 = i * 4; + final int index1 = index0 + 1; + final int index2 = index0 + 2; + final int index3 = index0 + 3; + final RSTransform rstTransform = transforms[i]; + final Rect rect = rects[i]; + assert(_rectIsValid(rect)); + rstTransformBuffer[index0] = rstTransform.scos; + rstTransformBuffer[index1] = rstTransform.ssin; + rstTransformBuffer[index2] = rstTransform.tx; + rstTransformBuffer[index3] = rstTransform.ty; + rectBuffer[index0] = rect.left; + rectBuffer[index1] = rect.top; + rectBuffer[index2] = rect.right; + rectBuffer[index3] = rect.bottom; + } + + final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors); + final Float32List? cullRectBuffer = cullRect?._value32; + final int qualityIndex = paint.filterQuality.index; + + _drawAtlas( + paint._objects, paint._data, qualityIndex, atlas._image, rstTransformBuffer, rectBuffer, + colorBuffer, (blendMode ?? BlendMode.src).index, cullRectBuffer + ); + } + + @override + void drawRawAtlas(Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint) { + assert(atlas != null); // atlas is checked on the engine side + assert(rstTransforms != null); + assert(rects != null); + assert(colors == null || blendMode != null); + assert(paint != null); + + final int rectCount = rects.length; + if (rstTransforms.length != rectCount) + throw ArgumentError('"rstTransforms" and "rects" lengths must match.'); + if (rectCount % 4 != 0) + throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.'); + if (colors != null && colors.length * 4 != rectCount) + throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".'); + + final int qualityIndex = paint.filterQuality.index; + + _drawAtlas( + paint._objects, paint._data, qualityIndex, atlas._image, rstTransforms, rects, + colors, (blendMode ?? BlendMode.src).index, cullRect?._value32 + ); + } + + void _drawAtlas(List? paintObjects, + ByteData paintData, + int filterQualityIndex, + _Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List? colors, + int blendMode, + Float32List? cullRect) native 'Canvas_drawAtlas'; + + @override + void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) { + assert(path != null); // path is checked on the engine side + assert(color != null); + assert(transparentOccluder != null); + _drawShadow(path, color.value, elevation, transparentOccluder); + } + void _drawShadow(Path path, + int color, + double elevation, + bool transparentOccluder) native 'Canvas_drawShadow'; +} + +@pragma('vm:entry-point') +class _SkiaPicture extends NativeFieldWrapperClass2 implements Picture { + @pragma('vm:entry-point') + _SkiaPicture._(); + + @override + Future toImage(int width, int height) { + if (width <= 0 || height <= 0) + throw Exception('Invalid image dimensions.'); + return _futurize( + (_Callback callback) => _toImage(width, height, (_Image? image) { + if (image == null) { + callback(null); + } else { + callback(Image._(image)); + } + }), + ); + } + + String? _toImage(int width, int height, _Callback<_Image?> callback) native 'Picture_toImage'; + + Rect _getBounds() { + final Float32List rect = _getBoundsF32(); + return Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]); + } + Float32List _getBoundsF32() native 'Picture_getBounds'; + + @override + void dispose() native 'Picture_dispose'; + + @override + int get approximateBytesUsed native 'Picture_GetAllocationSize'; +} + +class _SkiaPictureRecorder extends NativeFieldWrapperClass2 implements PictureRecorder { + @pragma('vm:entry-point') + _SkiaPictureRecorder() { _constructor(); } + void _constructor() native 'PictureRecorder_constructor'; + + @override + Canvas _makeCanvas(Rect? cullRect) => _SkiaCanvas(this, cullRect); + + @override + bool get isRecording => _canvas != null; + + @override + Picture endRecording() { + if (_canvas == null) + throw StateError('PictureRecorder did not start recording.'); + final Picture picture = _SkiaPicture._(); + _endRecording(picture); + _canvas!._recorder = null; + _canvas = null; + return picture; + } + + void _endRecording(Picture outPicture) native 'PictureRecorder_endRecording'; + + _SkiaCanvas? _canvas; +} + +enum _CanvasOp { + setAA, + clearAA, + setDither, + clearDither, + setInvertColors, + clearInvertColors, + setFillStyle, + setStrokeStyle, + + setCapsButt, + setCapsRound, + setCapsSquare, + setJoinsBevel, + setJoinsMiter, + setJoinsRound, + + setStrokeWidth, + setMiterLimit, + + setFilterQualityNearest, + setFilterQualityLinear, + setFilterQualityMipmap, + setFilterQualityCubic, + + setColor, + setBlendMode, + + setShader, + clearShader, + setColorFilter, + clearColorFilter, + setImageFilter, + clearImageFilter, + + clearMaskFilter, + setMaskFilterNormal, + setMaskFilterSolid, + setMaskFilterOuter, + setMaskFilterInner, + + save, + saveLayer, + saveLayerBounds, + restore, + + translate, + scale, + rotate, + skew, + transform2x3, + transform3x3, + + clipRect, + clipRectAA, + clipRectDiff, + clipRectAADiff, + clipRRect, + clipRRectAA, + clipPath, + clipPathAA, + + drawPaint, + drawColor, + + drawLine, + drawRect, + drawOval, + drawCircle, + drawRRect, + drawDRRect, + drawArc, + drawArcCenter, + drawPath, + + drawPoints, + drawLines, + drawPolygon, + drawVertices, + + drawImage, + drawImageRect, + drawImageNine, + drawAtlas, + drawAtlasColored, + drawAtlasCulled, + drawAtlasColoredCulled, + + drawSkPicture, + drawDisplayList, + drawShadow, + drawShadowOccluded, +} + +class _MatrixTransform { + _MatrixTransform._identity() + : mxx = 1.0, + mxy = 0.0, + mxt = 0.0, + myx = 0.0, + myy = 1.0, + myt = 0.0; + _MatrixTransform._copy(_MatrixTransform other) + : mxx = other.mxx, + mxy = other.mxy, + mxt = other.mxt, + myx = other.myx, + myy = other.myy, + myt = other.myt; + + double mxx; double mxy; double mxt; + double myx; double myy; double myt; + + bool get isRectilinear => (mxy == 0.0 && myx == 0.0) || (mxx == 0.0 && myy == 0.0); + bool get isNotRectilinear => (mxy != 0.0 || myx != 0.0) && (mxx != 0.0 || myy != 0.0); + + double transformX(double x, double y) { + return x * mxx + y * mxy + mxt; + } + + double transformY(double x, double y) { + return x * myx + y * myy + myt; + } + + void multiply(double mxx, double mxy, double mxt, double myx, double myy, double myt) { + final double mxxNew = this.mxx * mxx + this.mxy * myx; + final double mxyNew = this.mxx * mxy + this.mxy * myy; + final double mxtNew = this.mxx * mxt + this.mxy * myt + this.mxt; + final double myxNew = this.myx * mxx + this.myy * myx; + final double myyNew = this.myx * mxy + this.myy * myy; + final double mytNew = this.myx * mxt + this.myy * myt + this.myt; + this.mxx = mxxNew; + this.mxy = mxyNew; + this.mxt = mxtNew; + this.myx = myxNew; + this.myy = myyNew; + this.myt = mytNew; + } + + void translate(double tx, double ty) { + multiply(1.0, 0.0, tx, + 0.0, 1.0, ty); + } + + void scale(double sx, double sy) { + multiply(sx, 0.0, 0.0, + 0.0, sy, 0.0); + } + + void rotate(double radians) { + multiply(math.cos(radians), -math.sin(radians), 0.0, + math.sin(radians), math.cos(radians), 0.0); + } + + void skew(double sx, double sy) { + multiply(1.0, sx, 0.0, + sy, 1.0, 0.0); + } +} + +class _BoundsAccumulator { + double _minX = Rect._giantScalar; + double _minY = Rect._giantScalar; + double _maxX = -Rect._giantScalar; + double _maxY = -Rect._giantScalar; + + void accumulate(double x, double y) { + if (_minX > x) + _minX = x; + if (_minY > y) + _minY = y; + if (_maxX < x) + _maxX = x; + if (_maxY < y) + _maxY = y; + } + + void accumulateBounds(Rect r, RSTransform rst) { + accumulateRstBounds(r.left, r.top, r.right, r.bottom, rst.scos, rst.ssin, rst.tx, rst.ty); + } + + void accumulateRstBounds(double left, double top, double right, double bottom, + double scos, double ssin, double tx, double ty) { + if (ssin == 0) { + accumulate(scos * left + tx, scos * top + ty); + accumulate(scos * right + tx, scos * bottom + ty); + return; + } else if (scos == 0) { + accumulate(-ssin * top + tx, ssin * left + ty); + accumulate(-ssin * bottom + tx, ssin * right + ty); + return; + } else { + accumulate(scos * left - ssin * top + tx, ssin * left + scos * top + ty); + accumulate(scos * right - ssin * top + tx, ssin * right + scos * top + ty); + accumulate(scos * left - ssin * bottom + tx, ssin * left + scos * bottom + ty); + accumulate(scos * right - ssin * bottom + tx, ssin * right + scos * bottom + ty); + } + } + + Rect get bounds => Rect.fromLTRB(_minX, _minY, _maxX, _maxY); +} + +class _DisplayListCanvas implements Canvas { + _DisplayListCanvas(_DisplayListPictureRecorder recorder, [Rect? cullRect]) + : _recorder = recorder, + _cullRect = cullRect ?? Rect.largest, + _accumulator = _BoundsAccumulator(), + _ops = _emptyOps, _opCount = 0, + _data = _emptyData, _dataCount = 0, + _dataInts = _emptyInts, _dataFloats = _emptyFloats, + _nativeRefs = [], + _ctm = _MatrixTransform._identity(), + _ctmStack = <_MatrixTransform>[] { + recorder._canvas = this; + } + + static Uint8List _emptyOps = Uint8List(0); + static Uint8List _cachedOps = _emptyOps; + static ByteData _emptyData = ByteData(0); + static ByteData _cachedData = _emptyData; + static Uint32List _emptyInts = _emptyData.buffer.asUint32List(); + static Float32List _emptyFloats = _emptyData.buffer.asFloat32List(); + static List _deadObjects = List.empty(growable: false); + + _DisplayListPictureRecorder? _recorder; + Rect _cullRect; + _BoundsAccumulator _accumulator; + int _opCount; + Uint8List _ops; + int _dataCount; + ByteData _data; + late Uint32List _dataInts; + late Float32List _dataFloats; + List _nativeRefs; + _MatrixTransform _ctm; + List<_MatrixTransform> _ctmStack; + + void _dispose() { + if (_cachedOps.length < _ops.length) { + _cachedOps = _ops; + } + if (_cachedData.lengthInBytes < _data.lengthInBytes) { + _cachedData = _data; + } + _recorder = null; + _ops = _emptyOps; + _data = _emptyData; + _dataInts = _emptyInts; + _dataFloats = _emptyFloats; + _opCount = _dataCount = 0; + _nativeRefs = _deadObjects; + } + + Rect get _drawBounds => _accumulator.bounds; + + static const int _minOpsSize = 1 << 8; + static const int _maxOpsGrow = 1 << 20; + static const int _minDataSize = 1 << 10; + static const int _maxDataGrow = 1 << 24; + + int _newSize(int curSize, int minSize, int maxGrow, int needed) { + int newSize; + if (curSize < minSize) { + newSize = minSize; + } else if (curSize < maxGrow) { + newSize = curSize * 2; + } else { + newSize = curSize + maxGrow; + } + if (newSize < curSize + needed) { + newSize = curSize + needed + maxGrow; + newSize -= newSize % maxGrow; + } + return newSize; + } + + void _addOp(_CanvasOp op, int dataNeeded) { + if (_opCount >= _ops.length) { + if (_recorder == null) + throw StateError('Attempting to render to disposed Canvas'); + final int newSize = _newSize(_ops.length, _minOpsSize, _maxOpsGrow, 1); + final Uint8List newOps; + if (newSize <= _cachedOps.length) { + newOps = _cachedOps; + _cachedOps = _emptyOps; + } else { + newOps = Uint8List(_newSize(_ops.length, _minOpsSize, _maxOpsGrow, 1)); + } + newOps.setRange(0, _opCount, _ops); + _ops = newOps; + } + if (_dataCount + dataNeeded >= _dataInts.length) { + final int newSize = 4 * _newSize(_dataInts.length, _minDataSize, _maxDataGrow, dataNeeded); + final ByteData newData; + if (newSize <= _cachedData.lengthInBytes / 4) { + newData = _cachedData; + _cachedData = _emptyData; + } else { + newData = ByteData(4 * _newSize(_dataInts.length, _minDataSize, _maxDataGrow, dataNeeded)); + } + final Uint32List newInts = newData.buffer.asUint32List(); + newInts.setRange(0, _dataCount, _dataInts); + _data = newData; + _dataInts = newInts; + _dataFloats = newData.buffer.asFloat32List(); + } + assert(_opCount < _ops.length); + _ops[_opCount++] = op.index; + } + + void _addInt(int value) { + assert(_dataCount < _dataInts.length); + _dataInts[_dataCount++] = value; + } + + void _addScalar(double value) { + assert(_dataCount < _dataFloats.length); + _dataFloats[_dataCount++] = value; + } + + void _addScalar2(double v1, double v2) { + assert(_dataCount + 2 <= _dataFloats.length); + _dataFloats[_dataCount++] = v1; + _dataFloats[_dataCount++] = v2; + } + + void _addScalar3(double v1, double v2, double v3) { + assert(_dataCount + 3 <= _dataFloats.length); + _dataFloats[_dataCount++] = v1; + _dataFloats[_dataCount++] = v2; + _dataFloats[_dataCount++] = v3; + } + + static const int _nOffsetData = 2; + void _addOffset(Offset offset) { + assert(_dataCount + _nOffsetData <= _dataFloats.length); + _dataFloats[_dataCount++] = offset.dx; + _dataFloats[_dataCount++] = offset.dy; + } + + static const int _nRectData = 4; + void _addRect(Rect r) { + assert(_dataCount + _nRectData <= _dataFloats.length); + _dataFloats[_dataCount++] = r.left; + _dataFloats[_dataCount++] = r.top; + _dataFloats[_dataCount++] = r.right; + _dataFloats[_dataCount++] = r.bottom; + } + + static const int _nRoundRectData = 12; + void _addRRect(RRect rrect) { + assert(_dataCount + _nRoundRectData <= _dataFloats.length); + _dataFloats[_dataCount++] = rrect.left; + _dataFloats[_dataCount++] = rrect.top; + _dataFloats[_dataCount++] = rrect.right; + _dataFloats[_dataCount++] = rrect.bottom; + // SkRRect Radii order is UL, UR, LR, LL as per SkRRect::Corner indices + _dataFloats[_dataCount++] = rrect.tlRadiusX; + _dataFloats[_dataCount++] = rrect.tlRadiusY; + _dataFloats[_dataCount++] = rrect.trRadiusX; + _dataFloats[_dataCount++] = rrect.trRadiusY; + _dataFloats[_dataCount++] = rrect.brRadiusX; + _dataFloats[_dataCount++] = rrect.brRadiusY; + _dataFloats[_dataCount++] = rrect.blRadiusX; + _dataFloats[_dataCount++] = rrect.blRadiusY; + } + + void _addInt32List(Int32List data) { + final int len = data.length; + assert(_dataCount + len + 1 <= _dataInts.length); + _dataInts[_dataCount++] = len; + _dataInts.setRange(_dataCount, _dataCount + len, data); + _dataCount += len; + } + + void _addFloat32List(Float32List data) { + final int len = data.length; + assert(_dataCount + len + 1 <= _dataFloats.length); + _dataInts[_dataCount++] = len; + _dataFloats.setRange(_dataCount, _dataCount + len, data); + _dataCount += len; + } + + void _addImageData(Image image) { + _nativeRefs.add(image._image); + } + + void _addPathData(Path path) { + _nativeRefs.add(path); + } + + void _addVertices(Vertices vertices) { + _nativeRefs.add(vertices); + } + + void _addSkPicture(_SkiaPicture picture) { + _nativeRefs.add(picture); + } + + void _addPicture(_DisplayListPicture picture) { + _nativeRefs.add(picture); + } + + void _addShader(Shader shader) { + _nativeRefs.add(shader); + } + + void _addColorFilter(_ColorFilter filter) { + _nativeRefs.add(filter); + } + + void _addImageFilter(_ImageFilter filter) { + _nativeRefs.add(filter); + } + + void _addPointToBounds(double ux, double uy) { + _accumulator.accumulate(_ctm.transformX(ux, uy), _ctm.transformY(ux, uy)); + } + + void _addStrokedPointToBounds(double ux, double uy) { + final double pad = _currentStrokeWidth * 0.5; + _addPointToBounds(ux - pad, uy - pad); + _addPointToBounds(ux + pad, uy + pad); + } + + void _addLTRBToBounds(double l, double t, double r, double b, [ bool? isStroke ]) { + isStroke ??= _currentPaintStyle == PaintingStyle.stroke; + final double pad = isStroke ? _currentStrokeWidth * 0.5 : 0; + _addPointToBounds(l - pad, t - pad); + _addPointToBounds(r + pad, b + pad); + if (_ctm.isNotRectilinear) { + _addPointToBounds(r + pad, t - pad); + _addPointToBounds(l - pad, b + pad); + } + } + + void _addBounds(Rect r, [ bool? isStroke ]) { + _addLTRBToBounds(r.left, r.top, r.right, r.bottom, isStroke); + } + + @override + void save() { + _addOp(_CanvasOp.save, 0); + _ctmStack.add(_MatrixTransform._copy(_ctm)); + } + + @override + void saveLayer(Rect? bounds, Paint paint) { + _updatePaintData(paint, _saveLayerMask); + if (bounds == null) { + _addOp(_CanvasOp.saveLayer, 0); + } else { + _addOp(_CanvasOp.saveLayerBounds, _nRectData); + _addRect(bounds); + } + _ctmStack.add(_MatrixTransform._copy(_ctm)); + } + + @override + int getSaveCount() => _ctmStack.length; + + @override + void restore() { + if (_ctmStack.isNotEmpty) { + _ctm = _ctmStack.removeLast(); + _addOp(_CanvasOp.restore, 0); + } + } + @override + void translate(double dx, double dy) { + _addOp(_CanvasOp.translate, 2); + _addScalar2(dx, dy); + _ctm.translate(dx, dy); + } + + @override + void scale(double sx, [ double? sy ]) { + _addOp(_CanvasOp.scale, 2); + _addScalar2(sx, sy ?? sx); + _ctm.scale(sx, sy ?? sx); + } + + @override + void rotate(double radians) { + _addOp(_CanvasOp.rotate, 1); + _addScalar(radians); + _ctm.rotate(radians); + } + + @override + void skew(double sx, double sy) { + _addOp(_CanvasOp.skew, 2); + _addScalar2(sx, sy); + _ctm.skew(sx, sy); + } + + @override + void transform(Float64List matrix4) { + if (matrix4.length != 16) + throw ArgumentError('"matrix4" must have 16 entries.'); + if (matrix4[3] == 0.0 && matrix4[7] == 0.0 && matrix4[15] == 1.0) { + _addOp(_CanvasOp.transform2x3, 6); + _addScalar3(matrix4[0], matrix4[4], matrix4[12]); + _addScalar3(matrix4[1], matrix4[5], matrix4[13]); + } else { + _addOp(_CanvasOp.transform3x3, 9); + _addScalar3(matrix4[0], matrix4[4], matrix4[12]); + _addScalar3(matrix4[1], matrix4[5], matrix4[13]); + _addScalar3(matrix4[3], matrix4[7], matrix4[15]); + } + _ctm.multiply(matrix4[0], matrix4[4], matrix4[12], + matrix4[1], matrix4[5], matrix4[13]); + } + + @override + void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = false}) { + switch (clipOp) { + case ClipOp.intersect: + _addOp(doAntiAlias ? _CanvasOp.clipRectAA : _CanvasOp.clipRect, _nRectData); + break; + case ClipOp.difference: + _addOp(doAntiAlias ? _CanvasOp.clipRectAADiff : _CanvasOp.clipRectDiff, _nRectData); + break; + } + _addRect(rect); + } + + @override + void clipRRect(RRect rrect, {bool doAntiAlias = false}) { + if (rrect.isRect) { + clipRect(rrect.outerRect, doAntiAlias: doAntiAlias); + } else { + _addOp(doAntiAlias ? _CanvasOp.clipRRectAA : _CanvasOp.clipRRect, _nRoundRectData); + _addRRect(rrect); + } + } + + @override + void clipPath(Path path, {bool doAntiAlias = false}) { + _addOp(doAntiAlias ? _CanvasOp.clipPathAA : _CanvasOp.clipPath, 0); + _addPathData(path); + } + + // These values were originally derived from the SkPaint constructor defaults + // but now are their own spec for backwards compatibility. + // + // Rather than pass down a Paint object with each call, or repeating all relevant + // Paint attributes with each call, we instead maintain a concept of "the most + // recent attributes synchronized through the byte stream (referred to here as + // the "current" value of the attribute). If any operation depends on an attribute + // value that is not the same as the most recent value that was synchronized + // through the byte stream, we send along the new value and record it in these fields. + Color _currentColor = const Color(0xFF000000); + double _currentStrokeWidth = 0.0; + double _currentMiterLimit = 4.0; + bool _currentAA = false; + bool _currentDither = false; + bool _currentInvertColors = false; + StrokeCap _currentStrokeCap = StrokeCap.butt; + StrokeJoin _currentStrokeJoin = StrokeJoin.miter; + PaintingStyle _currentPaintStyle = PaintingStyle.fill; + FilterQuality _currentFilterQuality = FilterQuality.none; + BlendMode _currentBlendMode = BlendMode.srcOver; + Shader? _currentShader; + ColorFilter? _currentColorFilter; + MaskFilter? _currentMaskFilter; + ImageFilter? _currentImageFilter; + + // Mask bits for the various attributes that might be needed for a given operation. + static const int _aaNeeded = 1 << 0; + static const int _colorNeeded = 1 << 1; + static const int _blendNeeded = 1 << 2; + static const int _invertColorsNeeded = 1 << 3; + static const int _filterQualityNeeded = 1 << 4; + static const int _paintStyleNeeded = 1 << 5; + static const int _strokeStyleNeeded = 1 << 6; + static const int _shaderNeeded = 1 << 7; + static const int _colorFilterNeeded = 1 << 8; + static const int _imageFilterNeeded = 1 << 9; + static const int _maskFilterNeeded = 1 << 10; + static const int _ditherNeeded = 1 << 11; + + // Combinations of the above mask bits that are common to typical "draw" calls. + // Note that the _strokeStyle is handled conditionally during synchronization + // if the _paintStyle attribute value is synchronized. It can also be manually + // specified for operations that will be always stroking, like [drawLine]. + static const int _paintMask = _aaNeeded | _colorNeeded | _blendNeeded | _invertColorsNeeded + | _colorFilterNeeded | _shaderNeeded | _ditherNeeded | _imageFilterNeeded; + static const int _drawMask = _paintMask | _paintStyleNeeded | _maskFilterNeeded; + static const int _strokeMask = _paintMask | _strokeStyleNeeded | _maskFilterNeeded; + static const int _imageMask = _blendNeeded | _filterQualityNeeded | _imageFilterNeeded | _ditherNeeded; + static const int _saveLayerMask = _blendNeeded; + + static const List<_CanvasOp> _filterQualityOps = <_CanvasOp>[ + _CanvasOp.setFilterQualityNearest, + _CanvasOp.setFilterQualityLinear, + _CanvasOp.setFilterQualityMipmap, + _CanvasOp.setFilterQualityCubic, + ]; + + static const List<_CanvasOp> _strokeCapOps = <_CanvasOp>[ + _CanvasOp.setCapsButt, + _CanvasOp.setCapsRound, + _CanvasOp.setCapsSquare, + ]; + + static const List<_CanvasOp> _strokeJoinOps = <_CanvasOp>[ + _CanvasOp.setJoinsMiter, + _CanvasOp.setJoinsRound, + _CanvasOp.setJoinsBevel, + ]; + + static const List<_CanvasOp> _maskFilterOps = <_CanvasOp>[ + _CanvasOp.setMaskFilterNormal, + _CanvasOp.setMaskFilterSolid, + _CanvasOp.setMaskFilterOuter, + _CanvasOp.setMaskFilterInner, + ]; + + void _updatePaintData(Paint paint, int dataNeeded) { + if ((dataNeeded & _aaNeeded) != 0 && _currentAA != paint.isAntiAlias) { + _currentAA = paint.isAntiAlias; + _addOp(_currentAA ? _CanvasOp.setAA : _CanvasOp.clearAA, 0); + } + if ((dataNeeded & _ditherNeeded) != 0 && _currentDither != paint._dither) { + _currentDither = paint._dither; + _addOp(_currentDither ? _CanvasOp.setDither : _CanvasOp.clearDither, 0); + } + if ((dataNeeded & _colorNeeded) != 0 && _currentColor != paint.color) { + _currentColor = paint.color; + _addOp(_CanvasOp.setColor, 1); + _addInt(_currentColor.value); + } + if ((dataNeeded & _blendNeeded) != 0 && _currentBlendMode != paint.blendMode) { + _currentBlendMode = paint.blendMode; + _addOp(_CanvasOp.setBlendMode, 1); + _addInt(_currentBlendMode.index); + } + if ((dataNeeded & _invertColorsNeeded) != 0 && _currentInvertColors != paint.invertColors) { + _currentInvertColors = paint.invertColors; + _addOp(_currentInvertColors ? _CanvasOp.setInvertColors : _CanvasOp.clearInvertColors, 0); + } + if ((dataNeeded & _paintStyleNeeded) != 0) { + if (_currentPaintStyle != paint.style) { + _currentPaintStyle = paint.style; + _addOp(_currentPaintStyle == PaintingStyle.fill ? _CanvasOp.setFillStyle : _CanvasOp.setStrokeStyle, 0); + } + if (_currentPaintStyle == PaintingStyle.stroke) { + dataNeeded |= _strokeStyleNeeded; + } + } + if ((dataNeeded & _strokeStyleNeeded) != 0) { + if (_currentStrokeWidth != paint.strokeWidth) { + _currentStrokeWidth = paint.strokeWidth; + _addOp(_CanvasOp.setStrokeWidth, 1); + _addScalar(_currentStrokeWidth); + } + if (_currentStrokeCap != paint.strokeCap) { + _currentStrokeCap = paint.strokeCap; + _addOp(_strokeCapOps[_currentStrokeCap.index], 0); + } + if (_currentStrokeJoin != paint.strokeJoin) { + _currentStrokeJoin = paint.strokeJoin; + _addOp(_strokeJoinOps[_currentStrokeJoin.index], 0); + } + if (_currentMiterLimit != paint.strokeMiterLimit) { + _currentMiterLimit = paint.strokeMiterLimit; + _addOp(_CanvasOp.setMiterLimit, 1); + _addScalar(_currentMiterLimit); + } + } + if ((dataNeeded & _filterQualityNeeded) != 0 && _currentFilterQuality != paint.filterQuality) { + _currentFilterQuality = paint.filterQuality; + _addOp(_filterQualityOps[_currentFilterQuality.index], 0); + } + if ((dataNeeded & _shaderNeeded) != 0 && _currentShader != paint.shader) { + final Shader? shader = paint.shader; + if (shader == null) { + _addOp(_CanvasOp.clearShader, 0); + } else { + _addOp(_CanvasOp.setShader, 0); + _addShader(shader); + } + _currentShader = paint.shader; + } + if ((dataNeeded & _colorFilterNeeded) != 0 && _currentColorFilter != paint.colorFilter) { + final _ColorFilter? filter = paint.colorFilter?._toNativeColorFilter(); + if (filter == null) { + _addOp(_CanvasOp.clearColorFilter, 0); + } else { + _addOp(_CanvasOp.setColorFilter, 0); + _addColorFilter(filter); + } + _currentColorFilter = paint.colorFilter; + } + if ((dataNeeded & _imageFilterNeeded) != 0 && _currentImageFilter != paint.imageFilter) { + final _ImageFilter? filter = paint.imageFilter?._toNativeImageFilter(); + if (filter == null) { + _addOp(_CanvasOp.clearImageFilter, 0); + } else { + _addOp(_CanvasOp.setImageFilter, 0); + _addImageFilter(filter); + } + _currentImageFilter = paint.imageFilter; + } + if ((dataNeeded & _maskFilterNeeded) != 0 && _currentMaskFilter != paint.maskFilter) { + final MaskFilter? filter = paint.maskFilter; + if (filter == null) { + _addOp(_CanvasOp.clearMaskFilter, 0); + } else { + _addOp(_maskFilterOps[filter._style.index], 1); + _addScalar(filter._sigma); + } + _currentMaskFilter = paint.maskFilter; + } + } + + @override + void drawPaint(Paint paint) { + _updatePaintData(paint, _paintMask); + _addOp(_CanvasOp.drawPaint, 0); + } + + @override + void drawColor(Color color, BlendMode blendMode) { + _addOp(_CanvasOp.drawColor, 2); + _addInt(color.value); + _addInt(blendMode.index); + } + + @override + void drawLine(Offset p1, Offset p2, Paint paint) { + _updatePaintData(paint, _strokeMask); + _addOp(_CanvasOp.drawLine, 2 * _nOffsetData); + _addOffset(p1); + _addOffset(p2); + _addStrokedPointToBounds(p1.dx, p1.dy); + _addStrokedPointToBounds(p2.dx, p2.dy); + } + + @override + void drawRect(Rect rect, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawRect, _nRectData); + _addRect(rect); + _addBounds(rect); + } + + @override + void drawOval(Rect rect, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawOval, _nRectData); + _addRect(rect); + _addBounds(rect); + } + + @override + void drawCircle(Offset center, double radius, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawCircle, _nOffsetData + 1); + _addOffset(center); + _addScalar(radius); + _addBounds(Rect.fromCenter(center: center, width: radius, height: radius)); + } + + @override + void drawRRect(RRect rrect, Paint paint) { + final Rect outerRect = rrect.outerRect; + if (rrect.isRect) { + drawRect(outerRect, paint); + } else if (rrect.isEllipse) { + drawOval(outerRect, paint); + } else { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawRRect, _nRoundRectData); + _addRRect(rrect); + } + _addBounds(outerRect); + } + + @override + void drawDRRect(RRect outer, RRect inner, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawDRRect, 2 * _nRoundRectData); + _addRRect(outer); + _addRRect(inner); + _addBounds(outer.outerRect); + } + + @override + void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(useCenter ? _CanvasOp.drawArcCenter : _CanvasOp.drawArc, _nRectData + 2); + _addRect(rect); + _addScalar2(startAngle, sweepAngle); + _addBounds(rect); + } + + @override + void drawPath(Path path, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawPath, 0); + _addPathData(path); + _addBounds(path.getBounds()); + } + + @override + void drawPoints(PointMode pointMode, List points, Paint paint) { + _updatePaintData(paint, _strokeMask); + _addOp(_pointOps[pointMode.index], points.length * 2 + 1); + _dataInts[_dataCount++] = points.length * 2; + final _BoundsAccumulator pointBounds = _BoundsAccumulator(); + for (final Offset pt in points) { + _dataFloats[_dataCount++] = pt.dx; + _dataFloats[_dataCount++] = pt.dy; + pointBounds.accumulate(pt.dx, pt.dy); + } + _addBounds(pointBounds.bounds, true); + } + + static const List<_CanvasOp> _pointOps = <_CanvasOp>[ + _CanvasOp.drawPoints, + _CanvasOp.drawLines, + _CanvasOp.drawPolygon, + ]; + + @override + void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) { + _updatePaintData(paint, _strokeMask); + _addOp(_pointOps[pointMode.index], points.length + 1); + _addFloat32List(points); + final _BoundsAccumulator pointBounds = _BoundsAccumulator(); + for (int i = 0; i + 1 < points.length; i += 2) { + pointBounds.accumulate(points[i], points[i + 1]); + } + _addBounds(pointBounds.bounds, true); + } + + @override + void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) { + _updatePaintData(paint, _drawMask); + _addOp(_CanvasOp.drawVertices, 1); + _addVertices(vertices); + _addInt(blendMode.index); + _addBounds(vertices._getBounds()); + } + + @override + void drawImage(Image image, Offset offset, Paint paint) { + _updatePaintData(paint, _imageMask); + _addOp(_CanvasOp.drawImage, _nOffsetData); + _addImageData(image); + _addOffset(offset); + _addBounds(Rect.fromLTWH(offset.dx, offset.dy, image.width.toDouble(), image.height.toDouble()), false); + } + + @override + void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { + _updatePaintData(paint, _imageMask); + _addOp(_CanvasOp.drawImageRect, 2 * _nRectData); + _addImageData(image); + _addRect(src); + _addRect(dst); + _addBounds(dst, false); + } + + @override + void drawImageNine(Image image, Rect center, Rect dst, Paint paint) { + _updatePaintData(paint, _imageMask); + _addOp(_CanvasOp.drawImageNine, 2 * _nRectData); + _addImageData(image); + _addRect(center); + _addRect(dst); + _addBounds(dst, false); + } + + void _addAtlasOp(int rectCount, bool hasColors, bool hasCullRect) { + if (hasColors) { + if (hasCullRect) { + // 3 list counts + 2 lists of 4 values per rect + 1 list of 1 value per rect + rect + blend mode + _addOp(_CanvasOp.drawAtlasColoredCulled, 3 + rectCount * 9 + _nRectData + 1); + } else { + // 3 list counts + 2 lists of 4 values per rect + 1 list of 1 value per rect + blend mode + _addOp(_CanvasOp.drawAtlasColored, 3 + rectCount * 9 + 1); + } + } else { + if (hasCullRect) { + // 2 list counts + 2 lists of 4 values per rect + rect + blend mode + _addOp(_CanvasOp.drawAtlasCulled, 2 + rectCount * 8 + _nRectData + 1); + } else { + // 2 list counts + 2 lists of 4 values per rect + blend mode + _addOp(_CanvasOp.drawAtlas, 2 + rectCount * 8 + 1); + } + } + } + + @override + void drawAtlas(Image atlas, + List transforms, + List rects, + List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint) { + assert(atlas != null); // atlas is checked on the engine side + assert(transforms != null); + assert(rects != null); + assert(colors == null || colors.isEmpty || blendMode != null); + assert(paint != null); + + final int rectCount = rects.length; + if (transforms.length != rectCount) + throw ArgumentError('"transforms" and "rects" lengths must match.'); + if (colors != null && colors.isNotEmpty && colors.length != rectCount) + throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".'); + + _updatePaintData(paint, _imageMask); + _addAtlasOp(rectCount, colors?.isNotEmpty ?? false, cullRect != null); + _addImageData(atlas); + _addInt((blendMode ?? BlendMode.src).index); + + _dataInts[_dataCount++] = rectCount * 4; + int rstBase = _dataCount; + _dataCount += rectCount * 4; + _dataInts[_dataCount++] = rectCount * 4; + int rectBase = _dataCount; + _dataCount += rectCount * 4; + final _BoundsAccumulator rstBounds = _BoundsAccumulator(); + for (int i = 0; i < rectCount; i++) { + final RSTransform rstTransform = transforms[i]; + final Rect rect = rects[i]; + assert(_rectIsValid(rect)); + _dataFloats[rstBase++] = rstTransform.scos; + _dataFloats[rstBase++] = rstTransform.ssin; + _dataFloats[rstBase++] = rstTransform.tx; + _dataFloats[rstBase++] = rstTransform.ty; + _dataFloats[rectBase++] = rect.left; + _dataFloats[rectBase++] = rect.top; + _dataFloats[rectBase++] = rect.right; + _dataFloats[rectBase++] = rect.bottom; + rstBounds.accumulateBounds(rect, rstTransform); + } + if (colors != null && colors.isNotEmpty) { + _dataInts[_dataCount++] = rectCount; + for (int i = 0; i < rectCount; i++) { + _dataInts[_dataCount++] = colors[i].value; + } + } + if (cullRect != null) + _addRect(cullRect); + + _addBounds(rstBounds.bounds, false); + } + + @override + void drawRawAtlas(Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List? colors, + BlendMode? blendMode, + Rect? cullRect, + Paint paint) { + final int rectCount = rects.length; + if (rstTransforms.length != rectCount) + throw ArgumentError('"rstTransforms" and "rects" lengths must match.'); + if (rectCount % 4 != 0) + throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.'); + if (colors != null && colors.length * 4 != rectCount) + throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".'); + + _updatePaintData(paint, _imageMask); + _addAtlasOp(rectCount, colors?.isNotEmpty ?? false, cullRect != null); + _addImageData(atlas); + _addInt((blendMode ?? BlendMode.src).index); + + _addFloat32List(rstTransforms); + _addFloat32List(rects); + if (colors != null && colors.isNotEmpty) + _addInt32List(colors); + if (cullRect != null) + _addRect(cullRect); + + final _BoundsAccumulator rstBounds = _BoundsAccumulator(); + for (int i = 0; i < rects.length; i += 4) { + rstBounds.accumulateRstBounds( + rects[i], rects[i+1], rects[i+2], rects[i+3], + rstTransforms[i], rstTransforms[i+1], rstTransforms[i+2], rstTransforms[i+3]); + } + _addBounds(rstBounds.bounds, false); + } + + void _drawSkiaPicture(_SkiaPicture picture) { + _addOp(_CanvasOp.drawSkPicture, 0); + _addSkPicture(picture); + _addBounds(picture._getBounds(), false); + } + + void _drawDisplayListPicture(_DisplayListPicture picture) { + _addOp(_CanvasOp.drawDisplayList, 0); + _addPicture(picture); + _addBounds(picture._drawBounds ?? Rect.zero, false); + } + + @override + void drawParagraph(Paragraph paragraph, Offset offset) { + final _SkiaPictureRecorder recorder = _SkiaPictureRecorder(); + final _SkiaCanvas canvas = _SkiaCanvas(recorder); + paragraph._paint(canvas, offset.dx, offset.dy); + _drawSkiaPicture(recorder.endRecording() as _SkiaPicture); + } + + @override + void drawPicture(Picture picture) { + if (picture is _SkiaPicture) { + _drawSkiaPicture(picture); + } else { + _drawDisplayListPicture(picture as _DisplayListPicture); + } + } + + // Constants from physical_shape_layer.cc + static const double _kLightHeight = 600; + static const double _kLightRadius = 800; + Rect _shadowBounds(Rect pathBounds, double elevation) { + final double lightRelativeHeight = _kLightHeight - elevation; + final double tx = elevation * (_kLightRadius + pathBounds.width * 0.5) / lightRelativeHeight; + final double ty = elevation * (_kLightRadius + 600 + pathBounds.height) / lightRelativeHeight; + return Rect.fromLTRB(pathBounds.left - tx, pathBounds.top, + pathBounds.right + tx, pathBounds.bottom + ty); + } + + @override + void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) { + _addOp(transparentOccluder ? _CanvasOp.drawShadowOccluded : _CanvasOp.drawShadow, 2); + _addPathData(path); + _addInt(color.value); + _addScalar(elevation); + _addBounds(_shadowBounds(path.getBounds(), elevation), false); + } +} + +/// Local storage version of Picture. +class _DisplayListPicture extends NativeFieldWrapperClass2 implements Picture { + _DisplayListPicture._(Rect cullRect, + Rect drawBounds, + Uint8List ops, int numOps, + ByteData data, int numDataBytes, + List nativeRefs) + : _cullRect = cullRect, + _drawBounds = drawBounds, + _ops = ops, + _data = data, + _nativeRefs = nativeRefs { + _constructor(ops, numOps, data, numDataBytes, nativeRefs); + } + void _constructor(Uint8List ops, int numOps, ByteData data, int dataBytes, List objects) native 'DisplayList_constructor'; + + @override + Future toImage(int width, int height) { + if (width <= 0 || height <= 0) + throw Exception('Invalid image dimensions.'); + final Uint8List? ops = _ops; + final ByteData? data = _data; + final List? nativeRefs = _nativeRefs; + if (ops == null || data == null || nativeRefs == null) + throw UnimplementedError('toImage called on disposed Picture'); + return _futurize( + (_Callback callback) => _toImage(width, height, (_Image? image) { + if (image == null) { + callback(null); + } else { + callback(Image._(image)); + } + }), + ); + } + + + String? _toImage(int width, int height, _Callback<_Image?> callback) native 'DisplayList_toImage'; + + @override + void dispose() { + _cullRect = null; + _drawBounds = null; + _ops = null; + _data = null; + _nativeRefs = null; + } + + Rect? _cullRect; + Rect? _drawBounds; + Uint8List? _ops; + ByteData? _data; + List? _nativeRefs; + + @override + int get approximateBytesUsed => (_ops?.lengthInBytes ?? 0) + (_data?.lengthInBytes ?? 0); +} + +/// Local storage version of PictureRecorder +class _DisplayListPictureRecorder implements PictureRecorder { + @override + Canvas _makeCanvas(Rect? cullRect) => _DisplayListCanvas(this, cullRect); + + @override + bool get isRecording => _canvas != null; + + @override + Picture endRecording() { + final _DisplayListCanvas? canvas = _canvas; + if (canvas == null) + throw StateError('PictureRecorder did not start recording.'); + _canvas = null; + final Picture picture = _DisplayListPicture._( + canvas._cullRect, + canvas._drawBounds, + canvas._ops, canvas._opCount, + canvas._data, canvas._dataCount, + canvas._nativeRefs, + ); + canvas._dispose(); + return picture; + } - Canvas? _canvas; + _DisplayListCanvas? _canvas; } /// A single shadow. diff --git a/lib/ui/painting/display_list.cc b/lib/ui/painting/display_list.cc new file mode 100644 index 0000000000000..580bb17a3d199 --- /dev/null +++ b/lib/ui/painting/display_list.cc @@ -0,0 +1,277 @@ +// 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 "flutter/lib/ui/painting/display_list.h" + +#include "flutter/flow/display_list_interpreter.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/make_copyable.h" +#include "flutter/lib/ui/painting/canvas.h" +#include "flutter/lib/ui/painting/image_filter.h" +#include "flutter/lib/ui/painting/path.h" +#include "flutter/lib/ui/painting/shader.h" +#include "flutter/lib/ui/ui_dart_state.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/tonic/converter/dart_converter.h" +#include "third_party/tonic/dart_binding_macros.h" +#include "third_party/tonic/dart_library_natives.h" + +namespace flutter { + +static void DisplayList_constructor(Dart_NativeArguments args) { + UIDartState::ThrowIfUIOperationsProhibited(); + DartCallConstructor(&DisplayList::Create, args); +} + +IMPLEMENT_WRAPPERTYPEINFO(ui, DisplayList); + +#define FOR_EACH_BINDING(V) V(DisplayList, toImage) + +FOR_EACH_BINDING(DART_NATIVE_CALLBACK) + +void DisplayList::RegisterNatives(tonic::DartLibraryNatives* natives) { + natives->Register( + {{"DisplayList_constructor", DisplayList_constructor, 6, true}, + FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); +} + +#define DISPLAY_LIST_GRAB_OBJECT(holder_field, type, accessor) \ + do { \ + if (obj_index >= numObjects) { \ + FML_LOG(ERROR) << "DisplayList constructor passed too few objects."; \ + return fml::MakeRefCounted(); \ + } \ + DisplayListRefHolder holder; \ + holder.holder_field = \ + tonic::DartConverter::FromDart(objects[obj_index++])->accessor; \ + ref_vector->emplace_back(holder); \ + } while (0) + +fml::RefPtr DisplayList::Create(tonic::Uint8List& ops, + int numOps, + tonic::DartByteData& data, + int numData, + Dart_Handle objList) { + if (numOps < 0 || numOps > ops.num_elements() || numData < 0 || + (data.length_in_bytes() % sizeof(float)) != 0 || + numData > (int)(data.length_in_bytes() / sizeof(float))) { + FML_LOG(ERROR) << "DisplayList constructor called with bad list lengths."; + return fml::MakeRefCounted(); + } + if (Dart_IsNull(objList) || !Dart_IsList(objList)) { + FML_LOG(ERROR) << "DisplayList constructor called with bad object array."; + return fml::MakeRefCounted(); + } + + const uint8_t* ops_ptr = ops.data(); + std::shared_ptr> ops_vector = + std::make_shared>(ops_ptr, ops_ptr + numOps); + + const float* data_ptr = static_cast(data.data()); + std::shared_ptr> data_vector = + std::make_shared>(data_ptr, data_ptr + numData); + + intptr_t numObjects = 0; + Dart_ListLength(objList, &numObjects); + Dart_Handle objects[numObjects]; + Dart_Handle result = Dart_ListGetRange(objList, 0, numObjects, objects); + if (Dart_IsError(result)) { + FML_LOG(ERROR) << "Dart Error: " << ::Dart_GetError(result); + Dart_PropagateError(result); + return fml::MakeRefCounted(); + } + + std::shared_ptr> ref_vector = + std::make_shared>(); + int obj_index = 0; + SkSamplingOptions sampling = DisplayListInterpreter::NearestSampling; + for (uint8_t op : *ops_vector) { + switch (op) { + // All of the following op types have no arguments so they can + // use continue instead of break for efficiency. + case CanvasOp::cops_setFilterQualityNearest: + sampling = DisplayListInterpreter::NearestSampling; + continue; + case CanvasOp::cops_setFilterQualityLinear: + sampling = DisplayListInterpreter::LinearSampling; + continue; + case CanvasOp::cops_setFilterQualityMipmap: + sampling = DisplayListInterpreter::MipmapSampling; + continue; + case CanvasOp::cops_setFilterQualityCubic: + sampling = DisplayListInterpreter::CubicSampling; + continue; + } + for (uint32_t args = DisplayListInterpreter::opArguments[op]; args != 0; + args >>= CANVAS_OP_ARG_SHIFT) { + switch (static_cast(args & CANVAS_OP_ARG_MASK)) { + case color_filter: { + DISPLAY_LIST_GRAB_OBJECT(colorFilter, ColorFilter, filter()); + break; + } + case image_filter: { + DISPLAY_LIST_GRAB_OBJECT(imageFilter, ImageFilter, filter()); + break; + } + case display_list: { + DISPLAY_LIST_GRAB_OBJECT(displayList, DisplayList, data()); + break; + } + case path: { + DISPLAY_LIST_GRAB_OBJECT(pathData, CanvasPath, path().serialize()); + break; + } + case shader: { + DISPLAY_LIST_GRAB_OBJECT(shader, Shader, shader(sampling)); + break; + } + case image: { + DISPLAY_LIST_GRAB_OBJECT(image, CanvasImage, image()); + break; + } + case skpicture: { + DISPLAY_LIST_GRAB_OBJECT(picture, Picture, picture()); + break; + } + case vertices: { + DISPLAY_LIST_GRAB_OBJECT(vertices, Vertices, vertices()); + break; + } + case empty: + case angle: + case color: + case blend_mode: + case matrix_row3: + case point: + case rect: + case round_rect: + case scalar: + case scalar_list: + case uint32_list: + break; + } + } + } + if (obj_index != numObjects) { + FML_LOG(ERROR) << "DisplayList constructor passed too many objects."; + return fml::MakeRefCounted(); + } + + return fml::MakeRefCounted( + std::move(ops_vector), std::move(data_vector), std::move(ref_vector)); +} + +DisplayList::DisplayList( + std::shared_ptr> ops_vector, + std::shared_ptr> data_vector, + std::shared_ptr> ref_vector) + : ops_vector_(ops_vector), + data_vector_(data_vector), + ref_vector_(ref_vector) {} + +DisplayList::DisplayList() + : ops_vector_(std::make_shared>()), + data_vector_(std::make_shared>()), + ref_vector_(std::make_shared>()) {} + +DisplayList::~DisplayList() = default; + +Dart_Handle DisplayList::toImage(uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback) { + return RasterizeToImage(ops_vector_, data_vector_, ref_vector_, width, height, + raw_image_callback); +} + +void DisplayList::dispose() { + ops_vector_.reset(); + data_vector_.reset(); + ClearDartWrapper(); +} + +size_t DisplayList::GetAllocationSize() const { + return ops_vector_->size() + (data_vector_->size() * sizeof(float)); +} + +Dart_Handle DisplayList::RasterizeToImage( + std::shared_ptr> ops, + std::shared_ptr> data, + std::shared_ptr> refs, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback) { + if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) { + return tonic::ToDart("Image callback was invalid"); + } + + if (width == 0 || height == 0) { + return tonic::ToDart("Image dimensions for scene were invalid."); + } + + auto* dart_state = UIDartState::Current(); + auto image_callback = std::make_unique( + dart_state, raw_image_callback); + auto unref_queue = dart_state->GetSkiaUnrefQueue(); + auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner(); + auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner(); + auto snapshot_delegate = dart_state->GetSnapshotDelegate(); + + // We can't create an image on this task runner because we don't have a + // graphics context. Even if we did, it would be slow anyway. Also, this + // thread owns the sole reference to the layer tree. So we flatten the layer + // tree into a picture and use that as the thread transport mechanism. + + auto picture_bounds = SkISize::Make(width, height); + + auto ui_task = fml::MakeCopyable([image_callback = std::move(image_callback), + unref_queue]( + sk_sp raster_image) mutable { + auto dart_state = image_callback->dart_state().lock(); + if (!dart_state) { + // The root isolate could have died in the meantime. + return; + } + tonic::DartState::Scope scope(dart_state); + + if (!raster_image) { + tonic::DartInvoke(image_callback->Get(), {Dart_Null()}); + return; + } + + auto dart_image = CanvasImage::Create(); + dart_image->set_image({std::move(raster_image), std::move(unref_queue)}); + auto* raw_dart_image = tonic::ToDart(std::move(dart_image)); + + // All done! + tonic::DartInvoke(image_callback->Get(), {raw_dart_image}); + + // image_callback is associated with the Dart isolate and must be deleted + // on the UI thread. + image_callback.reset(); + }); + + // Kick things off on the raster rask runner. + fml::TaskRunner::RunNowOrPostTask( + raster_task_runner, [ui_task_runner, snapshot_delegate, ops, data, refs, + picture_bounds, ui_task] { + sk_sp raster_image = snapshot_delegate->MakeRasterSnapshot( + [ops = std::move(ops), data = std::move(data), + refs = std::move(refs)](SkCanvas* canvas) { + DisplayListInterpreter interpreter(ops, data, refs); + interpreter.Rasterize(canvas); + }, + picture_bounds); + + fml::TaskRunner::RunNowOrPostTask( + ui_task_runner, + [ui_task, raster_image]() { ui_task(raster_image); }); + }); + + return Dart_Null(); +} + +} // namespace flutter diff --git a/lib/ui/painting/display_list.h b/lib/ui/painting/display_list.h new file mode 100644 index 0000000000000..82699a1bf1450 --- /dev/null +++ b/lib/ui/painting/display_list.h @@ -0,0 +1,78 @@ +// 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. + +#ifndef FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_H_ +#define FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_H_ + +#include + +#include "flutter/flow/display_list_interpreter.h" +#include "flutter/lib/ui/dart_wrapper.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/tonic/typed_data/dart_byte_data.h" +#include "third_party/tonic/typed_data/typed_list.h" + +namespace tonic { +class DartLibraryNatives; +} // namespace tonic + +namespace flutter { + +class DisplayList : public RefCountedDartWrappable { + DEFINE_WRAPPERTYPEINFO(); + FML_FRIEND_MAKE_REF_COUNTED(DisplayList); + + public: + ~DisplayList() override; + + static fml::RefPtr Create(tonic::Uint8List& ops, + int numOps, + tonic::DartByteData& data, + int numData, + Dart_Handle objList); + + Dart_Handle toImage(uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback); + + DisplayListData data() { return {ops_vector_, data_vector_, ref_vector_}; } + + void dispose(); + + size_t GetAllocationSize() const override; + + static Dart_Handle RasterizeToImage( + std::shared_ptr> ops, + std::shared_ptr> data, + std::shared_ptr> refs, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback); + + static void RegisterNatives(tonic::DartLibraryNatives* natives); + + std::shared_ptr> ops_vector() { return ops_vector_; } + std::shared_ptr> data_vector() { return data_vector_; } + std::shared_ptr> ref_vector() { + return ref_vector_; + } + + private: + DisplayList(std::shared_ptr> ops_vector, + std::shared_ptr> data_vector, + std::shared_ptr> ref_vector); + + DisplayList(); + + std::shared_ptr> ops_vector_; + std::shared_ptr> data_vector_; + std::shared_ptr> ref_vector_; +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_H_ diff --git a/lib/ui/painting/picture.cc b/lib/ui/painting/picture.cc index 5225d993ba4fb..91d39c1812042 100644 --- a/lib/ui/painting/picture.cc +++ b/lib/ui/painting/picture.cc @@ -23,6 +23,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Picture); #define FOR_EACH_BINDING(V) \ V(Picture, toImage) \ + V(Picture, getBounds) \ V(Picture, dispose) \ V(Picture, GetAllocationSize) @@ -42,6 +43,18 @@ Picture::Picture(flutter::SkiaGPUObject picture) Picture::~Picture() = default; +tonic::Float32List Picture::getBounds() { + tonic::Float32List rect(Dart_NewTypedData(Dart_TypedData_kFloat32, 4)); + if (auto picture = picture_.get()) { + const SkRect& bounds = picture->cullRect(); + rect[0] = bounds.left(); + rect[1] = bounds.top(); + rect[2] = bounds.right(); + rect[3] = bounds.bottom(); + } + return rect; +} + Dart_Handle Picture::toImage(uint32_t width, uint32_t height, Dart_Handle raw_image_callback) { diff --git a/lib/ui/painting/picture.h b/lib/ui/painting/picture.h index e0158d400ff5e..b660772fe737a 100644 --- a/lib/ui/painting/picture.h +++ b/lib/ui/painting/picture.h @@ -10,6 +10,7 @@ #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/ui_dart_state.h" #include "third_party/skia/include/core/SkPicture.h" +#include "third_party/tonic/typed_data/typed_list.h" namespace tonic { class DartLibraryNatives; @@ -33,6 +34,8 @@ class Picture : public RefCountedDartWrappable { uint32_t height, Dart_Handle raw_image_callback); + tonic::Float32List getBounds(); + void dispose(); size_t GetAllocationSize() const override; diff --git a/lib/ui/painting/vertices.cc b/lib/ui/painting/vertices.cc index 8617aee51f554..fc8ea3dfc221c 100644 --- a/lib/ui/painting/vertices.cc +++ b/lib/ui/painting/vertices.cc @@ -31,7 +31,9 @@ void DecodeInts(const tonic::Int32List& ints, T* out) { IMPLEMENT_WRAPPERTYPEINFO(ui, Vertices); -#define FOR_EACH_BINDING(V) V(Vertices, init) +#define FOR_EACH_BINDING(V) \ + V(Vertices, init) \ + V(Vertices, getBounds) FOR_EACH_BINDING(DART_NATIVE_CALLBACK) @@ -94,6 +96,16 @@ bool Vertices::init(Dart_Handle vertices_handle, return true; } +tonic::Float32List Vertices::getBounds() { + tonic::Float32List rect(Dart_NewTypedData(Dart_TypedData_kFloat32, 4)); + const SkRect& bounds = vertices_->bounds(); + rect[0] = bounds.left(); + rect[1] = bounds.top(); + rect[2] = bounds.right(); + rect[3] = bounds.bottom(); + return rect; +} + size_t Vertices::GetAllocationSize() const { return sizeof(Vertices) + vertices_->approximateSize(); } diff --git a/lib/ui/painting/vertices.h b/lib/ui/painting/vertices.h index 1b4c8601df806..c34a4057955e7 100644 --- a/lib/ui/painting/vertices.h +++ b/lib/ui/painting/vertices.h @@ -33,6 +33,8 @@ class Vertices : public RefCountedDartWrappable { const sk_sp& vertices() const { return vertices_; } + tonic::Float32List getBounds(); + size_t GetAllocationSize() const override; private: diff --git a/lib/ui/snapshot_delegate.h b/lib/ui/snapshot_delegate.h index ad9b8ef1f3612..a1f7c618b0653 100644 --- a/lib/ui/snapshot_delegate.h +++ b/lib/ui/snapshot_delegate.h @@ -12,6 +12,10 @@ namespace flutter { class SnapshotDelegate { public: + virtual sk_sp MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) = 0; + virtual sk_sp MakeRasterSnapshot(sk_sp picture, SkISize picture_size) = 0; diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 7281f5f29dddc..da488be80a3e6 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -315,6 +315,12 @@ sk_sp Rasterizer::DoMakeRasterSnapshot( return result; } +sk_sp Rasterizer::MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) { + return DoMakeRasterSnapshot(picture_size, draw_callback); +} + sk_sp Rasterizer::MakeRasterSnapshot(sk_sp picture, SkISize picture_size) { return DoMakeRasterSnapshot(picture_size, diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index 1f1559cbb9466..8abd3f2b56b4e 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -464,6 +464,11 @@ class Rasterizer final : public SnapshotDelegate { std::shared_ptr external_view_embedder_; bool shared_engine_block_thread_merging_ = false; + // |SnapshotDelegate| + sk_sp MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) override; + // |SnapshotDelegate| sk_sp MakeRasterSnapshot(sk_sp picture, SkISize picture_size) override;