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