Flutter Impeller
canvas.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <memory>
8 #include <optional>
9 #include <unordered_map>
10 #include <utility>
11 
12 #include "display_list/dl_vertices.h"
13 #include "display_list/effects/color_filters/dl_blend_color_filter.h"
14 #include "display_list/effects/color_filters/dl_matrix_color_filter.h"
15 #include "display_list/effects/dl_color_filter.h"
16 #include "display_list/effects/dl_color_source.h"
17 #include "display_list/effects/dl_image_filter.h"
18 #include "display_list/image/dl_image.h"
19 #include "flutter/fml/logging.h"
20 #include "flutter/fml/trace_event.h"
23 #include "impeller/core/formats.h"
69 
70 namespace impeller {
71 
72 namespace {
73 
74 constexpr Scalar kAntialiasPadding = 1.0f;
75 
76 bool IsPipelineBlendOrMatrixFilter(const flutter::DlColorFilter* filter) {
77  return filter->type() == flutter::DlColorFilterType::kMatrix ||
78  (filter->type() == flutter::DlColorFilterType::kBlend &&
79  filter->asBlend()->mode() <= Entity::kLastPipelineBlendMode);
80 }
81 
82 static bool UseColorSourceContents(
83  const std::shared_ptr<VerticesGeometry>& vertices,
84  const Paint& paint) {
85  // If there are no vertex color or texture coordinates. Or if there
86  // are vertex coordinates but its just a color.
87  if (vertices->HasVertexColors()) {
88  return false;
89  }
90  if (vertices->HasTextureCoordinates() && !paint.color_source) {
91  return true;
92  }
93  return !vertices->HasTextureCoordinates();
94 }
95 
96 static void SetClipScissor(std::optional<Rect> clip_coverage,
97  RenderPass& pass,
98  Point global_pass_position) {
99  // Set the scissor to the clip coverage area. We do this prior to rendering
100  // the clip itself and all its contents.
101  IRect32 scissor;
102  if (clip_coverage.has_value()) {
103  clip_coverage = clip_coverage->Shift(-global_pass_position);
104  scissor = IRect32::RoundOut(clip_coverage.value());
105  // The scissor rect must not exceed the size of the render target.
106  scissor =
107  scissor.Intersection(IRect32::MakeSize(pass.GetRenderTargetSize()))
108  .value_or(IRect32());
109  }
110  pass.SetScissor(scissor);
111 }
112 
113 static void ApplyFramebufferBlend(Entity& entity) {
114  auto src_contents = entity.GetContents();
115  auto contents = std::make_shared<FramebufferBlendContents>();
116  contents->SetChildContents(src_contents);
117  contents->SetBlendMode(entity.GetBlendMode());
118  entity.SetContents(std::move(contents));
119  entity.SetBlendMode(BlendMode::kSrc);
120 }
121 
122 /// @brief Create the subpass restore contents, appling any filters or opacity
123 /// from the provided paint object.
124 static std::shared_ptr<Contents> CreateContentsForSubpassTarget(
125  const ContentContext& renderer,
126  const Paint& paint,
127  const std::shared_ptr<Texture>& target,
128  const Matrix& effect_transform) {
129  auto contents = TextureContents::MakeRect(Rect::MakeSize(target->GetSize()));
130  contents->SetTexture(target);
131  contents->SetLabel("Subpass");
132  contents->SetSourceRect(Rect::MakeSize(target->GetSize()));
133  contents->SetOpacity(paint.color.alpha);
134  contents->SetDeferApplyingOpacity(true);
135 
136  return paint.WithFiltersForSubpassTarget(renderer, std::move(contents),
137  effect_transform);
138 }
139 
140 static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig =
141  RenderTarget::AttachmentConfig{
142  .storage_mode = StorageMode::kDeviceTransient,
143  .load_action = LoadAction::kDontCare,
144  .store_action = StoreAction::kDontCare,
145  };
146 
147 static std::unique_ptr<EntityPassTarget> CreateRenderTarget(
148  ContentContext& renderer,
149  ISize size,
150  const Color& clear_color) {
151  const std::shared_ptr<Context>& context = renderer.GetContext();
152 
153  /// All of the load/store actions are managed by `InlinePassContext` when
154  /// `RenderPasses` are created, so we just set them to `kDontCare` here.
155  /// What's important is the `StorageMode` of the textures, which cannot be
156  /// changed for the lifetime of the textures.
157 
158  RenderTarget target;
159  if (context->GetCapabilities()->SupportsOffscreenMSAA()) {
160  target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA(
161  /*context=*/*context,
162  /*size=*/size,
163  /*mip_count=*/1,
164  /*label=*/"EntityPass",
165  /*color_attachment_config=*/
166  RenderTarget::AttachmentConfigMSAA{
167  .storage_mode = StorageMode::kDeviceTransient,
168  .resolve_storage_mode = StorageMode::kDevicePrivate,
169  .load_action = LoadAction::kDontCare,
170  .store_action = StoreAction::kMultisampleResolve,
171  .clear_color = clear_color},
172  /*stencil_attachment_config=*/kDefaultStencilConfig);
173  } else {
174  target = renderer.GetRenderTargetCache()->CreateOffscreen(
175  *context, // context
176  size, // size
177  /*mip_count=*/1,
178  "EntityPass", // label
179  RenderTarget::AttachmentConfig{
180  .storage_mode = StorageMode::kDevicePrivate,
181  .load_action = LoadAction::kDontCare,
182  .store_action = StoreAction::kDontCare,
183  .clear_color = clear_color,
184  }, // color_attachment_config
185  kDefaultStencilConfig //
186  );
187  }
188 
189  return std::make_unique<EntityPassTarget>(
190  target, //
191  renderer.GetDeviceCapabilities().SupportsReadFromResolve(), //
192  renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() //
193  );
194 }
195 
196 /// @brief Expands the rectangle to satisfy a 1-device-pixel minimum size using
197 /// the given transform and scales alpha based on the ratio of local
198 /// areas when `scale_alpha` is true.
199 ///
200 /// If the shape is scaled to zero under the transform or if its alpha
201 /// scales close to zero, an empty `Rect` is returned to indicate that
202 /// the shape is effectively invisible.
203 static std::pair<Rect, Color> ExpandRectToPixelMinimum(const Rect& rect,
204  const Color& color,
205  const Matrix& transform,
206  bool scale_alpha) {
207  std::optional<Rect> expanded =
208  rect.ExpandToMinTransformedSize({1.0f, 1.0f}, transform);
209  if (!expanded) {
210  // Rect is scaled to 0.
211  return {Rect(), color};
212  }
213 
214  // No alpha scaling needed.
215  if (!scale_alpha) {
216  return {expanded.value(), color};
217  }
218 
219  // Scale alpha based on expanded ratio.
220  Scalar alpha_scaling = rect.Area() / expanded->Area();
221  if (alpha_scaling < kEhCloseEnough) {
222  // Rect is effectively invisible.
223  return {Rect(), color};
224  }
225  return {expanded.value(), color.WithAlpha(color.alpha * alpha_scaling)};
226 }
227 
228 } // namespace
229 
230 class Canvas::RRectBlurShape : public BlurShape {
231  public:
232  RRectBlurShape(const Rect& rect, Scalar corner_radius)
233  : rect_(rect), corner_radius_(corner_radius) {}
234 
235  Rect GetBounds() const override { return rect_; }
236 
237  std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
238  auto contents = std::make_shared<SolidRRectBlurContents>();
239  contents->SetSigma(sigma);
240  contents->SetShape(rect_, corner_radius_);
241  return contents;
242  }
243 
244  const Geometry& BuildDrawGeometry() override {
245  return geom_.emplace(rect_, Size(corner_radius_));
246  }
247 
248  private:
249  const Rect rect_;
250  const Scalar corner_radius_;
251 
252  std::optional<RoundRectGeometry> geom_; // optional stack allocation
253 };
254 
255 class Canvas::RSuperellipseBlurShape : public BlurShape {
256  public:
257  RSuperellipseBlurShape(const Rect& rect, Scalar corner_radius)
258  : rect_(rect), corner_radius_(corner_radius) {}
259 
260  Rect GetBounds() const override { return rect_; }
261 
262  std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
263  auto contents = std::make_shared<SolidRSuperellipseBlurContents>();
264  contents->SetSigma(sigma);
265  contents->SetShape(rect_, corner_radius_);
266  return contents;
267  }
268 
269  const Geometry& BuildDrawGeometry() override {
270  return geom_.emplace(rect_, corner_radius_);
271  }
272 
273  private:
274  const Rect rect_;
275  const Scalar corner_radius_;
276 
277  std::optional<RoundSuperellipseGeometry> geom_; // optional stack allocation
278 };
279 
280 class Canvas::PathBlurShape : public BlurShape {
281  public:
282  /// Construct a PathBlurShape from a path source, a set of shadow vertices
283  /// (typically produced by ShadowPathGeometry) and the sigma that was used
284  /// to generate the vertex mesh.
285  ///
286  /// The sigma was already used to generate the shadow vertices, so it is
287  /// provided here only to make sure it matches the sigma we will see in
288  /// our BuildBlurContent method.
289  ///
290  /// The source was used to generate the mesh and it might be used again
291  /// for the SOLID mask operation so we save it here in case the mask
292  /// rendering code calls our BuildDrawGeometry method. Its lifetime
293  /// must survive the lifetime of this object, typically because the
294  /// source object was stack allocated not long before this object is
295  /// also being stack allocated.
296  PathBlurShape(const PathSource& source [[clang::lifetimebound]],
297  std::shared_ptr<ShadowVertices> shadow_vertices,
298  Sigma sigma)
299  : sigma_(sigma),
300  source_(source),
301  shadow_vertices_(std::move(shadow_vertices)) {}
302 
303  Rect GetBounds() const override {
304  return shadow_vertices_->GetBounds().value_or(Rect());
305  }
306 
307  std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
308  // We have to use the sigma to generate the mesh up front in order to
309  // even know if we can perform the operation, but then the method that
310  // actually uses our contents informs us of the sigma, but it's too
311  // late to make use of it. Instead we remember what sigma we used and
312  // make sure they match.
313  FML_DCHECK(sigma_.sigma == sigma.sigma);
314  return ShadowVerticesContents::Make(shadow_vertices_);
315  }
316 
317  const Geometry& BuildDrawGeometry() override {
318  return source_geometry_.emplace(source_);
319  }
320 
321  private:
322  const Sigma sigma_;
323  const PathSource& source_;
324  const std::shared_ptr<ShadowVertices> shadow_vertices_;
325 
326  // optional stack allocation - for BuildGeometry
327  std::optional<FillPathFromSourceGeometry> source_geometry_;
328 };
329 
331  const RenderTarget& render_target,
332  bool is_onscreen,
333  bool requires_readback)
334  : renderer_(renderer),
335  render_target_(render_target),
336  is_onscreen_(is_onscreen),
337  requires_readback_(requires_readback),
338  clip_coverage_stack_(EntityPassClipStack(
339  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
340  Initialize(std::nullopt);
341  SetupRenderPass();
342 }
343 
345  const RenderTarget& render_target,
346  bool is_onscreen,
347  bool requires_readback,
348  Rect cull_rect)
349  : renderer_(renderer),
350  render_target_(render_target),
351  is_onscreen_(is_onscreen),
352  requires_readback_(requires_readback),
353  clip_coverage_stack_(EntityPassClipStack(
354  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
355  Initialize(cull_rect);
356  SetupRenderPass();
357 }
358 
360  const RenderTarget& render_target,
361  bool is_onscreen,
362  bool requires_readback,
363  IRect32 cull_rect)
364  : renderer_(renderer),
365  render_target_(render_target),
366  is_onscreen_(is_onscreen),
367  requires_readback_(requires_readback),
368  clip_coverage_stack_(EntityPassClipStack(
369  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
370  Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(),
371  cull_rect.GetRight(), cull_rect.GetBottom()));
372  SetupRenderPass();
373 }
374 
375 void Canvas::Initialize(std::optional<Rect> cull_rect) {
376  initial_cull_rect_ = cull_rect;
377  transform_stack_.emplace_back(CanvasStackEntry{
379  });
380  FML_DCHECK(GetSaveCount() == 1u);
381 }
382 
383 void Canvas::Reset() {
384  current_depth_ = 0u;
385  transform_stack_ = {};
386 }
387 
389  transform_stack_.back().transform = GetCurrentTransform() * transform;
390 }
391 
393  transform_stack_.back().transform = transform * GetCurrentTransform();
394 }
395 
397  transform_stack_.back().transform = {};
398 }
399 
401  Concat(transform);
402 }
403 
405  return transform_stack_.back().transform;
406 }
407 
408 void Canvas::Translate(const Vector3& offset) {
410 }
411 
412 void Canvas::Scale(const Vector2& scale) {
413  Concat(Matrix::MakeScale(scale));
414 }
415 
416 void Canvas::Scale(const Vector3& scale) {
417  Concat(Matrix::MakeScale(scale));
418 }
419 
420 void Canvas::Skew(Scalar sx, Scalar sy) {
421  Concat(Matrix::MakeSkew(sx, sy));
422 }
423 
424 void Canvas::Rotate(Radians radians) {
425  Concat(Matrix::MakeRotationZ(radians));
426 }
427 
428 Point Canvas::GetGlobalPassPosition() const {
429  if (save_layer_state_.empty()) {
430  return Point(0, 0);
431  }
432  return save_layer_state_.back().coverage.GetOrigin();
433 }
434 
435 // clip depth of the previous save or 0.
436 size_t Canvas::GetClipHeightFloor() const {
437  if (transform_stack_.size() > 1) {
438  return transform_stack_[transform_stack_.size() - 2].clip_height;
439  }
440  return 0;
441 }
442 
443 size_t Canvas::GetSaveCount() const {
444  return transform_stack_.size();
445 }
446 
447 bool Canvas::IsSkipping() const {
448  return transform_stack_.back().skipping;
449 }
450 
451 void Canvas::RestoreToCount(size_t count) {
452  while (GetSaveCount() > count) {
453  if (!Restore()) {
454  return;
455  }
456  }
457 }
458 
459 void Canvas::DrawPath(const flutter::DlPath& path, const Paint& paint) {
460  if (IsShadowBlurDrawOperation(paint)) {
461  if (AttemptDrawBlurredPathSource(path, paint)) {
462  return;
463  }
464  }
465 
466  Entity entity;
468  entity.SetBlendMode(paint.blend_mode);
469 
470  if (paint.style == Paint::Style::kFill) {
471  FillPathGeometry geom(path);
472  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
473  } else {
474  StrokePathGeometry geom(path, paint.stroke);
475  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
476  }
477 }
478 
479 void Canvas::DrawPaint(const Paint& paint) {
480  Entity entity;
482  entity.SetBlendMode(paint.blend_mode);
483 
484  CoverGeometry geom;
485  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
486 }
487 
488 // Optimization: if the texture has a color filter that is a simple
489 // porter-duff blend or matrix filter, then instead of performing a save layer
490 // we should swap out the shader for the porter duff blend shader and avoid a
491 // saveLayer. This optimization is important for Flame.
492 bool Canvas::AttemptColorFilterOptimization(
493  const std::shared_ptr<Texture>& image,
494  Rect source,
495  Rect dest,
496  const Paint& paint,
497  const SamplerDescriptor& sampler,
498  SourceRectConstraint src_rect_constraint) {
499  if (!paint.color_filter || //
500  paint.image_filter != nullptr || //
501  paint.invert_colors || //
502  paint.mask_blur_descriptor.has_value() || //
503  !IsPipelineBlendOrMatrixFilter(paint.color_filter)) {
504  return false;
505  }
506 
507  if (paint.color_filter->type() == flutter::DlColorFilterType::kBlend) {
508  const flutter::DlBlendColorFilter* blend_filter =
509  paint.color_filter->asBlend();
510  DrawImageRectAtlasGeometry geometry = DrawImageRectAtlasGeometry(
511  /*texture=*/image,
512  /*source=*/source,
513  /*destination=*/dest,
514  /*color=*/skia_conversions::ToColor(blend_filter->color()),
515  /*blend_mode=*/blend_filter->mode(),
516  /*desc=*/sampler,
517  /*use_strict_src_rect=*/src_rect_constraint ==
519 
520  auto atlas_contents = std::make_shared<AtlasContents>();
521  atlas_contents->SetGeometry(&geometry);
522  atlas_contents->SetAlpha(paint.color.alpha);
523 
524  Entity entity;
525  entity.SetTransform(GetCurrentTransform());
526  entity.SetBlendMode(paint.blend_mode);
527  entity.SetContents(atlas_contents);
528 
529  AddRenderEntityToCurrentPass(entity);
530  } else {
531  // src_rect_constraint is only supported in the porter-duff mode
532  // for now.
533  if (src_rect_constraint == SourceRectConstraint::kStrict) {
534  return false;
535  }
536 
537  const flutter::DlMatrixColorFilter* matrix_filter =
538  paint.color_filter->asMatrix();
539 
540  DrawImageRectAtlasGeometry geometry = DrawImageRectAtlasGeometry(
541  /*texture=*/image,
542  /*source=*/source,
543  /*destination=*/dest,
544  /*color=*/Color::Khaki(), // ignored
545  /*blend_mode=*/BlendMode::kSrcOver, // ignored
546  /*desc=*/sampler,
547  /*use_strict_src_rect=*/src_rect_constraint ==
549 
550  auto atlas_contents = std::make_shared<ColorFilterAtlasContents>();
551  atlas_contents->SetGeometry(&geometry);
552  atlas_contents->SetAlpha(paint.color.alpha);
553  impeller::ColorMatrix color_matrix;
554  matrix_filter->get_matrix(color_matrix.array);
555  atlas_contents->SetMatrix(color_matrix);
556 
557  Entity entity;
558  entity.SetTransform(GetCurrentTransform());
559  entity.SetBlendMode(paint.blend_mode);
560  entity.SetContents(atlas_contents);
561 
562  AddRenderEntityToCurrentPass(entity);
563  }
564  return true;
565 }
566 
567 bool Canvas::AttemptDrawAntialiasedCircle(const Point& center,
568  Scalar radius,
569  const Paint& paint) {
570  if (paint.HasColorFilter() || paint.image_filter || paint.invert_colors ||
571  paint.color_source || paint.mask_blur_descriptor.has_value()) {
572  return false;
573  }
574 
575  Entity entity;
576  entity.SetTransform(GetCurrentTransform());
577  entity.SetBlendMode(paint.blend_mode);
578 
579  const bool is_stroked = paint.style == Paint::Style::kStroke;
580  std::unique_ptr<CircleGeometry> geom;
581  if (is_stroked) {
582  geom = std::make_unique<CircleGeometry>(center, radius, paint.stroke.width);
583  } else {
584  geom = std::make_unique<CircleGeometry>(center, radius);
585  }
586  geom->SetAntialiasPadding(kAntialiasPadding);
587 
588  auto contents =
589  CircleContents::Make(std::move(geom), paint.color, is_stroked);
590  entity.SetContents(std::move(contents));
591  AddRenderEntityToCurrentPass(entity);
592 
593  return true;
594 }
595 
596 bool Canvas::IsShadowBlurDrawOperation(const Paint& paint) {
597  if (paint.style != Paint::Style::kFill) {
598  return false;
599  }
600 
601  if (paint.color_source) {
602  return false;
603  }
604 
605  if (!paint.mask_blur_descriptor.has_value()) {
606  return false;
607  }
608 
609  // A blur sigma that is not positive enough should not result in a blur.
610  // We test both the sigma value and the converted radius value as the
611  // algorithms might use either and either indicates the blur is too small
612  // to be noticeable.
613  if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) {
614  return false;
615  }
616  Radius radius = paint.mask_blur_descriptor->sigma;
617  if (radius.radius <= kEhCloseEnough) {
618  return false;
619  }
620 
621  return true;
622 }
623 
624 bool Canvas::AttemptDrawBlurredPathSource(const PathSource& source,
625  const Paint& paint) {
626  FML_DCHECK(IsShadowBlurDrawOperation);
627 
628  // This has_value() test should always succeed as it is checked by the
629  // IsShadowBlurDrawOperation method which should have been called before
630  // this method, but we check again here to avoid warnings from the
631  // following code.
632  if (paint.mask_blur_descriptor.has_value()) {
633  // This value was determined by empirical eyesight tests so that the
634  // shadow mesh results will match the results of the shape-specific
635  // optimized shadow shaders.
636  static constexpr Scalar kSigmaScale = 2.8f;
637 
638  Sigma sigma = paint.mask_blur_descriptor->sigma;
639  const Matrix& matrix = GetCurrentTransform();
640  Scalar basis_scale = matrix.GetMaxBasisLengthXY();
641  Scalar device_radius = sigma.sigma * kSigmaScale * basis_scale;
642  std::shared_ptr<ShadowVertices> shadow_vertices =
644  renderer_.GetTessellator(), source, device_radius, matrix);
645  if (shadow_vertices) {
646  PathBlurShape shape(source, std::move(shadow_vertices), sigma);
647  return AttemptDrawBlur(shape, paint);
648  }
649  }
650  return false;
651 }
652 
653 Scalar Canvas::GetCommonRRectLikeRadius(const RoundingRadii& radii) {
654  if (!radii.AreAllCornersSame()) {
655  return -1;
656  }
657  const Size& corner_radii = radii.top_left;
658  if (ScalarNearlyEqual(corner_radii.width, corner_radii.height)) {
659  return corner_radii.width;
660  }
661  return -1;
662 }
663 
664 bool Canvas::AttemptDrawBlurredRRect(const RoundRect& round_rect,
665  const Paint& paint) {
666  Scalar radius = GetCommonRRectLikeRadius(round_rect.GetRadii());
667  if (radius < 0) {
668  RoundRectPathSource source(round_rect);
669  return AttemptDrawBlurredPathSource(source, paint);
670  }
671  RRectBlurShape shape(round_rect.GetBounds(), radius);
672  return AttemptDrawBlur(shape, paint);
673 }
674 
675 bool Canvas::AttemptDrawBlurredRSuperellipse(const RoundSuperellipse& rse,
676  const Paint& paint) {
677  Scalar radius = GetCommonRRectLikeRadius(rse.GetRadii());
678  if (radius < 0) {
679  RoundSuperellipsePathSource source(rse);
680  return AttemptDrawBlurredPathSource(source, paint);
681  }
682  RSuperellipseBlurShape shape(rse.GetBounds(), radius);
683  return AttemptDrawBlur(shape, paint);
684 }
685 
686 bool Canvas::AttemptDrawBlur(BlurShape& shape, const Paint& paint) {
687  FML_DCHECK(IsShadowBlurDrawOperation(paint));
688 
689  // For symmetrically mask blurred solid RRects, absorb the mask blur and use
690  // a faster SDF approximation.
691  Color rrect_color = paint.color;
692  if (paint.invert_colors) {
693  rrect_color = rrect_color.ApplyColorMatrix(kColorInversion);
694  }
695  if (paint.color_filter) {
696  rrect_color = GetCPUColorFilterProc(paint.color_filter)(rrect_color);
697  }
698 
699  Paint rrect_paint = {.mask_blur_descriptor = paint.mask_blur_descriptor};
700 
701  if (!rrect_paint.mask_blur_descriptor.has_value()) {
702  // This should never happen in practice because the caller would have
703  // first called |IsShadowBlurDrawOperation| on the paint object, but
704  // we test anyway to make the compiler happy about the dereferences
705  // below.
706  return false;
707  }
708 
709  // In some cases, we need to render the mask blur to a separate layer.
710  //
711  // 1. If the blur style is normal, we'll be drawing using one draw call and
712  // no clips. And so we can just wrap the RRect contents with the
713  // ImageFilter, which will get applied to the result as per usual.
714  //
715  // 2. If the blur style is solid, we combine the non-blurred RRect with the
716  // blurred RRect via two separate draw calls, and so we need to defer any
717  // fancy blending, translucency, or image filtering until after these two
718  // draws have been combined in a separate layer.
719  //
720  // 3. If the blur style is outer or inner, we apply the blur style via a
721  // clip. The ImageFilter needs to be applied to the mask blurred result.
722  // And so if there's an ImageFilter, we need to defer applying it until
723  // after the clipped RRect blur has been drawn to a separate texture.
724  // However, since there's only one draw call that produces color, we
725  // don't need to worry about the blend mode or translucency (unlike with
726  // BlurStyle::kSolid).
727  //
728  if ((paint.mask_blur_descriptor->style !=
730  paint.image_filter) ||
731  (paint.mask_blur_descriptor->style == FilterContents::BlurStyle::kSolid &&
732  (!rrect_color.IsOpaque() || paint.blend_mode != BlendMode::kSrcOver))) {
733  Rect render_bounds = shape.GetBounds();
734  if (paint.mask_blur_descriptor->style !=
736  render_bounds =
737  render_bounds.Expand(paint.mask_blur_descriptor->sigma.sigma * 4.0);
738  }
739  // Defer the alpha, blend mode, and image filter to a separate layer.
740  SaveLayer(
741  Paint{
742  .color = Color::White().WithAlpha(rrect_color.alpha),
743  .image_filter = paint.image_filter,
744  .blend_mode = paint.blend_mode,
745  },
746  render_bounds, nullptr, ContentBoundsPromise::kContainsContents, 1u);
747  rrect_paint.color = rrect_color.WithAlpha(1);
748  } else {
749  rrect_paint.color = rrect_color;
750  rrect_paint.blend_mode = paint.blend_mode;
751  rrect_paint.image_filter = paint.image_filter;
752  Save(1u);
753  }
754 
755  auto draw_blurred_rrect = [this, &rrect_paint, &shape]() {
756  std::shared_ptr<SolidBlurContents> contents =
757  shape.BuildBlurContent(rrect_paint.mask_blur_descriptor->sigma);
758  FML_DCHECK(contents);
759 
760  contents->SetColor(rrect_paint.color);
761 
762  Entity blurred_rrect_entity;
763  blurred_rrect_entity.SetTransform(GetCurrentTransform());
764  blurred_rrect_entity.SetBlendMode(rrect_paint.blend_mode);
765 
766  rrect_paint.mask_blur_descriptor = std::nullopt;
767  blurred_rrect_entity.SetContents(
768  rrect_paint.WithFilters(renderer_, std::move(contents)));
769  AddRenderEntityToCurrentPass(blurred_rrect_entity);
770  };
771 
772  switch (rrect_paint.mask_blur_descriptor->style) {
774  draw_blurred_rrect();
775  break;
776  }
778  // First, draw the blurred RRect.
779  draw_blurred_rrect();
780  // Then, draw the non-blurred RRect on top.
781  Entity entity;
782  entity.SetTransform(GetCurrentTransform());
783  entity.SetBlendMode(rrect_paint.blend_mode);
784 
785  const Geometry& geom = shape.BuildDrawGeometry();
786  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, rrect_paint,
787  /*reuse_depth=*/true);
788  break;
789  }
791  const Geometry& geom = shape.BuildDrawGeometry();
793  draw_blurred_rrect();
794  break;
795  }
797  const Geometry& geom = shape.BuildDrawGeometry();
799  draw_blurred_rrect();
800  break;
801  }
802  }
803 
804  Restore();
805 
806  return true;
807 }
808 
809 bool Canvas::AttemptDrawLineSDF(const Point& p0,
810  const Point& p1,
811  const Paint& paint,
812  bool reuse_depth) {
813  if (!(renderer_.GetContext()->GetFlags().use_sdfs ||
814  renderer_.GetContext()->GetFlags().antialiased_lines) ||
816  return false;
817  }
818  // Draw the line as a filled rectangle with width=line_length and
819  // height=stroke_width.
820 
821  Paint rect_paint = paint;
822  rect_paint.style = Paint::Style::kFill;
823 
824  Scalar line_length = p0.GetDistance(p1);
825  if (line_length == 0.0f && paint.stroke.cap == Cap::kButt) {
826  // 0 length line with butt caps is invisible.
827  return true;
828  }
829  Scalar half_stroke_width = paint.stroke.width * 0.5f;
830  Scalar half_length = line_length * 0.5f;
831 
832  // For Butt stroke caps, the rect width is line_length. For Square and Round
833  // stroke caps, the rect extends past the line's endpoints by
834  // half_stroke_width at each end.
835  if (paint.stroke.cap != Cap::kButt) {
836  half_length += half_stroke_width;
837  }
838 
839  // The axis-aligned origin-centered rect which the line will be drawn as.
840  Rect rect = Rect::MakeEllipseBounds(Point(0.0f, 0.0f),
841  Point(half_length, half_stroke_width));
842 
843  // A transform matrix is used to rotate and translate the rect to match the
844  // position of the input line.
845 
846  // Unit vector along the line. Fallback to (1, 0) if length is 0.
847  Vector2 u =
848  line_length > 0.0f ? ((p1 - p0) / line_length) : Point(1.0f, 0.0f);
849  Vector2 perp = u.PerpendicularRight();
850  Point center = (p0 + p1) * 0.5f;
851  Matrix rect_to_line_transform = Matrix::MakeColumn(
852  // X basis: unit vector along the line
853  u.x, u.y, 0.0f, 0.0f,
854  // Y basis: unit vector perpendicular to the line
855  perp.x, perp.y, 0.0f, 0.0f,
856  // Z basis: unchanged
857  0.0f, 0.0f, 1.0f, 0.0f,
858  // Translation: to line center
859  center.x, center.y, 0.0f, 1.0f);
860 
861  // Expand rect to 1 pixel minimum dimensions if applicable.
862  if (!GetCurrentTransform().HasPerspective2D()) {
863  auto [expanded, alpha_scaled_color] = ExpandRectToPixelMinimum(
864  rect, paint.color, GetCurrentTransform() * rect_to_line_transform,
865  // Don't scale alpha when stroke width is 0. This draws a hairline that
866  // is always 1 pixel regardless of the transform.
867  /*scale_alpha=*/paint.stroke.width != 0.0f);
868 
869  if (expanded.IsEmpty()) {
870  // Line is invisible due to transform scaling or alpha scaling.
871  return true;
872  }
873 
874  rect_paint.color = alpha_scaled_color;
875  rect = expanded;
876  }
877 
878  UberSDFParameters params;
879  if (paint.stroke.cap == Cap::kRound) {
881  /*color=*/rect_paint.color,
882  /*rect=*/rect,
883  /*radii=*/
884  RoundingRadii::MakeRadius(rect.GetHeight() * 0.5f),
885  /*stroke=*/std::nullopt);
886  } else {
888  /*color=*/rect_paint.color,
889  /*rect=*/rect,
890  /*stroke=*/std::nullopt);
891  }
892  AddRenderSDFEntityToCurrentPass(paint, params, reuse_depth,
893  /*shape_transform=*/rect_to_line_transform);
894  return true;
895 }
896 
897 void Canvas::DrawLine(const Point& p0,
898  const Point& p1,
899  const Paint& paint,
900  bool reuse_depth) {
901  if (AttemptDrawLineSDF(p0, p1, paint, reuse_depth)) {
902  return;
903  }
904 
905  Entity entity;
907  entity.SetBlendMode(paint.blend_mode);
908 
909  auto geometry = std::make_unique<LineGeometry>(p0, p1, paint.stroke);
910 
911  AddRenderEntityWithFiltersToCurrentPass(entity, geometry.get(), paint,
912  reuse_depth);
913 }
914 
916  const Point& p1,
917  Scalar on_length,
918  Scalar off_length,
919  const Paint& paint) {
920  // Reasons to defer to regular DrawLine:
921  // - performance for degenerate and "regular line" cases
922  // - length is non-positive - DrawLine will draw appropriate "dot"
923  // - off_length is non-positive - no gaps, DrawLine will draw it solid
924  // - on_length is negative - invalid dashing
925  //
926  // Note that a 0 length "on" dash will draw "dot"s every "off" distance
927  // apart so we proceed with the dashing process in that case.
928  Scalar length = p0.GetDistance(p1);
929  if (length > 0.0f && on_length >= 0.0f && off_length > 0.0f) {
930  Entity entity;
932  entity.SetBlendMode(paint.blend_mode);
933 
934  StrokeDashedLineGeometry geom(p0, p1, on_length, off_length, paint.stroke);
935  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
936  } else {
937  DrawLine(p0, p1, paint);
938  }
939 }
940 
941 void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
942  if (paint.style == Paint::Style::kFill && rect.IsEmpty()) {
943  return;
944  }
945 
946  if (IsShadowBlurDrawOperation(paint)) {
947  RRectBlurShape shape(rect, 0.0f);
948  if (AttemptDrawBlur(shape, paint)) {
949  return;
950  }
951  }
952 
953  if (renderer_.GetContext()->GetFlags().use_sdfs &&
955  Rect effective_rect = rect;
956  Color effective_color = paint.color;
957 
958  // Expand rect to 1 pixel minimum dimensions if applicable.
959  if (paint.style == Paint::Style::kFill &&
960  !GetCurrentTransform().HasPerspective2D()) {
961  auto [expanded, alpha_scaled_color] = ExpandRectToPixelMinimum(
962  rect, paint.color, GetCurrentTransform(), /*scale_alpha=*/true);
963 
964  if (expanded.IsEmpty()) {
965  // Rect is invisible due to transform scaling or alpha scaling.
966  return;
967  }
968 
969  effective_rect = expanded;
970  effective_color = alpha_scaled_color;
971  }
972 
973  auto params = UberSDFParameters::MakeRect(
974  /*color=*/effective_color,
975  /*rect=*/effective_rect,
976  /*stroke=*/paint.GetStroke());
977  AddRenderSDFEntityToCurrentPass(paint, params);
978  return;
979  }
980 
981  Entity entity;
983  entity.SetBlendMode(paint.blend_mode);
984 
985  if (paint.style == Paint::Style::kStroke) {
986  StrokeRectGeometry geom(rect, paint.stroke);
987  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
988  } else {
989  FillRectGeometry geom(rect);
990  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
991  }
992 }
993 
994 void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
995  // TODO(jonahwilliams): This additional condition avoids an assert in the
996  // stroke circle geometry generator. I need to verify the condition that this
997  // assert prevents.
998  if (rect.IsSquare() && (paint.style == Paint::Style::kFill ||
999  (paint.style == Paint::Style::kStroke &&
1000  paint.stroke.width < rect.GetWidth()))) {
1001  // Circles have slightly less overhead and can do stroking
1002  DrawCircle(rect.GetCenter(), rect.GetWidth() * 0.5f, paint);
1003  return;
1004  }
1005 
1006  if (IsShadowBlurDrawOperation(paint)) {
1007  if (rect.IsSquare()) {
1008  // RRectBlurShape takes the corner radii which are half of the
1009  // overall width and height of the DrawOval bounds rect.
1010  RRectBlurShape shape(rect, rect.GetWidth() * 0.5f);
1011  if (AttemptDrawBlur(shape, paint)) {
1012  return;
1013  }
1014  } else {
1015  EllipsePathSource source(rect);
1016  if (AttemptDrawBlurredPathSource(source, paint)) {
1017  return;
1018  }
1019  }
1020  }
1021 
1022  Entity entity;
1024  entity.SetBlendMode(paint.blend_mode);
1025 
1026  if (renderer_.GetContext()->GetFlags().use_sdfs &&
1028  UberSDFParameters params;
1029 
1030  if (paint.style == Paint::Style::kStroke) {
1031  params = UberSDFParameters::MakeOval(paint.color, rect, paint.stroke);
1032  } else {
1033  params = UberSDFParameters::MakeOval(paint.color, rect, std::nullopt);
1034  }
1035 
1036  AddRenderSDFEntityToCurrentPass(paint, params);
1037  return;
1038  }
1039 
1040  if (paint.style == Paint::Style::kStroke) {
1041  StrokeEllipseGeometry geom(rect, paint.stroke);
1042  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1043  } else {
1044  EllipseGeometry geom(rect);
1045  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1046  }
1047 }
1048 
1049 void Canvas::DrawArc(const Arc& arc, const Paint& paint) {
1050  Entity entity;
1052  entity.SetBlendMode(paint.blend_mode);
1053 
1054  if (paint.style == Paint::Style::kFill) {
1055  ArcGeometry geom(arc);
1056  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1057  return;
1058  }
1059 
1060  const Rect& oval_bounds = arc.GetOvalBounds();
1061  if (paint.stroke.width > oval_bounds.GetSize().MaxDimension()) {
1062  // This is a special case for rendering arcs whose stroke width is so large
1063  // you are effectively drawing a sector of a circle.
1064  // https://github.com/flutter/flutter/issues/158567
1065  Arc expanded_arc(oval_bounds.Expand(Size(paint.stroke.width * 0.5f)),
1066  arc.GetStart(), arc.GetSweep(), true);
1067 
1068  ArcGeometry geom(expanded_arc);
1069  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1070  return;
1071  }
1072 
1073  // IncludeCenter incurs lots of extra work for stroking an arc, including:
1074  // - It introduces segments to/from the center point (not too hard).
1075  // - It introduces joins on those segments (a bit more complicated).
1076  // - Even if the sweep is >=360 degrees, we still draw the segment to
1077  // the center and it basically looks like a pie cut into the complete
1078  // boundary circle, as if the slice were cut, but not extracted
1079  // (hard to express as a continuous kTriangleStrip).
1080  if (!arc.IncludeCenter()) {
1081  if (arc.IsFullCircle()) {
1082  return DrawOval(oval_bounds, paint);
1083  }
1084 
1085  // Our fast stroking code only works for circular bounds as it assumes
1086  // that the inner and outer radii can be scaled along each angular step
1087  // of the arc - which is not true for elliptical arcs where the inner
1088  // and outer samples are perpendicular to the traveling direction of the
1089  // elliptical curve which may not line up with the center of the bounds.
1090  if (oval_bounds.IsSquare()) {
1091  ArcGeometry geom(arc, paint.stroke);
1092  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1093  return;
1094  }
1095  }
1096 
1097  ArcStrokeGeometry geom(arc, paint.stroke);
1098  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1099 }
1100 
1101 void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) {
1102  if (paint.style == Paint::Style::kFill && round_rect.IsEmpty()) {
1103  return;
1104  }
1105 
1106  if (IsShadowBlurDrawOperation(paint)) {
1107  if (AttemptDrawBlurredRRect(round_rect, paint)) {
1108  return;
1109  }
1110  }
1111 
1112  const RoundingRadii& radii = round_rect.GetRadii();
1113 
1114  if (renderer_.GetContext()->GetFlags().use_sdfs &&
1116  Color effective_color = paint.color;
1117  Rect bounds = round_rect.GetBounds();
1118 
1119  // Expand rrect bounds to 1 pixel minimum dimensions if applicable.
1120  if (paint.style == Paint::Style::kFill &&
1121  !GetCurrentTransform().HasPerspective2D()) {
1122  auto [expanded, alpha_scaled_color] = ExpandRectToPixelMinimum(
1123  bounds, paint.color, GetCurrentTransform(), /*scale_alpha=*/true);
1124 
1125  if (expanded.IsEmpty()) {
1126  // RRect is invisible due to transform scaling or alpha scaling.
1127  return;
1128  }
1129 
1130  bounds = expanded;
1131  effective_color = alpha_scaled_color;
1132  }
1133 
1134  auto params = UberSDFParameters::MakeRoundedRect(
1135  /*color=*/effective_color,
1136  /*rect=*/bounds,
1137  /*radii=*/radii,
1138  /*stroke=*/paint.style == Paint::Style::kStroke
1139  ? std::make_optional(paint.stroke)
1140  : std::nullopt);
1141  AddRenderSDFEntityToCurrentPass(paint, params);
1142  return;
1143  }
1144 
1145  if (round_rect.GetRadii().AreAllCornersSame() &&
1146  paint.style == Paint::Style::kFill) {
1147  Entity entity;
1149  entity.SetBlendMode(paint.blend_mode);
1150 
1151  RoundRectGeometry geom(round_rect.GetBounds(),
1152  round_rect.GetRadii().top_left);
1153  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1154  return;
1155  }
1156 
1157  Entity entity;
1159  entity.SetBlendMode(paint.blend_mode);
1160 
1161  if (paint.style == Paint::Style::kFill) {
1162  FillRoundRectGeometry geom(round_rect);
1163  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1164  } else {
1165  StrokeRoundRectGeometry geom(round_rect, paint.stroke);
1166  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1167  }
1168 }
1169 
1171  const RoundRect& inner,
1172  const Paint& paint) {
1173  Entity entity;
1175  entity.SetBlendMode(paint.blend_mode);
1176 
1177  if (paint.style == Paint::Style::kFill) {
1178  FillDiffRoundRectGeometry geom(outer, inner);
1179  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1180  } else {
1181  StrokeDiffRoundRectGeometry geom(outer, inner, paint.stroke);
1182  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1183  }
1184 }
1185 
1186 void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse,
1187  const Paint& paint) {
1188  if (IsShadowBlurDrawOperation(paint)) {
1189  if (AttemptDrawBlurredRSuperellipse(round_superellipse, paint)) {
1190  return;
1191  }
1192  }
1193 
1194  Entity entity;
1196  entity.SetBlendMode(paint.blend_mode);
1197 
1198  if (renderer_.GetContext()->GetFlags().use_sdfs &&
1200  auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii(
1201  round_superellipse.GetBounds(), round_superellipse.GetRadii());
1202 
1203  if (round_superellipse_params.all_corners_same) {
1205  /*color=*/paint.color,
1206  /*bounds=*/round_superellipse.GetBounds(),
1207  /*round_superellipse_params=*/round_superellipse_params,
1208  /*stroke=*/paint.GetStroke());
1209 
1210  AddRenderSDFEntityToCurrentPass(paint, params);
1211  return;
1212  } else {
1214  /*color=*/paint.color_source ? Color::White() : paint.color,
1215  /*bounds=*/round_superellipse.GetBounds(),
1216  /*round_superellipse_params=*/round_superellipse_params,
1217  /*stroke=*/paint.GetStroke());
1218 
1219  const Geometry* geom = contents->GetGeometry();
1220 
1221  if (paint.color_source) {
1222  std::shared_ptr<Contents> color_source_contents =
1223  paint.CreateContents(renderer_, geom);
1224  std::shared_ptr<Contents> final_contents =
1226  BlendMode::kSrcIn, {FilterInput::Make(std::move(contents)),
1227  FilterInput::Make(color_source_contents)});
1228 
1229  Paint new_paint = paint;
1230  new_paint.color_source = nullptr;
1231  AddRenderEntityWithFiltersToCurrentPass(entity, geom, new_paint,
1232  /*reuse_depth=*/false,
1233  /*override_contents=*/
1234  std::move(final_contents));
1235  } else {
1236  AddRenderEntityWithFiltersToCurrentPass(entity, geom, paint,
1237  /*reuse_depth=*/false,
1238  /*override_contents=*/
1239  std::move(contents));
1240  }
1241  return;
1242  }
1243  }
1244 
1245  if (paint.style == Paint::Style::kFill) {
1246  RoundSuperellipseGeometry geom(round_superellipse.GetBounds(),
1247  round_superellipse.GetRadii());
1248  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1249  } else {
1250  StrokeRoundSuperellipseGeometry geom(round_superellipse, paint.stroke);
1251  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1252  }
1253 }
1254 
1255 void Canvas::DrawCircle(const Point& center,
1256  Scalar radius,
1257  const Paint& paint) {
1258  if (IsShadowBlurDrawOperation(paint)) {
1259  Rect bounds = Rect::MakeCircleBounds(center, radius);
1260  RRectBlurShape shape(bounds, radius);
1261  if (AttemptDrawBlur(shape, paint)) {
1262  return;
1263  }
1264  }
1265 
1266  if (renderer_.GetContext()->GetFlags().use_sdfs &&
1268  auto params = UberSDFParameters::MakeCircle(
1269  /*color=*/paint.color, /*center=*/center, /*radius=*/radius,
1270  /*stroke=*/paint.GetStroke());
1271  AddRenderSDFEntityToCurrentPass(paint, params);
1272  return;
1273  }
1274 
1275  if (AttemptDrawAntialiasedCircle(center, radius, paint)) {
1276  return;
1277  }
1278 
1279  Entity entity;
1281  entity.SetBlendMode(paint.blend_mode);
1282 
1283  if (paint.style == Paint::Style::kStroke) {
1284  CircleGeometry geom(center, radius, paint.stroke.width);
1285  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1286  } else {
1287  CircleGeometry geom(center, radius);
1288  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1289  }
1290 }
1291 
1292 void Canvas::ClipGeometry(const Geometry& geometry,
1293  Entity::ClipOperation clip_op,
1294  bool is_aa) {
1295  if (IsSkipping()) {
1296  return;
1297  }
1298 
1299  // Ideally the clip depth would be greater than the current rendering
1300  // depth because any rendering calls that follow this clip operation will
1301  // pre-increment the depth and then be rendering above our clip depth,
1302  // but that case will be caught by the CHECK in AddRenderEntity above.
1303  // In practice we sometimes have a clip set with no rendering after it
1304  // and in such cases the current depth will equal the clip depth.
1305  // Eventually the DisplayList should optimize these out, but it is hard
1306  // to know if a clip will actually be used in advance of storing it in
1307  // the DisplayList buffer.
1308  // See https://github.com/flutter/flutter/issues/147021
1309  FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth)
1310  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
1311  uint32_t clip_depth = transform_stack_.back().clip_depth;
1312 
1313  const Matrix clip_transform =
1314  Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) *
1316 
1317  std::optional<Rect> clip_coverage = geometry.GetCoverage(clip_transform);
1318  if (!clip_coverage.has_value()) {
1319  return;
1320  }
1321 
1322  ClipContents clip_contents(
1323  clip_coverage.value(),
1324  /*is_axis_aligned_rect=*/geometry.IsAxisAlignedRect() &&
1325  GetCurrentTransform().IsTranslationScaleOnly());
1326  clip_contents.SetClipOperation(clip_op);
1327 
1328  EntityPassClipStack::ClipStateResult clip_state_result =
1329  clip_coverage_stack_.RecordClip(
1330  clip_contents, //
1331  /*transform=*/clip_transform, //
1332  /*global_pass_position=*/GetGlobalPassPosition(), //
1333  /*clip_depth=*/clip_depth, //
1334  /*clip_height_floor=*/GetClipHeightFloor(), //
1335  /*is_aa=*/is_aa);
1336 
1337  if (clip_state_result.clip_did_change) {
1338  // We only need to update the pass scissor if the clip state has changed.
1339  SetClipScissor(
1340  clip_coverage_stack_.CurrentClipCoverage(),
1341  *render_passes_.back().GetInlinePassContext()->GetRenderPass(),
1342  GetGlobalPassPosition());
1343  }
1344 
1345  ++transform_stack_.back().clip_height;
1346  ++transform_stack_.back().num_clips;
1347 
1348  if (!clip_state_result.should_render) {
1349  return;
1350  }
1351 
1352  // Note: this is a bit of a hack. Its not possible to construct a geometry
1353  // result without begninning the render pass. We should refactor the geometry
1354  // objects so that they only need a reference to the render pass size and/or
1355  // orthographic transform.
1356  Entity entity;
1357  entity.SetTransform(clip_transform);
1358  entity.SetClipDepth(clip_depth);
1359 
1360  GeometryResult geometry_result = geometry.GetPositionBuffer(
1361  renderer_, //
1362  entity, //
1363  *render_passes_.back().GetInlinePassContext()->GetRenderPass() //
1364  );
1365  clip_contents.SetGeometry(geometry_result);
1366  clip_coverage_stack_.GetLastReplayResult().clip_contents.SetGeometry(
1367  geometry_result);
1368 
1369  clip_contents.Render(
1370  renderer_, *render_passes_.back().GetInlinePassContext()->GetRenderPass(),
1371  clip_depth);
1372 }
1373 
1375  uint32_t count,
1376  Scalar radius,
1377  const Paint& paint,
1378  PointStyle point_style) {
1379  if (radius <= 0) {
1380  return;
1381  }
1382 
1383  Entity entity;
1385  entity.SetBlendMode(paint.blend_mode);
1386 
1387  PointFieldGeometry geom(points, count, radius,
1388  /*round=*/point_style == PointStyle::kRound);
1389  AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
1390 }
1391 
1392 void Canvas::DrawImage(const std::shared_ptr<Texture>& image,
1393  Point offset,
1394  const Paint& paint,
1395  const SamplerDescriptor& sampler) {
1396  if (!image) {
1397  return;
1398  }
1399 
1400  const Rect source = Rect::MakeSize(image->GetSize());
1401  const Rect dest = source.Shift(offset);
1402 
1403  DrawImageRect(image, source, dest, paint, sampler);
1404 }
1405 
1406 void Canvas::DrawImageRect(const std::shared_ptr<Texture>& image,
1407  Rect source,
1408  Rect dest,
1409  const Paint& paint,
1410  const SamplerDescriptor& sampler,
1411  SourceRectConstraint src_rect_constraint) {
1412  if (!image || source.IsEmpty() || dest.IsEmpty()) {
1413  return;
1414  }
1415 
1416  ISize size = image->GetSize();
1417  if (size.IsEmpty()) {
1418  return;
1419  }
1420 
1421  std::optional<Rect> clipped_source =
1422  source.Intersection(Rect::MakeSize(size));
1423  if (!clipped_source) {
1424  return;
1425  }
1426 
1427  if (AttemptColorFilterOptimization(image, source, dest, paint, sampler,
1428  src_rect_constraint)) {
1429  return;
1430  }
1431 
1432  if (*clipped_source != source) {
1433  Scalar sx = dest.GetWidth() / source.GetWidth();
1434  Scalar sy = dest.GetHeight() / source.GetHeight();
1435  Scalar tx = dest.GetLeft() - source.GetLeft() * sx;
1436  Scalar ty = dest.GetTop() - source.GetTop() * sy;
1437  Matrix src_to_dest = Matrix::MakeTranslateScale({sx, sy, 1}, {tx, ty, 0});
1438  dest = clipped_source->TransformBounds(src_to_dest);
1439  }
1440 
1441  auto texture_contents = TextureContents::MakeRect(dest);
1442  texture_contents->SetTexture(image);
1443  texture_contents->SetSourceRect(*clipped_source);
1444  texture_contents->SetStrictSourceRect(src_rect_constraint ==
1446  texture_contents->SetSamplerDescriptor(sampler);
1447  texture_contents->SetOpacity(paint.color.alpha);
1448  texture_contents->SetDeferApplyingOpacity(paint.HasColorFilter());
1449 
1450  Entity entity;
1451  entity.SetBlendMode(paint.blend_mode);
1453 
1454  if (!paint.mask_blur_descriptor.has_value()) {
1455  entity.SetContents(
1456  paint.WithFilters(renderer_, std::move(texture_contents)));
1457  AddRenderEntityToCurrentPass(entity);
1458  return;
1459  }
1460 
1461  FillRectGeometry out_rect(Rect{});
1462 
1463  entity.SetContents(paint.WithFilters(
1464  renderer_,
1465  paint.mask_blur_descriptor->CreateMaskBlur(texture_contents, &out_rect)));
1466  AddRenderEntityToCurrentPass(entity);
1467 }
1468 
1469 size_t Canvas::GetClipHeight() const {
1470  return transform_stack_.back().clip_height;
1471 }
1472 
1473 void Canvas::DrawVertices(const std::shared_ptr<VerticesGeometry>& vertices,
1474  BlendMode blend_mode,
1475  const Paint& paint) {
1476  // Override the blend mode with kDestination in order to match the behavior
1477  // of Skia's SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER flag, which
1478  // is enabled when the Flutter engine builds Skia.
1479  if (!paint.color_source) {
1480  blend_mode = BlendMode::kDst;
1481  }
1482 
1483  Entity entity;
1485  entity.SetBlendMode(paint.blend_mode);
1486 
1487  // If there are no vertex colors.
1488  if (UseColorSourceContents(vertices, paint)) {
1489  AddRenderEntityWithFiltersToCurrentPass(entity, vertices.get(), paint);
1490  return;
1491  }
1492 
1493  // If the blend mode is destination don't bother to bind or create a texture.
1494  if (blend_mode == BlendMode::kDst) {
1495  auto contents = std::make_shared<VerticesSimpleBlendContents>();
1496  contents->SetBlendMode(blend_mode);
1497  contents->SetAlpha(paint.color.alpha);
1498  contents->SetGeometry(vertices);
1499  entity.SetContents(paint.WithFilters(renderer_, std::move(contents)));
1500  AddRenderEntityToCurrentPass(entity);
1501  return;
1502  }
1503 
1504  // If there is a texture, use this directly. Otherwise render the color
1505  // source to a texture.
1506  if (paint.color_source &&
1507  paint.color_source->type() == flutter::DlColorSourceType::kImage) {
1508  const flutter::DlImageColorSource* image_color_source =
1509  paint.color_source->asImage();
1510  FML_DCHECK(image_color_source);
1511  auto texture =
1512  image_color_source->image()->asImpellerImage()->GetCachedTexture(
1513  renderer_);
1514  FML_DCHECK(texture);
1515  auto x_tile_mode = static_cast<Entity::TileMode>(
1516  image_color_source->horizontal_tile_mode());
1517  auto y_tile_mode =
1518  static_cast<Entity::TileMode>(image_color_source->vertical_tile_mode());
1519  auto sampler_descriptor =
1520  skia_conversions::ToSamplerDescriptor(image_color_source->sampling());
1521  auto effect_transform = image_color_source->matrix();
1522 
1523  auto contents = std::make_shared<VerticesSimpleBlendContents>();
1524  contents->SetBlendMode(blend_mode);
1525  contents->SetAlpha(paint.color.alpha);
1526  contents->SetGeometry(vertices);
1527  contents->SetEffectTransform(effect_transform);
1528  contents->SetTexture(texture);
1529  contents->SetTileMode(x_tile_mode, y_tile_mode);
1530  contents->SetSamplerDescriptor(sampler_descriptor);
1531 
1532  entity.SetContents(paint.WithFilters(renderer_, std::move(contents)));
1533  AddRenderEntityToCurrentPass(entity);
1534  return;
1535  }
1536 
1537  auto src_paint = paint;
1538  src_paint.color = paint.color.WithAlpha(1.0);
1539 
1540  std::shared_ptr<ColorSourceContents> src_contents =
1541  src_paint.CreateContents(renderer_, vertices.get());
1542 
1543  // If the color source has an intrinsic size, then we use that to
1544  // create the src contents as a simplification. Otherwise we use
1545  // the extent of the texture coordinates to determine how large
1546  // the src contents should be. If neither has a value we fall back
1547  // to using the geometry coverage data.
1548  Rect src_coverage;
1549  auto size = src_contents->GetColorSourceSize();
1550  if (size.has_value()) {
1551  src_coverage = Rect::MakeXYWH(0, 0, size->width, size->height);
1552  } else {
1553  auto cvg = vertices->GetCoverage(Matrix{});
1554  FML_CHECK(cvg.has_value());
1555  auto texture_coverage = vertices->GetTextureCoordinateCoverage();
1556  if (texture_coverage.has_value()) {
1557  src_coverage =
1558  Rect::MakeOriginSize(texture_coverage->GetOrigin(),
1559  texture_coverage->GetSize().Max({1, 1}));
1560  } else {
1561  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1562  src_coverage = cvg.value();
1563  }
1564  }
1565  clip_geometry_.push_back(Geometry::MakeRect(Rect::Round(src_coverage)));
1566  src_contents =
1567  src_paint.CreateContents(renderer_, clip_geometry_.back().get());
1568 
1569  auto contents = std::make_shared<VerticesSimpleBlendContents>();
1570  contents->SetBlendMode(blend_mode);
1571  contents->SetAlpha(paint.color.alpha);
1572  contents->SetGeometry(vertices);
1573  contents->SetLazyTextureCoverage(src_coverage);
1574  contents->SetLazyTexture(
1575  [src_contents, src_coverage](
1576  const ContentContext& renderer) -> std::shared_ptr<Texture> {
1577  // Applying the src coverage as the coverage limit prevents the 1px
1578  // coverage pad from adding a border that is picked up by developer
1579  // specified UVs.
1580  auto snapshot = src_contents->RenderToSnapshot(
1581  renderer, {}, {.coverage_limit = Rect::Round(src_coverage)});
1582  return snapshot.has_value() ? snapshot->texture : nullptr;
1583  });
1584  entity.SetContents(paint.WithFilters(renderer_, std::move(contents)));
1585  AddRenderEntityToCurrentPass(entity);
1586 }
1587 
1588 void Canvas::DrawAtlas(const std::shared_ptr<AtlasContents>& atlas_contents,
1589  const Paint& paint) {
1590  atlas_contents->SetAlpha(paint.color.alpha);
1591 
1592  Entity entity;
1594  entity.SetBlendMode(paint.blend_mode);
1595  entity.SetContents(paint.WithFilters(renderer_, atlas_contents));
1596 
1597  AddRenderEntityToCurrentPass(entity);
1598 }
1599 
1600 /// Compositor Functionality
1601 /////////////////////////////////////////
1602 
1603 void Canvas::SetupRenderPass() {
1604  renderer_.GetRenderTargetCache()->Start();
1605  ColorAttachment color0 = render_target_.GetColorAttachment(0);
1606 
1607  auto& stencil_attachment = render_target_.GetStencilAttachment();
1608  auto& depth_attachment = render_target_.GetDepthAttachment();
1609  if (!stencil_attachment.has_value() || !depth_attachment.has_value()) {
1610  // Setup a new root stencil with an optimal configuration if one wasn't
1611  // provided by the caller.
1612  render_target_.SetupDepthStencilAttachments(
1613  *renderer_.GetContext(),
1614  *renderer_.GetContext()->GetResourceAllocator(),
1615  color0.texture->GetSize(),
1616  renderer_.GetContext()->GetCapabilities()->SupportsOffscreenMSAA() &&
1617  color0.texture->GetTextureDescriptor().sample_count >
1619  "ImpellerOnscreen", kDefaultStencilConfig);
1620  }
1621 
1622  // Set up the clear color of the root pass.
1624  render_target_.SetColorAttachment(color0, 0);
1625 
1626  // If requires_readback is true, then there is a backdrop filter or emulated
1627  // advanced blend in the first save layer. This requires a readback, which
1628  // isn't supported by onscreen textures. To support this, we immediately begin
1629  // a second save layer with the same dimensions as the onscreen. When
1630  // rendering is completed, we must blit this saveLayer to the onscreen.
1631  if (requires_readback_) {
1632  auto entity_pass_target =
1633  CreateRenderTarget(renderer_, //
1634  color0.texture->GetSize(), //
1635  /*clear_color=*/Color::BlackTransparent());
1636  render_passes_.push_back(
1637  LazyRenderingConfig(renderer_, std::move(entity_pass_target)));
1638  } else {
1639  auto entity_pass_target = std::make_unique<EntityPassTarget>(
1640  render_target_, //
1643  );
1644  render_passes_.push_back(
1645  LazyRenderingConfig(renderer_, std::move(entity_pass_target)));
1646  }
1647 }
1648 
1649 void Canvas::SkipUntilMatchingRestore(size_t total_content_depth) {
1650  auto entry = CanvasStackEntry{};
1651  entry.skipping = true;
1652  entry.clip_depth = current_depth_ + total_content_depth;
1653  transform_stack_.push_back(entry);
1654 }
1655 
1656 void Canvas::Save(uint32_t total_content_depth) {
1657  if (IsSkipping()) {
1658  return SkipUntilMatchingRestore(total_content_depth);
1659  }
1660 
1661  auto entry = CanvasStackEntry{};
1662  entry.transform = transform_stack_.back().transform;
1663  entry.clip_depth = current_depth_ + total_content_depth;
1664  entry.distributed_opacity = transform_stack_.back().distributed_opacity;
1665  FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth)
1666  << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth
1667  << " after allocating " << total_content_depth;
1668  entry.clip_height = transform_stack_.back().clip_height;
1669  entry.rendering_mode = Entity::RenderingMode::kDirect;
1670  transform_stack_.push_back(entry);
1671 }
1672 
1673 std::optional<Rect> Canvas::GetLocalCoverageLimit() const {
1674  if (!clip_coverage_stack_.HasCoverage()) {
1675  // The current clip is empty. This means the pass texture won't be
1676  // visible, so skip it.
1677  return std::nullopt;
1678  }
1679 
1680  std::optional<Rect> maybe_current_clip_coverage =
1681  clip_coverage_stack_.CurrentClipCoverage();
1682  if (!maybe_current_clip_coverage.has_value()) {
1683  return std::nullopt;
1684  }
1685 
1686  Rect current_clip_coverage = maybe_current_clip_coverage.value();
1687 
1688  FML_CHECK(!render_passes_.empty());
1689  const LazyRenderingConfig& back_render_pass = render_passes_.back();
1690  std::shared_ptr<Texture> back_texture =
1691  back_render_pass.GetInlinePassContext()->GetTexture();
1692  FML_CHECK(back_texture) << "Context is valid:"
1693  << back_render_pass.GetInlinePassContext()->IsValid();
1694 
1695  // The maximum coverage of the subpass. Subpasses textures should never
1696  // extend outside the parent pass texture or the current clip coverage.
1697  std::optional<Rect> maybe_coverage_limit =
1698  Rect::MakeOriginSize(GetGlobalPassPosition(),
1699  Size(back_texture->GetSize()))
1700  .Intersection(current_clip_coverage);
1701 
1702  if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) {
1703  return std::nullopt;
1704  }
1705 
1706  return maybe_coverage_limit->Intersection(
1707  Rect::MakeSize(render_target_.GetRenderTargetSize()));
1708 }
1709 
1710 void Canvas::SaveLayer(const Paint& paint,
1711  std::optional<Rect> bounds,
1712  const flutter::DlImageFilter* backdrop_filter,
1713  ContentBoundsPromise bounds_promise,
1714  uint32_t total_content_depth,
1715  bool can_distribute_opacity,
1716  std::optional<int64_t> backdrop_id) {
1717  TRACE_EVENT0("flutter", "Canvas::saveLayer");
1718  if (IsSkipping()) {
1719  return SkipUntilMatchingRestore(total_content_depth);
1720  }
1721 
1722  auto maybe_coverage_limit = GetLocalCoverageLimit();
1723  if (!maybe_coverage_limit.has_value()) {
1724  return SkipUntilMatchingRestore(total_content_depth);
1725  }
1726  auto coverage_limit = maybe_coverage_limit.value();
1727 
1728  if (can_distribute_opacity && !backdrop_filter &&
1730  bounds_promise != ContentBoundsPromise::kMayClipContents) {
1731  Save(total_content_depth);
1732  transform_stack_.back().distributed_opacity *= paint.color.alpha;
1733  return;
1734  }
1735 
1736  std::shared_ptr<FilterContents> filter_contents = paint.WithImageFilter(
1737  renderer_, Rect(), transform_stack_.back().transform,
1739 
1740  std::optional<Rect> maybe_subpass_coverage = ComputeSaveLayerCoverage(
1741  bounds.value_or(Rect::MakeMaximum()),
1742  transform_stack_.back().transform, //
1743  coverage_limit, //
1744  filter_contents, //
1745  /*flood_output_coverage=*/
1747  /*flood_input_coverage=*/!!backdrop_filter ||
1748  (paint.color_filter &&
1749  paint.color_filter->modifies_transparent_black()) //
1750  );
1751 
1752  if (!maybe_subpass_coverage.has_value()) {
1753  return SkipUntilMatchingRestore(total_content_depth);
1754  }
1755 
1756  auto subpass_coverage = maybe_subpass_coverage.value();
1757 
1758  // When an image filter is present, clamp to avoid flicking due to nearest
1759  // sampled image. For other cases, round out to ensure than any geometry is
1760  // not cut off.
1761  //
1762  // See also this bug: https://github.com/flutter/flutter/issues/144213
1763  //
1764  // TODO(jonahwilliams): this could still round out for filters that use decal
1765  // sampling mode.
1767  bool did_round_out = false;
1768  Point coverage_origin_adjustment = Point{0, 0};
1769  if (paint.image_filter) {
1770  subpass_size = ISize(subpass_coverage.GetSize());
1771  } else {
1772  did_round_out = true;
1773  subpass_size =
1774  static_cast<ISize>(IRect::RoundOut(subpass_coverage).GetSize());
1775  // If rounding out, adjust the coverage to account for the subpixel shift.
1776  coverage_origin_adjustment =
1777  Point(subpass_coverage.GetLeftTop().x -
1778  std::floor(subpass_coverage.GetLeftTop().x),
1779  subpass_coverage.GetLeftTop().y -
1780  std::floor(subpass_coverage.GetLeftTop().y));
1781  }
1782  if (subpass_size.IsEmpty()) {
1783  return SkipUntilMatchingRestore(total_content_depth);
1784  }
1785 
1786  // When there are scaling filters present, these contents may exceed the
1787  // maximum texture size. Perform a clamp here, which may cause rendering
1788  // artifacts.
1789  subpass_size = subpass_size.Min(renderer_.GetContext()
1790  ->GetCapabilities()
1791  ->GetMaximumRenderPassAttachmentSize());
1792 
1793  // Backdrop filter state, ignored if there is no BDF.
1794  std::shared_ptr<FilterContents> backdrop_filter_contents;
1795  Point local_position = Point(0, 0);
1796  if (backdrop_filter) {
1797  local_position = subpass_coverage.GetOrigin() - GetGlobalPassPosition();
1798 
1799  std::shared_ptr<Texture> input_texture;
1800 
1801  // If the backdrop ID is not nullopt and there is more than one usage
1802  // of it in the current scene, cache the backdrop texture and remove it from
1803  // the current entity pass flip.
1804  bool will_cache_backdrop_texture = false;
1805  BackdropData* backdrop_data = nullptr;
1806  // If we've reached this point, there is at least one backdrop filter. But
1807  // potentially more if there is a backdrop id. We may conditionally set this
1808  // to a higher value in the if block below.
1809  size_t backdrop_count = 1;
1810  if (backdrop_id.has_value()) {
1811  std::unordered_map<int64_t, BackdropData>::iterator backdrop_data_it =
1812  backdrop_data_.find(backdrop_id.value());
1813  if (backdrop_data_it != backdrop_data_.end()) {
1814  backdrop_data = &backdrop_data_it->second;
1815  will_cache_backdrop_texture =
1816  backdrop_data_it->second.backdrop_count > 1;
1817  backdrop_count = backdrop_data_it->second.backdrop_count;
1818  }
1819  }
1820 
1821  if (!will_cache_backdrop_texture || !backdrop_data->texture_slot) {
1822  backdrop_count_ -= backdrop_count;
1823 
1824  // The onscreen texture can be flipped to if:
1825  // 1. The device supports framebuffer fetch
1826  // 2. There are no more backdrop filters
1827  // 3. The current render pass is for the onscreen pass.
1828  const bool should_use_onscreen =
1830  backdrop_count_ == 0 && render_passes_.size() == 1u;
1831  input_texture = FlipBackdrop(
1832  GetGlobalPassPosition(), //
1833  /*should_remove_texture=*/will_cache_backdrop_texture, //
1834  /*should_use_onscreen=*/should_use_onscreen //
1835  );
1836  if (!input_texture) {
1837  // Validation failures are logged in FlipBackdrop.
1838  return;
1839  }
1840 
1841  if (will_cache_backdrop_texture) {
1842  backdrop_data->texture_slot = input_texture;
1843  }
1844  } else {
1845  input_texture = backdrop_data->texture_slot;
1846  }
1847 
1848  backdrop_filter_contents =
1849  WrapInput(renderer_, backdrop_filter,
1850  FilterInput::Make(std::move(input_texture)));
1851  backdrop_filter_contents->SetEffectTransform(
1852  transform_stack_.back().transform.Basis());
1853  backdrop_filter_contents->SetRenderingMode(
1854  transform_stack_.back().transform.HasTranslation()
1857 
1858  if (will_cache_backdrop_texture) {
1859  FML_DCHECK(backdrop_data);
1860  // If all filters on the shared backdrop layer are equal, process the
1861  // layer once.
1862  if (backdrop_data->all_filters_equal &&
1863  !backdrop_data->shared_filter_snapshot.has_value()) {
1864  // TODO(157110): compute minimum input hint.
1865  backdrop_data->shared_filter_snapshot =
1866  backdrop_filter_contents->RenderToSnapshot(renderer_, {}, {});
1867  }
1868 
1869  std::optional<Snapshot> maybe_snapshot =
1870  backdrop_data->shared_filter_snapshot;
1871  if (maybe_snapshot.has_value()) {
1872  const Snapshot& snapshot = maybe_snapshot.value();
1873  std::shared_ptr<TextureContents> contents = TextureContents::MakeRect(
1874  subpass_coverage.Shift(-GetGlobalPassPosition()));
1875  auto scaled =
1876  subpass_coverage.TransformBounds(snapshot.transform.Invert());
1877  contents->SetTexture(snapshot.texture);
1878  contents->SetSourceRect(scaled);
1879  contents->SetSamplerDescriptor(snapshot.sampler_descriptor);
1880 
1881  // This backdrop entity sets a depth value as it is written to the newly
1882  // flipped backdrop and not into a new saveLayer.
1883  Entity backdrop_entity;
1884  backdrop_entity.SetContents(std::move(contents));
1885  backdrop_entity.SetClipDepth(++current_depth_);
1886  backdrop_entity.SetBlendMode(paint.blend_mode);
1887 
1888  backdrop_entity.Render(renderer_, GetCurrentRenderPass());
1889  Save(0);
1890  return;
1891  }
1892  }
1893  }
1894 
1895  // When applying a save layer, absorb any pending distributed opacity.
1896  Paint paint_copy = paint;
1897  paint_copy.color.alpha *= transform_stack_.back().distributed_opacity;
1898  transform_stack_.back().distributed_opacity = 1.0;
1899 
1900  render_passes_.push_back(
1901  LazyRenderingConfig(renderer_, //
1902  CreateRenderTarget(renderer_, //
1903  subpass_size, //
1905  )));
1906  save_layer_state_.push_back(SaveLayerState{
1907  paint_copy, subpass_coverage.Shift(-coverage_origin_adjustment)});
1908 
1909  CanvasStackEntry entry;
1910  entry.transform = transform_stack_.back().transform;
1911  entry.clip_depth = current_depth_ + total_content_depth;
1912  FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth)
1913  << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth
1914  << " after allocating " << total_content_depth;
1915  entry.clip_height = transform_stack_.back().clip_height;
1917  entry.did_round_out = did_round_out;
1918  transform_stack_.emplace_back(entry);
1919 
1920  // Start non-collapsed subpasses with a fresh clip coverage stack limited by
1921  // the subpass coverage. This is important because image filters applied to
1922  // save layers may transform the subpass texture after it's rendered,
1923  // causing parent clip coverage to get misaligned with the actual area that
1924  // the subpass will affect in the parent pass.
1925  clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight());
1926 
1927  if (!backdrop_filter_contents) {
1928  return;
1929  }
1930 
1931  // Render the backdrop entity.
1932  Entity backdrop_entity;
1933  backdrop_entity.SetContents(std::move(backdrop_filter_contents));
1934  backdrop_entity.SetTransform(
1935  Matrix::MakeTranslation(Vector3(-local_position)));
1936  backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
1937  backdrop_entity.Render(renderer_, GetCurrentRenderPass());
1938 }
1939 
1941  FML_DCHECK(transform_stack_.size() > 0);
1942  if (transform_stack_.size() == 1) {
1943  return false;
1944  }
1945 
1946  // This check is important to make sure we didn't exceed the depth
1947  // that the clips were rendered at while rendering any of the
1948  // rendering ops. It is OK for the current depth to equal the
1949  // outgoing clip depth because that means the clipping would have
1950  // been successful up through the last rendering op, but it cannot
1951  // be greater.
1952  // Also, we bump the current rendering depth to the outgoing clip
1953  // depth so that future rendering operations are not clipped by
1954  // any of the pixels set by the expiring clips. It is OK for the
1955  // estimates used to determine the clip depth in save/saveLayer
1956  // to be overly conservative, but we need to jump the depth to
1957  // the clip depth so that the next rendering op will get a
1958  // larger depth (it will pre-increment the current_depth_ value).
1959  FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth)
1960  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
1961  current_depth_ = transform_stack_.back().clip_depth;
1962 
1963  if (IsSkipping()) {
1964  transform_stack_.pop_back();
1965  return true;
1966  }
1967 
1968  if (transform_stack_.back().rendering_mode ==
1970  transform_stack_.back().rendering_mode ==
1972  auto lazy_render_pass = std::move(render_passes_.back());
1973  render_passes_.pop_back();
1974  // Force the render pass to be constructed if it never was.
1975  lazy_render_pass.GetInlinePassContext()->GetRenderPass();
1976 
1977  SaveLayerState save_layer_state = save_layer_state_.back();
1978  save_layer_state_.pop_back();
1979  auto global_pass_position = GetGlobalPassPosition();
1980 
1981  std::shared_ptr<Contents> contents = CreateContentsForSubpassTarget(
1982  renderer_, save_layer_state.paint, //
1983  lazy_render_pass.GetInlinePassContext()->GetTexture(), //
1984  Matrix::MakeTranslation(Vector3{-global_pass_position}) * //
1985  transform_stack_.back().transform //
1986  );
1987 
1988  lazy_render_pass.GetInlinePassContext()->EndPass();
1989 
1990  // Round the subpass texture position for pixel alignment with the parent
1991  // pass render target. By default, we draw subpass textures with nearest
1992  // sampling, so aligning here is important for avoiding visual nearest
1993  // sampling errors caused by limited floating point precision when
1994  // straddling a half pixel boundary.
1995  Point subpass_texture_position;
1996  if (transform_stack_.back().did_round_out) {
1997  // Subpass coverage was rounded out, origin potentially moved "down" by
1998  // as much as a pixel.
1999  subpass_texture_position =
2000  (save_layer_state.coverage.GetOrigin() - global_pass_position)
2001  .Floor();
2002  } else {
2003  // Subpass coverage was truncated. Pick the closest phyiscal pixel.
2004  subpass_texture_position =
2005  (save_layer_state.coverage.GetOrigin() - global_pass_position)
2006  .Round();
2007  }
2008 
2009  Entity element_entity;
2010  element_entity.SetClipDepth(++current_depth_);
2011  element_entity.SetContents(std::move(contents));
2012  element_entity.SetBlendMode(save_layer_state.paint.blend_mode);
2013  element_entity.SetTransform(
2014  Matrix::MakeTranslation(Vector3(subpass_texture_position)));
2015 
2016  if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
2017  if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) {
2018  ApplyFramebufferBlend(element_entity);
2019  } else {
2020  // End the active pass and flush the buffer before rendering "advanced"
2021  // blends. Advanced blends work by binding the current render target
2022  // texture as an input ("destination"), blending with a second texture
2023  // input ("source"), writing the result to an intermediate texture, and
2024  // finally copying the data from the intermediate texture back to the
2025  // render target texture. And so all of the commands that have written
2026  // to the render target texture so far need to execute before it's bound
2027  // for blending (otherwise the blend pass will end up executing before
2028  // all the previous commands in the active pass).
2029  auto input_texture = FlipBackdrop(GetGlobalPassPosition());
2030  if (!input_texture) {
2031  return false;
2032  }
2033 
2034  FilterInput::Vector inputs = {
2035  FilterInput::Make(input_texture,
2036  element_entity.GetTransform().Invert()),
2037  FilterInput::Make(element_entity.GetContents())};
2038  auto contents = ColorFilterContents::MakeBlend(
2039  element_entity.GetBlendMode(), inputs);
2040  contents->SetCoverageHint(element_entity.GetCoverage());
2041  element_entity.SetContents(std::move(contents));
2042  element_entity.SetBlendMode(BlendMode::kSrc);
2043  }
2044  }
2045 
2046  element_entity.Render(
2047  renderer_, //
2048  *render_passes_.back().GetInlinePassContext()->GetRenderPass() //
2049  );
2050  clip_coverage_stack_.PopSubpass();
2051  transform_stack_.pop_back();
2052 
2053  // We don't need to restore clips if a saveLayer was performed, as the clip
2054  // state is per render target, and no more rendering operations will be
2055  // performed as the render target workloaded is completed in the restore.
2056  return true;
2057  }
2058 
2059  size_t num_clips = transform_stack_.back().num_clips;
2060  transform_stack_.pop_back();
2061 
2062  if (num_clips > 0) {
2063  EntityPassClipStack::ClipStateResult clip_state_result =
2064  clip_coverage_stack_.RecordRestore(GetGlobalPassPosition(),
2065  GetClipHeight());
2066 
2067  // Clip restores are never required with depth based clipping.
2068  FML_DCHECK(!clip_state_result.should_render);
2069  if (clip_state_result.clip_did_change) {
2070  // We only need to update the pass scissor if the clip state has changed.
2071  SetClipScissor(
2072  clip_coverage_stack_.CurrentClipCoverage(), //
2073  *render_passes_.back().GetInlinePassContext()->GetRenderPass(), //
2074  GetGlobalPassPosition() //
2075  );
2076  }
2077  }
2078 
2079  return true;
2080 }
2081 
2082 bool Canvas::AttemptBlurredTextOptimization(
2083  const std::shared_ptr<TextFrame>& text_frame,
2084  const std::shared_ptr<TextContents>& text_contents,
2085  Entity& entity,
2086  const Paint& paint) {
2087  if (!paint.mask_blur_descriptor.has_value() || //
2088  paint.image_filter != nullptr || //
2089  paint.color_filter != nullptr || //
2090  paint.invert_colors) {
2091  return false;
2092  }
2093 
2094  // TODO(bdero): This mask blur application is a hack. It will always wind up
2095  // doing a gaussian blur that affects the color source itself
2096  // instead of just the mask. The color filter text support
2097  // needs to be reworked in order to interact correctly with
2098  // mask filters.
2099  // https://github.com/flutter/flutter/issues/133297
2100  std::shared_ptr<FilterContents> filter =
2101  paint.mask_blur_descriptor->CreateMaskBlur(
2102  FilterInput::Make(text_contents),
2103  /*is_solid_color=*/true, GetCurrentTransform());
2104 
2105  std::optional<Glyph> maybe_glyph = text_frame->AsSingleGlyph();
2106  int64_t identifier = maybe_glyph.has_value()
2107  ? maybe_glyph.value().index
2108  : reinterpret_cast<int64_t>(text_frame.get());
2109  TextShadowCache::TextShadowCacheKey cache_key(
2110  /*p_max_basis=*/entity.GetTransform().GetMaxBasisLengthXY(),
2111  /*p_identifier=*/identifier,
2112  /*p_is_single_glyph=*/maybe_glyph.has_value(),
2113  /*p_font=*/text_frame->GetFont(),
2114  /*p_sigma=*/paint.mask_blur_descriptor->sigma,
2115  /*p_color=*/paint.color);
2116 
2117  std::optional<Entity> result = renderer_.GetTextShadowCache().Lookup(
2118  renderer_, entity, filter, cache_key);
2119  if (result.has_value()) {
2120  AddRenderEntityToCurrentPass(result.value(), /*reuse_depth=*/false);
2121  return true;
2122  } else {
2123  return false;
2124  }
2125 }
2126 
2127 // If the text point size * max basis XY is larger than this value,
2128 // render the text as paths (if available) for faster and higher
2129 // fidelity rendering. This is a somewhat arbitrary cutoff
2130 static constexpr Scalar kMaxTextScale = 250;
2131 
2132 void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
2133  Point position,
2134  const Paint& paint) {
2136  if (max_scale * text_frame->GetFont().GetMetrics().point_size >
2137  kMaxTextScale) {
2138  fml::StatusOr<flutter::DlPath> path = text_frame->GetPath();
2139  if (path.ok()) {
2140  Save(1);
2141  Concat(Matrix::MakeTranslation(position));
2142  DrawPath(path.value(), paint);
2143  Restore();
2144  return;
2145  }
2146  }
2147 
2148  Entity entity;
2149  entity.SetClipDepth(GetClipHeight());
2150  entity.SetBlendMode(paint.blend_mode);
2151 
2152  auto text_contents = std::make_shared<TextContents>();
2153  text_contents->SetTextFrame(text_frame);
2154  text_contents->SetPosition(position);
2155  text_contents->SetScreenTransform(GetCurrentTransform());
2156  text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value());
2157  text_contents->SetColor(paint.color);
2158  text_contents->SetTextProperties(paint.color, paint.GetStroke());
2159 
2160  entity.SetTransform(GetCurrentTransform().Translate(position));
2161 
2162  if (AttemptBlurredTextOptimization(text_frame, text_contents, entity,
2163  paint)) {
2164  return;
2165  }
2166 
2167  entity.SetContents(paint.WithFilters(renderer_, std::move(text_contents)));
2168  AddRenderEntityToCurrentPass(entity, false);
2169 }
2170 
2171 void Canvas::AddRenderSDFEntityToCurrentPass(
2172  const Paint& paint,
2173  UberSDFParameters params,
2174  bool reuse_depth,
2175  const std::optional<Matrix>& shape_transform) {
2177  if (shape_transform.has_value()) {
2178  transform = transform * shape_transform.value();
2179  }
2180 
2181  Entity entity;
2182  entity.SetTransform(transform);
2183  entity.SetBlendMode(paint.blend_mode);
2184 
2185  if (paint.color_source) {
2186  // Since we are going to use BlendMode::kSrcIn to implement the color_source
2187  // the SDF portion of the blend should just be solid white to get the
2188  // correct color from the color_source.
2189  params.color = Color::White();
2190  }
2191  auto geometry = std::make_unique<UberSDFGeometry>(params);
2192  auto contents = UberSDFContents::Make(params, std::move(geometry));
2193  const Geometry* geom = contents->GetGeometry();
2194 
2195  if (paint.color_source) {
2196  // UberSDF doesn't perform things like gradients so we blend the SDF
2197  // with the color source.
2198  std::shared_ptr<ColorSourceContents> color_source_contents =
2199  paint.CreateContents(renderer_, geom, shape_transform);
2200  std::shared_ptr<Contents> final_contents = ColorFilterContents::MakeBlend(
2201  BlendMode::kSrcIn, {FilterInput::Make(std::move(contents)),
2202  FilterInput::Make(color_source_contents)});
2203 
2204  Paint new_paint = paint;
2205  new_paint.color_source = nullptr;
2206  AddRenderEntityWithFiltersToCurrentPass(entity, geom, new_paint,
2207  reuse_depth,
2208  /*override_contents=*/
2209  std::move(final_contents));
2210  } else {
2211  AddRenderEntityWithFiltersToCurrentPass(entity, geom, paint, reuse_depth,
2212  /*override_contents=*/
2213  std::move(contents));
2214  }
2215 }
2216 
2217 void Canvas::AddRenderEntityWithFiltersToCurrentPass(
2218  Entity& entity,
2219  const Geometry* geometry,
2220  const Paint& paint,
2221  bool reuse_depth,
2222  std::shared_ptr<Contents> override_contents) {
2223  std::shared_ptr<ColorSourceContents> color_source_contents;
2224  std::shared_ptr<Contents> contents;
2225  if (override_contents) {
2226  contents = std::move(override_contents);
2227  } else {
2228  color_source_contents = paint.CreateContents(renderer_, geometry);
2229  contents = color_source_contents;
2230  }
2231 
2232  if (!paint.color_filter && !paint.invert_colors && !paint.image_filter &&
2233  !paint.mask_blur_descriptor.has_value()) {
2234  entity.SetContents(std::move(contents));
2235  AddRenderEntityToCurrentPass(entity, reuse_depth);
2236  return;
2237  }
2238 
2239  // Attempt to apply the color filter on the CPU first.
2240  // Note: This is not just an optimization; some color sources rely on
2241  // CPU-applied color filters to behave properly.
2242  bool needs_color_filter = paint.color_filter || paint.invert_colors;
2243  if (needs_color_filter &&
2244  contents->ApplyColorFilter([&](Color color) -> Color {
2245  if (paint.color_filter) {
2246  color = GetCPUColorFilterProc(paint.color_filter)(color);
2247  }
2248  if (paint.invert_colors) {
2249  color = color.ApplyColorMatrix(kColorInversion);
2250  }
2251  return color;
2252  })) {
2253  needs_color_filter = false;
2254  }
2255 
2256  bool can_apply_mask_filter = geometry->CanApplyMaskFilter();
2257 
2258  if (can_apply_mask_filter && paint.mask_blur_descriptor.has_value()) {
2259  // If there's a mask blur and we need to apply the color filter on the GPU,
2260  // we need to be careful to only apply the color filter to the source
2261  // colors. CreateMaskBlur is able to handle this case.
2262  FML_DCHECK(color_source_contents) << "Mask blur is only supported when no "
2263  "override contents are provided.";
2264  FillRectGeometry out_rect(Rect{});
2265  auto filter = paint.mask_blur_descriptor->CreateMaskBlur(
2266  paint, renderer_, geometry, color_source_contents, needs_color_filter,
2267  &out_rect);
2268  entity.SetContents(std::move(filter));
2269  AddRenderEntityToCurrentPass(entity, reuse_depth);
2270  return;
2271  }
2272 
2273  std::shared_ptr<Contents> contents_copy = std::move(contents);
2274 
2275  // Image input types will directly set their color filter,
2276  // if any. See `TiledTextureContents.SetColorFilter`.
2277  if (needs_color_filter &&
2278  (!paint.color_source ||
2279  paint.color_source->type() != flutter::DlColorSourceType::kImage)) {
2280  if (paint.color_filter) {
2281  contents_copy = WrapWithGPUColorFilter(
2282  paint.color_filter, FilterInput::Make(std::move(contents_copy)),
2284  }
2285  if (paint.invert_colors) {
2286  contents_copy =
2287  WrapWithInvertColors(FilterInput::Make(std::move(contents_copy)),
2289  }
2290  }
2291 
2292  if (paint.image_filter) {
2293  std::shared_ptr<FilterContents> filter =
2294  WrapInput(renderer_, paint.image_filter,
2295  FilterInput::Make(std::move(contents_copy)));
2296  filter->SetRenderingMode(Entity::RenderingMode::kDirect);
2297  entity.SetContents(filter);
2298  AddRenderEntityToCurrentPass(entity, reuse_depth);
2299  return;
2300  }
2301 
2302  entity.SetContents(std::move(contents_copy));
2303  AddRenderEntityToCurrentPass(entity, reuse_depth);
2304 }
2305 
2306 void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) {
2307  if (IsSkipping()) {
2308  return;
2309  }
2310 
2311  entity.SetTransform(
2312  Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) *
2313  entity.GetTransform());
2314  entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity);
2315  if (entity.GetBlendMode() == BlendMode::kSrcOver &&
2316  entity.GetContents()->IsOpaque(entity.GetTransform())) {
2317  entity.SetBlendMode(BlendMode::kSrc);
2318  }
2319 
2320  // If the entity covers the current render target and is a solid color, then
2321  // conditionally update the backdrop color to its solid color value blended
2322  // with the current backdrop.
2323  if (render_passes_.back().IsApplyingClearColor()) {
2324  std::optional<Color> maybe_color = entity.AsBackgroundColor(
2325  render_passes_.back().GetInlinePassContext()->GetTexture()->GetSize());
2326  if (maybe_color.has_value()) {
2327  Color color = maybe_color.value();
2328  RenderTarget& render_target = render_passes_.back()
2329  .GetInlinePassContext()
2330  ->GetPassTarget()
2331  .GetRenderTarget();
2332  ColorAttachment attachment = render_target.GetColorAttachment(0);
2333  // Attachment.clear color needs to be premultiplied at all times, but the
2334  // Color::Blend function requires unpremultiplied colors.
2335  attachment.clear_color = attachment.clear_color.Unpremultiply()
2336  .Blend(color, entity.GetBlendMode())
2337  .Premultiply();
2338  render_target.SetColorAttachment(attachment, 0u);
2339  return;
2340  }
2341  }
2342  if (!reuse_depth) {
2343  ++current_depth_;
2344  }
2345 
2346  // We can render at a depth up to and including the depth of the currently
2347  // active clips and we will still be clipped out, but we cannot render at
2348  // a depth that is greater than the current clips or we will not be clipped.
2349  FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth)
2350  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
2351  entity.SetClipDepth(current_depth_);
2352 
2353  if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
2354  if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) {
2355  ApplyFramebufferBlend(entity);
2356  } else {
2357  // End the active pass and flush the buffer before rendering "advanced"
2358  // blends. Advanced blends work by binding the current render target
2359  // texture as an input ("destination"), blending with a second texture
2360  // input ("source"), writing the result to an intermediate texture, and
2361  // finally copying the data from the intermediate texture back to the
2362  // render target texture. And so all of the commands that have written
2363  // to the render target texture so far need to execute before it's bound
2364  // for blending (otherwise the blend pass will end up executing before
2365  // all the previous commands in the active pass).
2366  auto input_texture = FlipBackdrop(GetGlobalPassPosition(), //
2367  /*should_remove_texture=*/false,
2368  /*should_use_onscreen=*/false,
2369  /*post_depth_increment=*/true);
2370  if (!input_texture) {
2371  return;
2372  }
2373 
2374  // The coverage hint tells the rendered Contents which portion of the
2375  // rendered output will actually be used, and so we set this to the
2376  // current clip coverage (which is the max clip bounds). The contents may
2377  // optionally use this hint to avoid unnecessary rendering work.
2378  auto element_coverage_hint = entity.GetContents()->GetCoverageHint();
2379  entity.GetContents()->SetCoverageHint(Rect::Intersection(
2380  element_coverage_hint, clip_coverage_stack_.CurrentClipCoverage()));
2381 
2382  FilterInput::Vector inputs = {
2383  FilterInput::Make(input_texture, entity.GetTransform().Invert()),
2384  FilterInput::Make(entity.GetContents())};
2385  auto contents =
2386  ColorFilterContents::MakeBlend(entity.GetBlendMode(), inputs);
2387  entity.SetContents(std::move(contents));
2388  entity.SetBlendMode(BlendMode::kSrc);
2389  }
2390  }
2391 
2392  const std::shared_ptr<RenderPass>& result =
2393  render_passes_.back().GetInlinePassContext()->GetRenderPass();
2394  if (!result) {
2395  // Failure to produce a render pass should be explained by specific errors
2396  // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't
2397  // append a validation log here.
2398  return;
2399  }
2400 
2401  entity.Render(renderer_, *result);
2402 }
2403 
2404 RenderPass& Canvas::GetCurrentRenderPass() const {
2405  return *render_passes_.back().GetInlinePassContext()->GetRenderPass();
2406 }
2407 
2408 void Canvas::SetBackdropData(
2409  std::unordered_map<int64_t, BackdropData> backdrop_data,
2410  size_t backdrop_count) {
2411  backdrop_data_ = std::move(backdrop_data);
2412  backdrop_count_ = backdrop_count;
2413 }
2414 
2415 std::shared_ptr<Texture> Canvas::FlipBackdrop(Point global_pass_position,
2416  bool should_remove_texture,
2417  bool should_use_onscreen,
2418  bool post_depth_increment) {
2419  LazyRenderingConfig rendering_config = std::move(render_passes_.back());
2420  render_passes_.pop_back();
2421 
2422  // If the very first thing we render in this EntityPass is a subpass that
2423  // happens to have a backdrop filter or advanced blend, than that backdrop
2424  // filter/blend will sample from an uninitialized texture.
2425  //
2426  // By calling `pass_context.GetRenderPass` here, we force the texture to pass
2427  // through at least one RenderPass with the correct clear configuration before
2428  // any sampling occurs.
2429  //
2430  // In cases where there are no contents, we
2431  // could instead check the clear color and initialize a 1x2 CPU texture
2432  // instead of ending the pass.
2433  rendering_config.GetInlinePassContext()->GetRenderPass();
2434  if (!rendering_config.GetInlinePassContext()->EndPass()) {
2436  << "Failed to end the current render pass in order to read from "
2437  "the backdrop texture and apply an advanced blend or backdrop "
2438  "filter.";
2439  // Note: adding this render pass ensures there are no later crashes from
2440  // unbalanced save layers. Ideally, this method would return false and the
2441  // renderer could handle that by terminating dispatch.
2442  render_passes_.emplace_back(std::move(rendering_config));
2443  return nullptr;
2444  }
2445 
2446  const std::shared_ptr<Texture>& input_texture =
2447  rendering_config.GetInlinePassContext()->GetTexture();
2448 
2449  if (!input_texture) {
2450  VALIDATION_LOG << "Failed to fetch the color texture in order to "
2451  "apply an advanced blend or backdrop filter.";
2452 
2453  // Note: see above.
2454  render_passes_.emplace_back(std::move(rendering_config));
2455  return nullptr;
2456  }
2457 
2458  if (should_use_onscreen) {
2459  ColorAttachment color0 = render_target_.GetColorAttachment(0);
2460  // When MSAA is being used, we end up overriding the entire backdrop by
2461  // drawing the previous pass texture, and so we don't have to clear it and
2462  // can use kDontCare.
2463  color0.load_action = color0.resolve_texture != nullptr
2464  ? LoadAction::kDontCare
2465  : LoadAction::kLoad;
2466  render_target_.SetColorAttachment(color0, 0);
2467 
2468  auto entity_pass_target = std::make_unique<EntityPassTarget>(
2469  render_target_, //
2470  renderer_.GetDeviceCapabilities().SupportsReadFromResolve(), //
2471  renderer_.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() //
2472  );
2473  render_passes_.push_back(
2474  LazyRenderingConfig(renderer_, std::move(entity_pass_target)));
2475  requires_readback_ = false;
2476  } else {
2477  render_passes_.emplace_back(std::move(rendering_config));
2478  // If the current texture is being cached for a BDF we need to ensure we
2479  // don't recycle it during recording; remove it from the entity pass target.
2480  if (should_remove_texture) {
2481  render_passes_.back().GetEntityPassTarget()->RemoveSecondary();
2482  }
2483  }
2484  RenderPass& current_render_pass =
2485  *render_passes_.back().GetInlinePassContext()->GetRenderPass();
2486 
2487  // Eagerly restore the BDF contents.
2488 
2489  // If the pass context returns a backdrop texture, we need to draw it to the
2490  // current pass. We do this because it's faster and takes significantly less
2491  // memory than storing/loading large MSAA textures. Also, it's not possible
2492  // to blit the non-MSAA resolve texture of the previous pass to MSAA
2493  // textures (let alone a transient one).
2494  Rect size_rect = Rect::MakeSize(input_texture->GetSize());
2495  auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect);
2496  msaa_backdrop_contents->SetStencilEnabled(false);
2497  msaa_backdrop_contents->SetLabel("MSAA backdrop");
2498  msaa_backdrop_contents->SetSourceRect(size_rect);
2499  msaa_backdrop_contents->SetTexture(input_texture);
2500 
2501  Entity msaa_backdrop_entity;
2502  msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
2503  msaa_backdrop_entity.SetBlendMode(BlendMode::kSrc);
2504  msaa_backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
2505  if (!msaa_backdrop_entity.Render(renderer_, current_render_pass)) {
2506  VALIDATION_LOG << "Failed to render MSAA backdrop entity.";
2507  return nullptr;
2508  }
2509 
2510  // Restore any clips that were recorded before the backdrop filter was
2511  // applied.
2512  auto& replay_entities = clip_coverage_stack_.GetReplayEntities();
2513  uint64_t current_depth =
2514  post_depth_increment ? current_depth_ - 1 : current_depth_;
2515  for (const auto& replay : replay_entities) {
2516  if (replay.clip_depth <= current_depth) {
2517  continue;
2518  }
2519 
2520  SetClipScissor(replay.clip_coverage, current_render_pass,
2521  global_pass_position);
2522  if (!replay.clip_contents.Render(renderer_, current_render_pass,
2523  replay.clip_depth)) {
2524  VALIDATION_LOG << "Failed to render entity for clip restore.";
2525  }
2526  }
2527 
2528  return input_texture;
2529 }
2530 
2531 bool Canvas::SupportsBlitToOnscreen() const {
2532  return renderer_.GetContext()
2533  ->GetCapabilities()
2534  ->SupportsTextureToTextureBlits() &&
2535  renderer_.GetContext()->GetBackendType() ==
2536  Context::BackendType::kMetal;
2537 }
2538 
2539 bool Canvas::BlitToOnscreen(bool is_onscreen) {
2540  auto command_buffer = renderer_.GetContext()->CreateCommandBuffer();
2541  command_buffer->SetLabel("EntityPass Root Command Buffer");
2542  auto offscreen_target = render_passes_.back()
2543  .GetInlinePassContext()
2544  ->GetPassTarget()
2545  .GetRenderTarget();
2546  if (SupportsBlitToOnscreen()) {
2547  auto blit_pass = command_buffer->CreateBlitPass();
2548  blit_pass->AddCopy(offscreen_target.GetRenderTargetTexture(),
2549  render_target_.GetRenderTargetTexture());
2550  if (!blit_pass->EncodeCommands()) {
2551  VALIDATION_LOG << "Failed to encode root pass blit command.";
2552  return false;
2553  }
2554  } else {
2555  auto render_pass = command_buffer->CreateRenderPass(render_target_);
2556  render_pass->SetLabel("EntityPass Root Render Pass");
2557 
2558  {
2559  auto size_rect = Rect::MakeSize(offscreen_target.GetRenderTargetSize());
2560  auto contents = TextureContents::MakeRect(size_rect);
2561  contents->SetTexture(offscreen_target.GetRenderTargetTexture());
2562  contents->SetSourceRect(size_rect);
2563  contents->SetLabel("Root pass blit");
2564 
2565  Entity entity;
2566  entity.SetContents(contents);
2567  entity.SetBlendMode(BlendMode::kSrc);
2568 
2569  if (!entity.Render(renderer_, *render_pass)) {
2570  VALIDATION_LOG << "Failed to render EntityPass root blit.";
2571  return false;
2572  }
2573  }
2574 
2575  if (!render_pass->EncodeCommands()) {
2576  VALIDATION_LOG << "Failed to encode root pass command buffer.";
2577  return false;
2578  }
2579  }
2580 
2581  if (is_onscreen) {
2582  return renderer_.GetContext()->SubmitOnscreen(std::move(command_buffer));
2583  } else {
2584  return renderer_.GetContext()->EnqueueCommandBuffer(
2585  std::move(command_buffer));
2586  }
2587 }
2588 
2589 bool Canvas::EnsureFinalMipmapGeneration() const {
2590  if (!render_target_.GetRenderTargetTexture()->NeedsMipmapGeneration()) {
2591  return true;
2592  }
2593  std::shared_ptr<CommandBuffer> cmd_buffer =
2594  renderer_.GetContext()->CreateCommandBuffer();
2595  if (!cmd_buffer) {
2596  return false;
2597  }
2598  std::shared_ptr<BlitPass> blit_pass = cmd_buffer->CreateBlitPass();
2599  if (!blit_pass) {
2600  return false;
2601  }
2602  blit_pass->GenerateMipmap(render_target_.GetRenderTargetTexture());
2603  blit_pass->EncodeCommands();
2604  return renderer_.GetContext()->EnqueueCommandBuffer(std::move(cmd_buffer));
2605 }
2606 
2607 void Canvas::EndReplay() {
2608  FML_DCHECK(render_passes_.size() == 1u);
2609  render_passes_.back().GetInlinePassContext()->GetRenderPass();
2610  render_passes_.back().GetInlinePassContext()->EndPass(
2611  /*is_onscreen=*/!requires_readback_ && is_onscreen_);
2612  backdrop_data_.clear();
2613 
2614  // If requires_readback_ was true, then we rendered to an offscreen texture
2615  // instead of to the onscreen provided in the render target. Now we need to
2616  // draw or blit the offscreen back to the onscreen.
2617  if (requires_readback_) {
2618  BlitToOnscreen(/*is_onscreen_=*/is_onscreen_);
2619  }
2620  if (!EnsureFinalMipmapGeneration()) {
2621  VALIDATION_LOG << "Failed to generate onscreen mipmaps.";
2622  }
2623  if (!renderer_.GetContext()->FlushCommandBuffers()) {
2624  // Not much we can do.
2625  VALIDATION_LOG << "Failed to submit command buffers";
2626  }
2627  render_passes_.clear();
2628  renderer_.GetRenderTargetCache()->End();
2629  clip_geometry_.clear();
2630 
2631  Reset();
2632  Initialize(initial_cull_rect_);
2633 }
2634 
2635 bool Canvas::IsCompatibleWithSDFRendering(const Paint& paint) {
2636  if (!paint.anti_alias) {
2637  return false;
2638  }
2639  if (paint.mask_blur_descriptor.has_value()) {
2640  return false;
2641  }
2642  switch (paint.blend_mode) {
2643  // Incompatible blend modes:
2644  case BlendMode::kClear:
2645  case BlendMode::kSrc:
2646  case BlendMode::kSrcIn:
2647  case BlendMode::kDstIn:
2648  case BlendMode::kSrcOut:
2649  case BlendMode::kDstATop:
2650  case BlendMode::kPlus:
2651  case BlendMode::kModulate:
2652  return false;
2653  // Compatible blend modes:
2654  case BlendMode::kDst:
2655  case BlendMode::kSrcOver:
2656  case BlendMode::kDstOver:
2657  case BlendMode::kDstOut:
2658  case BlendMode::kSrcATop:
2659  case BlendMode::kXor:
2660  case BlendMode::kScreen:
2661  case BlendMode::kOverlay:
2662  case BlendMode::kDarken:
2663  case BlendMode::kLighten:
2664  case BlendMode::kColorDodge:
2665  case BlendMode::kColorBurn:
2666  case BlendMode::kHardLight:
2667  case BlendMode::kSoftLight:
2668  case BlendMode::kDifference:
2669  case BlendMode::kExclusion:
2670  case BlendMode::kMultiply:
2671  case BlendMode::kHue:
2672  case BlendMode::kSaturation:
2673  case BlendMode::kColor:
2674  case BlendMode::kLuminosity:
2675  return true;
2676  }
2677 }
2678 
2679 LazyRenderingConfig::LazyRenderingConfig(
2680  ContentContext& renderer,
2681  std::unique_ptr<EntityPassTarget> p_entity_pass_target)
2682  : entity_pass_target_(std::move(p_entity_pass_target)) {
2683  inline_pass_context_ =
2684  std::make_unique<InlinePassContext>(renderer, *entity_pass_target_);
2685 }
2686 
2688  return !inline_pass_context_->IsActive();
2689 }
2690 
2692  return entity_pass_target_.get();
2693 }
2694 
2696  return inline_pass_context_.get();
2697 }
2698 
2699 } // namespace impeller
A Geometry that produces fillable vertices representing the stroked outline of an |Arc| object using ...
std::shared_ptr< SolidBlurContents > BuildBlurContent(Sigma sigma) override
Definition: canvas.cc:307
Rect GetBounds() const override
Definition: canvas.cc:303
const Geometry & BuildDrawGeometry() override
Definition: canvas.cc:317
PathBlurShape(const PathSource &source[[clang::lifetimebound]], std::shared_ptr< ShadowVertices > shadow_vertices, Sigma sigma)
Definition: canvas.cc:296
Rect GetBounds() const override
Definition: canvas.cc:235
RRectBlurShape(const Rect &rect, Scalar corner_radius)
Definition: canvas.cc:232
std::shared_ptr< SolidBlurContents > BuildBlurContent(Sigma sigma) override
Definition: canvas.cc:237
const Geometry & BuildDrawGeometry() override
Definition: canvas.cc:244
const Geometry & BuildDrawGeometry() override
Definition: canvas.cc:269
Rect GetBounds() const override
Definition: canvas.cc:260
std::shared_ptr< SolidBlurContents > BuildBlurContent(Sigma sigma) override
Definition: canvas.cc:262
RSuperellipseBlurShape(const Rect &rect, Scalar corner_radius)
Definition: canvas.cc:257
void ClipGeometry(const Geometry &geometry, Entity::ClipOperation clip_op, bool is_aa=true)
Definition: canvas.cc:1292
static constexpr uint32_t kMaxDepth
Definition: canvas.h:121
Canvas(ContentContext &renderer, const RenderTarget &render_target, bool is_onscreen, bool requires_readback)
Definition: canvas.cc:330
void DrawRoundSuperellipse(const RoundSuperellipse &rse, const Paint &paint)
Definition: canvas.cc:1186
std::optional< Rect > GetLocalCoverageLimit() const
Return the culling bounds of the current render target, or nullopt if there is no coverage.
Definition: canvas.cc:1673
void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const flutter::DlImageFilter *backdrop_filter=nullptr, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown, uint32_t total_content_depth=kMaxDepth, bool can_distribute_opacity=false, std::optional< int64_t > backdrop_id=std::nullopt)
Definition: canvas.cc:1710
const Matrix & GetCurrentTransform() const
Definition: canvas.cc:404
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition: canvas.cc:1473
void DrawOval(const Rect &rect, const Paint &paint)
Definition: canvas.cc:994
void DrawImageRect(const std::shared_ptr< Texture > &image, Rect source, Rect dest, const Paint &paint, const SamplerDescriptor &sampler={}, SourceRectConstraint src_rect_constraint=SourceRectConstraint::kFast)
Definition: canvas.cc:1406
void RestoreToCount(size_t count)
Definition: canvas.cc:451
static bool IsCompatibleWithSDFRendering(const Paint &paint)
Definition: canvas.cc:2635
bool Restore()
Definition: canvas.cc:1940
size_t GetSaveCount() const
Definition: canvas.cc:443
void Concat(const Matrix &transform)
Definition: canvas.cc:388
void Transform(const Matrix &transform)
Definition: canvas.cc:400
void DrawDashedLine(const Point &p0, const Point &p1, Scalar on_length, Scalar off_length, const Paint &paint)
Definition: canvas.cc:915
void DrawDiffRoundRect(const RoundRect &outer, const RoundRect &inner, const Paint &paint)
Definition: canvas.cc:1170
void DrawPath(const flutter::DlPath &path, const Paint &paint)
Definition: canvas.cc:459
void PreConcat(const Matrix &transform)
Definition: canvas.cc:392
void Rotate(Radians radians)
Definition: canvas.cc:424
void DrawPoints(const Point points[], uint32_t count, Scalar radius, const Paint &paint, PointStyle point_style)
Definition: canvas.cc:1374
void ResetTransform()
Definition: canvas.cc:396
void DrawTextFrame(const std::shared_ptr< TextFrame > &text_frame, Point position, const Paint &paint)
Definition: canvas.cc:2132
void DrawImage(const std::shared_ptr< Texture > &image, Point offset, const Paint &paint, const SamplerDescriptor &sampler={})
Definition: canvas.cc:1392
void DrawPaint(const Paint &paint)
Definition: canvas.cc:479
void DrawRoundRect(const RoundRect &rect, const Paint &paint)
Definition: canvas.cc:1101
void Skew(Scalar sx, Scalar sy)
Definition: canvas.cc:420
void Scale(const Vector2 &scale)
Definition: canvas.cc:412
void Save(uint32_t total_content_depth=kMaxDepth)
Definition: canvas.cc:1656
void DrawRect(const Rect &rect, const Paint &paint)
Definition: canvas.cc:941
void DrawAtlas(const std::shared_ptr< AtlasContents > &atlas_contents, const Paint &paint)
Definition: canvas.cc:1588
void DrawLine(const Point &p0, const Point &p1, const Paint &paint, bool reuse_depth=false)
Definition: canvas.cc:897
void Translate(const Vector3 &offset)
Definition: canvas.cc:408
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition: canvas.cc:1255
void DrawArc(const Arc &arc, const Paint &paint)
Definition: canvas.cc:1049
virtual bool SupportsImplicitResolvingMSAA() const =0
Whether the context backend supports multisampled rendering to the on-screen surface without requirin...
virtual bool SupportsFramebufferFetch() const =0
Whether the context backend is able to support pipelines with shaders that read from the framebuffer ...
virtual bool SupportsReadFromResolve() const =0
Whether the context backend supports binding the current RenderPass attachments. This is supported if...
static std::unique_ptr< CircleContents > Make(std::unique_ptr< CircleGeometry > geometry, Color color, bool stroked)
void SetGeometry(GeometryResult geometry)
Set the pre-tessellated clip geometry.
void SetClipOperation(Entity::ClipOperation clip_op)
bool Render(const ContentContext &renderer, RenderPass &pass, uint32_t clip_depth) const
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
static std::unique_ptr< ComplexRoundedSuperellipseContents > Make(Color color, const Rect &bounds, const RoundSuperellipseParam &round_superellipse_params, std::optional< StrokeParameters > stroke)
const Capabilities & GetDeviceCapabilities() const
const std::shared_ptr< RenderTargetAllocator > & GetRenderTargetCache() const
TextShadowCache & GetTextShadowCache() const
Tessellator & GetTessellator() const
std::shared_ptr< Context > GetContext() const
A geometry that implements "drawPaint" like behavior by covering the entire render pass area.
A Geometry class that can directly generate vertices (with or without texture coordinates) for filled...
A PathSource object that provides path iteration for any ellipse inscribed within a Rect bounds.
Definition: path_source.h:90
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
std::optional< Rect > GetCoverage() const
Definition: entity.cc:66
const std::shared_ptr< Contents > & GetContents() const
Definition: entity.cc:78
void SetClipDepth(uint32_t clip_depth)
Definition: entity.cc:82
BlendMode GetBlendMode() const
Definition: entity.cc:102
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:74
void SetBlendMode(BlendMode blend_mode)
Definition: entity.cc:98
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition: entity.cc:145
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
static constexpr BlendMode kLastPipelineBlendMode
Definition: entity.h:28
static bool IsBlendModeDestructive(BlendMode blend_mode)
Returns true if the blend mode is "destructive", meaning that even fully transparent source colors wo...
Definition: entity.cc:128
A class that tracks all clips that have been recorded in the current entity pass stencil.
std::optional< Rect > CurrentClipCoverage() const
void PushSubpass(std::optional< Rect > subpass_coverage, size_t clip_height)
ClipStateResult RecordClip(const ClipContents &clip_contents, Matrix transform, Point global_pass_position, uint32_t clip_depth, size_t clip_height_floor, bool is_aa)
ClipStateResult RecordRestore(Point global_pass_position, size_t restore_height)
A Geometry that produces fillable vertices for the gap between a pair of |RoundRect| objects using th...
A Geometry that produces fillable vertices from a |DlPath| object using the |FillPathSourceGeometry| ...
A Geometry class that produces fillable vertices from any |RoundRect| object regardless of radii unif...
@ kNormal
Blurred inside and outside.
@ kOuter
Nothing inside, blurred outside.
@ kInner
Blurred inside, nothing outside.
@ kSolid
Solid inside, blurred outside.
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
static std::unique_ptr< Geometry > MakeRect(const Rect &rect)
Definition: geometry.cc:83
virtual GeometryResult GetPositionBuffer(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const =0
virtual std::optional< Rect > GetCoverage(const Matrix &transform) const =0
The coverage rectangle of this geometry, transformed by the transform argument.
virtual bool IsAxisAlignedRect() const
Definition: geometry.cc:140
bool EndPass(bool is_onscreen=false)
std::shared_ptr< Texture > GetTexture()
const std::shared_ptr< RenderPass > & GetRenderPass()
bool IsApplyingClearColor() const
Whether or not the clear color texture can still be updated.
Definition: canvas.cc:2687
EntityPassTarget * GetEntityPassTarget() const
Definition: canvas.cc:2691
InlinePassContext * GetInlinePassContext() const
Definition: canvas.cc:2695
A geometry class specialized for Canvas::DrawPoints.
ColorAttachment GetColorAttachment(size_t index) const
Get the color attachment at [index].
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
ISize GetRenderTargetSize() const
const std::optional< DepthAttachment > & GetDepthAttachment() const
const std::optional< StencilAttachment > & GetStencilAttachment() const
void SetupDepthStencilAttachments(const Context &context, Allocator &allocator, ISize size, bool msaa, std::string_view label="Offscreen", RenderTarget::AttachmentConfig stencil_attachment_config=RenderTarget::kDefaultStencilAttachmentConfig, const std::shared_ptr< Texture > &depth_stencil_texture=nullptr)
A Geometry class that generates fillable vertices (with or without texture coordinates) directly from...
A Geometry class that generates fillable vertices (with or without texture coordinates) directly from...
static std::shared_ptr< ShadowVertices > MakeAmbientShadowVertices(Tessellator &tessellator, const PathSource &source, Scalar occluder_height, const Matrix &matrix)
static std::shared_ptr< ShadowVerticesContents > Make(const std::shared_ptr< ShadowVertices > &geometry)
A Geometry that produces fillable vertices representing the stroked outline of a |DlPath| object usin...
A Geometry that produces fillable vertices representing the stroked outline of a pair of nested |Roun...
A Geometry class that produces fillable vertices representing the stroked outline of an ellipse with ...
A Geometry that produces fillable vertices representing the stroked outline of a |DlPath| object usin...
A Geometry class that produces fillable vertices representing the stroked outline of any |Roundrect| ...
A Geometry class that produces fillable vertices representing the stroked outline of any |RoundSupere...
std::optional< Entity > Lookup(const ContentContext &renderer, const Entity &entity, const std::shared_ptr< FilterContents > &contents, const TextShadowCacheKey &)
Lookup the entity in the cache with the given filter/text contents, returning the new entity to rende...
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
static std::unique_ptr< UberSDFContents > Make(const UberSDFParameters &params, std::unique_ptr< Geometry > geometry)
ISize subpass_size
The output size of the down-sampling pass.
impeller::SamplerDescriptor ToSamplerDescriptor(const flutter::DlImageSampling options)
Color ToColor(const flutter::DlColor &color)
static constexpr Scalar kMaxTextScale
Definition: canvas.cc:2130
std::shared_ptr< ColorFilterContents > WrapWithGPUColorFilter(const flutter::DlColorFilter *filter, const std::shared_ptr< FilterInput > &input, ColorFilterContents::AbsorbOpacity absorb_opacity)
Definition: color_filter.cc:24
Point Vector2
Definition: point.h:430
TRect< int32_t > IRect32
Definition: rect.h:858
float Scalar
Definition: scalar.h:19
SourceRectConstraint
Controls the behavior of the source rectangle given to DrawImageRect.
Definition: canvas.h:75
@ kStrict
Sample only within the source rectangle. May be slower.
constexpr float kEhCloseEnough
Definition: constants.h:57
std::shared_ptr< ColorFilterContents > WrapWithInvertColors(const std::shared_ptr< FilterInput > &input, ColorFilterContents::AbsorbOpacity absorb_opacity)
Definition: color_filter.cc:16
TRect< Scalar > Rect
Definition: rect.h:857
PointStyle
Definition: canvas.h:66
@ kRound
Points are drawn as squares.
TPoint< Scalar > Point
Definition: point.h:426
ColorFilterProc GetCPUColorFilterProc(const flutter::DlColorFilter *filter)
Definition: color_filter.cc:66
std::shared_ptr< FilterContents > WrapInput(const ContentContext &renderer, const flutter::DlImageFilter *filter, const FilterInput::Ref &input)
Generate a new FilterContents using this filter's configuration.
Definition: image_filter.cc:20
flutter::DlPath DlPath
Definition: dl_dispatcher.h:34
BlendMode
Definition: color.h:58
ContentBoundsPromise
Definition: canvas.h:85
@ kMayClipContents
The caller claims the bounds are a subset of an estimate of the reasonably tight bounds but likely cl...
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
TSize< Scalar > Size
Definition: size.h:159
static constexpr const ColorMatrix kColorInversion
A color matrix which inverts colors.
Definition: color_filter.h:16
std::optional< Rect > ComputeSaveLayerCoverage(const Rect &content_coverage, const Matrix &effect_transform, const Rect &coverage_limit, const std::shared_ptr< FilterContents > &image_filter, bool flood_output_coverage, bool flood_input_coverage)
Compute the coverage of a subpass in the global coordinate space.
ISize64 ISize
Definition: size.h:162
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:36
Definition: comparable.h:93
std::shared_ptr< ContextGLES > context
std::shared_ptr< RenderPass > render_pass
std::shared_ptr< CommandBuffer > command_buffer
constexpr bool IncludeCenter() const
Definition: arc.h:110
constexpr bool IsFullCircle() const
Definition: arc.h:114
const Rect & GetOvalBounds() const
Return the bounds of the oval in which this arc is inscribed.
Definition: arc.h:94
constexpr Degrees GetSweep() const
Definition: arc.h:108
constexpr Degrees GetStart() const
Definition: arc.h:106
LoadAction load_action
Definition: formats.h:911
std::shared_ptr< Texture > texture
Definition: formats.h:909
std::shared_ptr< Texture > texture_slot
Definition: canvas.h:44
size_t backdrop_count
Definition: canvas.h:42
std::optional< Snapshot > shared_filter_snapshot
Definition: canvas.h:47
Definition: canvas.h:51
size_t clip_height
Definition: canvas.h:54
bool did_round_out
Definition: canvas.h:63
Entity::RenderingMode rendering_mode
Definition: canvas.h:58
Matrix transform
Definition: canvas.h:52
uint32_t clip_depth
Definition: canvas.h:53
static constexpr Color BlackTransparent()
Definition: color.h:275
static constexpr Color Khaki()
Definition: color.h:523
Scalar alpha
Definition: color.h:143
static constexpr Color White()
Definition: color.h:269
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:283
Scalar array[20]
Definition: color.h:118
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
Matrix Invert() const
Definition: matrix.cc:99
static constexpr Matrix MakeColumn(Scalar m0, Scalar m1, Scalar m2, Scalar m3, Scalar m4, Scalar m5, Scalar m6, Scalar m7, Scalar m8, Scalar m9, Scalar m10, Scalar m11, Scalar m12, Scalar m13, Scalar m14, Scalar m15)
Definition: matrix.h:69
static constexpr Matrix MakeSkew(Scalar sx, Scalar sy)
Definition: matrix.h:127
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
Definition: matrix.h:113
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
Scalar GetMaxBasisLengthXY() const
Return the maximum scale applied specifically to either the X axis or Y axis unit vectors (the bases)...
Definition: matrix.h:328
std::shared_ptr< FilterContents > WithImageFilter(const ContentContext &renderer, const FilterInput::Variant &input, const Matrix &effect_transform, Entity::RenderingMode rendering_mode) const
Definition: paint.cc:322
std::optional< StrokeParameters > GetStroke() const
Return an optional StrokeParameters if this Paint is a stroked Paint, otherwise return a nullopt.
Definition: paint.h:95
const flutter::DlColorFilter * color_filter
Definition: paint.h:81
const flutter::DlColorSource * color_source
Definition: paint.h:80
bool anti_alias
Definition: paint.h:88
const flutter::DlImageFilter * image_filter
Definition: paint.h:82
std::shared_ptr< Contents > WithFilters(const ContentContext &renderer, std::shared_ptr< Contents > input) const
Wrap this paint's configured filters to the given contents.
Definition: paint.cc:286
Style style
Definition: paint.h:85
bool invert_colors
Definition: paint.h:87
static bool CanApplyOpacityPeephole(const Paint &paint)
Whether or not a save layer with the provided paint can perform the opacity peephole optimization.
Definition: paint.h:42
std::optional< MaskBlurDescriptor > mask_blur_descriptor
Definition: paint.h:90
Color color
Definition: paint.h:79
BlendMode blend_mode
Definition: paint.h:86
std::shared_ptr< ColorSourceContents > CreateContents(const ContentContext &renderer, const Geometry *geometry, const std::optional< Matrix > &geometry_transform=std::nullopt) const
Create a ColorSourceContents representing this paint's shader/colors.
Definition: paint.cc:64
StrokeParameters stroke
Definition: paint.h:84
bool HasColorFilter() const
Whether this paint has a color filter that can apply opacity.
Definition: paint.cc:480
constexpr const RoundingRadii & GetRadii() const
Definition: round_rect.h:55
constexpr bool IsEmpty() const
Definition: round_rect.h:65
constexpr const Rect & GetBounds() const
Definition: round_rect.h:53
constexpr const RoundingRadii & GetRadii() const
constexpr const Rect & GetBounds() const
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
constexpr bool AreAllCornersCircular() const
constexpr bool AreAllCornersSame(Scalar tolerance=kEhCloseEnough) const
constexpr static RoundingRadii MakeRadius(Scalar radius)
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
Scalar sigma
Definition: sigma.h:33
Represents a texture and its intended draw transform/sampler configuration.
Definition: snapshot.h:24
Matrix transform
The transform that should be applied to this texture for rendering.
Definition: snapshot.h:27
std::shared_ptr< Texture > texture
Definition: snapshot.h:25
SamplerDescriptor sampler_descriptor
Definition: snapshot.h:29
constexpr Type GetDistance(const TPoint &p) const
Definition: point.h:201
constexpr TPoint PerpendicularRight() const
Definition: point.h:324
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:652
constexpr auto GetBottom() const
Definition: rect.h:391
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:506
constexpr auto GetTop() const
Definition: rect.h:387
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
Definition: rect.h:381
constexpr TPoint< Type > GetOrigin() const
Returns the upper left corner of the rectangle as specified by the left/top or x/y values when it was...
Definition: rect.h:354
constexpr std::optional< TRect > Intersection(const TRect &o) const
Definition: rect.h:562
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: rect.h:331
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:144
constexpr static TRect MakeCircleBounds(const TPoint< Type > &center, Type radius)
Definition: rect.h:156
constexpr auto GetLeft() const
Definition: rect.h:385
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:361
Round(const TRect< U > &r)
Definition: rect.h:764
RoundOut(const TRect< U > &r)
Definition: rect.h:748
constexpr auto GetRight() const
Definition: rect.h:389
constexpr bool IsSquare() const
Returns true if width and height are equal and neither is NaN.
Definition: rect.h:338
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:636
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.
Definition: rect.h:375
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:416
constexpr static TRect MakeEllipseBounds(const TPoint< Type > &center, const TSize< Type > &radii)
Definition: rect.h:164
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
constexpr static TRect MakeMaximum()
Definition: rect.h:212
Type width
Definition: size.h:28
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: size.h:123
Parameters for rendering shapes using the UberSDF shader.
Color color
The color used for filling or stroking the shape.
static UberSDFParameters MakeCircle(Color color, const Point &center, Scalar radius, std::optional< StrokeParameters > stroke)
Creates UberSDFParameters for a circle.
static UberSDFParameters MakeOval(Color color, const Rect &bounds, std::optional< StrokeParameters > stroke)
Creates UberSDFParameters for an Oval.
static UberSDFParameters MakeRoundedRect(Color color, const Rect &rect, const RoundingRadii &radii, std::optional< StrokeParameters > stroke)
Creates UberSDFParameters for a rounded rectangle.
static UberSDFParameters MakeRoundedSuperellipse(Color color, const Rect &bounds, const RoundSuperellipseParam &round_superellipse_params, std::optional< StrokeParameters > stroke)
Creates UberSDFParameters for an asymmetric round superellipse.
static UberSDFParameters MakeRect(Color color, const Rect &rect, std::optional< StrokeParameters > stroke)
Creates UberSDFParameters for a rectangle.
std::vector< Point > points
#define VALIDATION_LOG
Definition: validation.h:91