Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
dl_rendering_unittests.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
5#include <utility>
6
7#include "absl/strings/str_split.h"
8
24#include "flutter/fml/file.h"
25#include "flutter/fml/math.h"
28#ifdef IMPELLER_SUPPORTS_RENDERING
31#endif // IMPELLER_SUPPORTS_RENDERING
32
33#include "flutter/third_party/skia/include/core/SkColor.h"
34#include "flutter/third_party/skia/include/core/SkColorFilter.h"
36
37namespace flutter {
38namespace testing {
39
40constexpr uint32_t kTestWidth = 200;
41constexpr uint32_t kTestHeight = 200;
42constexpr uint32_t kRenderWidth = 100;
43constexpr uint32_t kRenderHeight = 100;
50constexpr DlScalar kRenderRadius = std::min(kRenderWidth, kRenderHeight) / 2.0;
53
58
59// The tests try 3 miter limit values, 0.0, 4.0 (the default), and 10.0
60// These values will allow us to construct a diamond that spans the
61// width or height of the render box and still show the miter for 4.0
62// and 10.0.
63// These values were discovered by drawing a diamond path in Skia fiddle
64// and then playing with the cross-axis size until the miter was about
65// as large as it could get before it got cut off.
66
67// The X offsets which will be used for tall vertical diamonds are
68// expressed in terms of the rendering height to obtain the proper angle
72
73// The Y offsets which will be used for long horizontal diamonds are
74// expressed in terms of the rendering width to obtain the proper angle
78
79// Render 3 vertical and horizontal diamonds each
80// designed to break at the tested miter limits
81// 0.0, 4.0 and 10.0
82// Center is biased by 0.5 to include more pixel centers in the
83// thin miters
92 // Vertical diamonds:
93 // M10 M4 Mextreme
94 // /\ /|\ /\ top of RenderBounds
95 // / \ / | \ / \ to
96 // <----X--+--X----> RenderCenter
97 // \ / \ | / \ / to
98 // \/ \|/ \/ bottom of RenderBounds
99 // clang-format off
113 // clang-format on
114};
118
127 // Horizontal diamonds
128 // Same configuration as Vertical diamonds above but
129 // rotated 90 degrees
130 // clang-format off
144 // clang-format on
145};
149
150namespace {
151constexpr uint8_t toC(DlScalar fComp) {
152 return round(fComp * 255);
153}
154
155constexpr uint32_t PremultipliedArgb(const DlColor& color) {
156 if (color.isOpaque()) {
157 return color.argb();
158 }
159 DlScalar f = color.getAlphaF();
160 return (color.argb() & 0xFF000000) | //
161 toC(color.getRedF() * f) << 16 | //
162 toC(color.getGreenF() * f) << 8 | //
163 toC(color.getBlueF() * f);
164}
165} // namespace
166
167static void DrawCheckerboard(DlCanvas* canvas) {
168 DlPaint p0, p1;
170 p0.setColor(DlColor(0xff00fe00)); // off-green
173 // Some pixels need some transparency for DstIn testing
174 p1.setAlpha(128);
175 int cbdim = 5;
176 int width = canvas->GetBaseLayerDimensions().width;
177 int height = canvas->GetBaseLayerDimensions().height;
178 for (int y = 0; y < width; y += cbdim) {
179 for (int x = 0; x < height; x += cbdim) {
180 DlPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1;
181 canvas->DrawRect(DlRect::MakeXYWH(x, y, cbdim, cbdim), cellp);
182 }
183 }
184}
185
186static std::shared_ptr<DlImageColorSource> MakeColorSource(
187 const sk_sp<DlImage>& image) {
188 return std::make_shared<DlImageColorSource>(image, //
192}
193
194// Used to show "INFO" warnings about tests that are omitted on certain
195// backends, but only once for the entire test run to avoid warning spam
197 public:
198 explicit OncePerBackendWarning(const std::string& warning)
199 : warning_(warning) {}
200
201 void warn(const std::string& name) {
202 if (warnings_sent_.find(name) == warnings_sent_.end()) {
203 warnings_sent_.insert(name);
204 FML_LOG(INFO) << warning_ << " on " << name;
205 }
206 }
207
208 private:
209 std::string warning_;
210 std::set<std::string> warnings_sent_;
211};
212
213// A class to specify how much tolerance to allow in bounds estimates.
214// For some attributes, the machinery must make some conservative
215// assumptions as to the extent of the bounds, but some of our test
216// parameters do not produce bounds that expand by the full conservative
217// estimates. This class provides a number of tweaks to apply to the
218// pixel bounds to account for the conservative factors.
219//
220// An instance is passed along through the methods and if any test adds
221// a paint attribute or other modifier that will cause a more conservative
222// estimate for bounds, it can modify the factors here to account for it.
223// Ideally, all tests will be executed with geometry that will trigger
224// the conservative cases anyway and all attributes will be combined with
225// other attributes that make their output more predictable, but in those
226// cases where a given test sequence cannot really provide attributes to
227// demonstrate the worst case scenario, they can modify these factors to
228// avoid false bounds overflow notifications.
230 public:
231 BoundsTolerance() = default;
233
235 DlScalar bounds_pad_y) const {
236 BoundsTolerance copy = BoundsTolerance(*this);
237 copy.bounds_pad_ += DlPoint(bounds_pad_x, bounds_pad_y);
238 return copy;
239 }
240
241 BoundsTolerance mulScale(DlScalar scale_x, DlScalar scale_y) const {
242 BoundsTolerance copy = BoundsTolerance(*this);
243 copy.scale_ *= DlPoint(scale_x, scale_y);
244 return copy;
245 }
246
248 DlScalar absolute_pad_y) const {
249 BoundsTolerance copy = BoundsTolerance(*this);
250 copy.absolute_pad_ += DlPoint(absolute_pad_x, absolute_pad_y);
251 return copy;
252 }
253
255 DlScalar absolute_pad_y) const {
256 BoundsTolerance copy = BoundsTolerance(*this);
257 copy.clip_pad_ += DlPoint(absolute_pad_x, absolute_pad_y);
258 return copy;
259 }
260
262 BoundsTolerance copy = BoundsTolerance(*this);
263 copy.discrete_offset_ += discrete_offset;
264 return copy;
265 }
266
268 BoundsTolerance copy = BoundsTolerance(*this);
269 copy.clip_ = copy.clip_.IntersectionOrEmpty(clip);
270 return copy;
271 }
272
273 static DlRect Scale(const DlRect& rect, const DlPoint& scales) {
274 DlScalar outset_x = rect.GetWidth() * (scales.x - 1);
275 DlScalar outset_y = rect.GetHeight() * (scales.y - 1);
276 return rect.Expand(outset_x, outset_y);
277 }
278
279 bool overflows(DlIRect pix_bounds,
280 int worst_bounds_pad_x,
281 int worst_bounds_pad_y) const {
282 DlRect allowed = DlRect::Make(pix_bounds);
283 allowed = allowed.Expand(bounds_pad_.x, bounds_pad_.y);
284 allowed = Scale(allowed, scale_);
285 allowed = allowed.Expand(absolute_pad_.x, absolute_pad_.y);
286 allowed = allowed.IntersectionOrEmpty(clip_);
287 allowed = allowed.Expand(clip_pad_.x, clip_pad_.y);
288 DlIRect rounded = DlIRect::RoundOut(allowed);
289 int pad_left = std::max(0, pix_bounds.GetLeft() - rounded.GetLeft());
290 int pad_top = std::max(0, pix_bounds.GetTop() - rounded.GetTop());
291 int pad_right = std::max(0, pix_bounds.GetRight() - rounded.GetRight());
292 int pad_bottom = std::max(0, pix_bounds.GetBottom() - rounded.GetBottom());
293 int allowed_pad_x = std::max(pad_left, pad_right);
294 int allowed_pad_y = std::max(pad_top, pad_bottom);
295 if (worst_bounds_pad_x > allowed_pad_x ||
296 worst_bounds_pad_y > allowed_pad_y) {
297 FML_LOG(ERROR) << "acceptable bounds padding: " //
298 << allowed_pad_x << ", " << allowed_pad_y;
299 }
300 return (worst_bounds_pad_x > allowed_pad_x ||
301 worst_bounds_pad_y > allowed_pad_y);
302 }
303
304 DlScalar discrete_offset() const { return discrete_offset_; }
305
306 bool operator==(BoundsTolerance const& other) const {
307 return bounds_pad_ == other.bounds_pad_ && scale_ == other.scale_ &&
308 absolute_pad_ == other.absolute_pad_ && clip_ == other.clip_ &&
309 clip_pad_ == other.clip_pad_ &&
310 discrete_offset_ == other.discrete_offset_;
311 }
312
313 private:
314 DlPoint bounds_pad_ = {0, 0};
315 DlPoint scale_ = {1, 1};
316 DlPoint absolute_pad_ = {0, 0};
317 DlRect clip_ = DlRect::MakeLTRB(-1E9, -1E9, 1E9, 1E9);
318 DlPoint clip_pad_ = {0, 0};
319
320 DlScalar discrete_offset_ = 0;
321};
322
323class RenderEnvironment;
334
335using DlSetup = const std::function<void(const DlSetupContext&)>;
336using DlRenderer = const std::function<void(const DlRenderContext&)>;
337static const DlSetup kEmptyDlSetup = [](const DlSetupContext&) {};
338static const DlRenderer kEmptyDlRenderer = [](const DlRenderContext&) {};
339
342
344 static RenderResult Make(const std::shared_ptr<DlSurfaceInstance>& surface) {
345 return Make(surface->SnapshotToPixelData());
346 }
347
348 static RenderResult Make(const std::shared_ptr<DlPixelData>& data) {
349 return Make(data, DlRect::MakeWH(data->width(), data->height()));
350 }
351
352 static RenderResult Make(const std::shared_ptr<DlPixelData>& data,
353 const DlRect& bounds) {
354 return {
355 .pixel_data = data,
356 .render_bounds = bounds,
357 };
358 }
359
360 std::shared_ptr<DlPixelData> pixel_data;
362};
363
371
373 virtual void Render(const RenderEnvironment& env,
374 DlCanvas* canvas,
375 const RenderJobInfo& info) = 0;
376};
377
379 public:
380 const DlMatrix& GetSetupMatrix() const {
382 return setup_matrix_;
383 }
384
387 return setup_clip_bounds_;
388 }
389
390 protected:
391 bool is_setup_ = false;
394};
395
397 explicit DlJobRenderer(const DlSetup& dl_setup,
398 const DlRenderer& dl_render,
399 const DlRenderer& dl_restore)
400 : dl_setup_(dl_setup), //
401 dl_render_(dl_render), //
402 dl_restore_(dl_restore) {}
403
404 void Render(const RenderEnvironment& env,
405 DlCanvas* canvas,
406 const RenderJobInfo& info) override {
407 FML_DCHECK(info.opacity == 1.0f);
408 DlPaint paint;
409 dl_setup_({env, canvas, paint});
410 setup_paint_ = paint;
411 setup_matrix_ = canvas->GetMatrix();
414 is_setup_ = true;
415 dl_render_({env, canvas, paint});
416 dl_restore_({env, canvas, paint});
417 }
418
419 sk_sp<DisplayList> MakeDisplayList(const RenderEnvironment& env,
420 const RenderJobInfo& info) {
422 Render(env, &builder, info);
423 return builder.Build();
424 }
425
426 const DlPaint& GetSetupPaint() const {
428 return setup_paint_;
429 }
430
431 private:
432 const DlSetup dl_setup_;
433 const DlRenderer dl_render_;
434 const DlRenderer dl_restore_;
435 DlPaint setup_paint_;
436};
437
439 explicit DisplayListJobRenderer(sk_sp<DisplayList> display_list)
440 : display_list_(std::move(display_list)) {}
441
442 void Render(const RenderEnvironment& env,
443 DlCanvas* canvas,
444 const RenderJobInfo& info) override {
445 canvas->DrawDisplayList(display_list_, info.opacity);
446 }
447
448 private:
449 sk_sp<DisplayList> display_list_;
450};
451
453 public:
455 : provider_(provider), format_(format) {}
456
458 return RenderEnvironment(provider, PixelFormat::k565);
459 }
460
462 return RenderEnvironment(provider, PixelFormat::kN32Premul);
463 }
464
466 DlRenderer& dl_renderer,
468 RenderJobInfo info = {
469 .bg = bg,
470 };
471 DlJobRenderer dl_job(dl_setup, dl_renderer, kEmptyDlRenderer);
472 ref_dl_result_ = GetResult(info, dl_job);
473 ref_dl_paint_ = dl_job.GetSetupPaint();
474 ref_matrix_ = dl_job.GetSetupMatrix();
475 ref_clip_bounds_ = dl_job.GetSetupClipBounds();
476 }
477
479 JobRenderer& renderer) const {
480 std::shared_ptr<DlSurfaceInstance> surface =
481 getSurface(info.width, info.height);
482 FML_DCHECK(surface != nullptr);
483
484 DisplayListBuilder builder(DlRect::MakeWH(info.width, info.height));
485 builder.Clear(info.bg);
486 builder.Scale(info.scale, info.scale);
487 renderer.Render(*this, &builder, info);
488 sk_sp<DisplayList> display_list = builder.Build();
489 DlRect dl_bounds = display_list->GetBounds();
490 surface->RenderDisplayList(display_list);
491 surface->FlushSubmitCpuSync();
492
493 return RenderResult::Make(surface->SnapshotToPixelData(), dl_bounds);
494 }
495
496 RenderResult GetResult(const sk_sp<DisplayList>& dl) const {
498 RenderJobInfo info = {};
499 return GetResult(info, job);
500 }
501
502 PixelFormat GetPixelFormat() const { return format_; }
503 const DlSurfaceProvider* GetProvider() const { return provider_; }
504 bool IsValid() const { return provider_->SupportsPixelFormat(format_); }
505 const std::string GetBackendName() const {
506 return provider_->GetBackendName();
507 }
508
509 const DlPaint& GetReferencePaint() const { return ref_dl_paint_; }
510 const DlMatrix& GetReferenceMatrix() const { return ref_matrix_; }
511 const DlIRect& GetReferenceClipBounds() const { return ref_clip_bounds_; }
512 const RenderResult& GetReferenceResult() const { return ref_dl_result_; }
513
514 const sk_sp<DlImage> GetTestImage() const {
515 if (test_image_ == nullptr) {
516 test_image_ = MakeTestImage();
517 }
518 return test_image_;
519 }
520
521 const std::shared_ptr<DlText> GetTestText() const {
522 if (test_text_ == nullptr) {
523 test_text_ = MakeTestText();
524 }
525 return test_text_;
526 }
527
528 private:
529 mutable std::shared_ptr<DlSurfaceInstance> cached_surface_;
530 std::shared_ptr<DlSurfaceInstance> getSurface(int width, int height) const {
532 if (cached_surface_ == nullptr || //
533 cached_surface_->width() != width ||
534 cached_surface_->height() != height) {
535 cached_surface_.reset();
536 cached_surface_ =
537 provider_->MakeOffscreenSurface(kTestWidth, kTestHeight, format_);
538 }
539 return cached_surface_;
540 }
541
542 const DlSurfaceProvider* provider_;
543 const PixelFormat format_;
544
545 DlPaint ref_dl_paint_;
546 DlMatrix ref_matrix_;
547 DlIRect ref_clip_bounds_;
548 RenderResult ref_dl_result_;
549
550 mutable sk_sp<DlImage> test_image_;
551 sk_sp<DlImage> MakeTestImage() const {
552 std::shared_ptr<DlSurfaceInstance> surface =
553 provider_->MakeOffscreenSurface(kRenderWidth, kRenderHeight,
555 DisplayListBuilder builder(DlRect::MakeWH(kRenderWidth, kRenderHeight));
556 DrawCheckerboard(&builder);
557 surface->RenderDisplayList(builder.Build());
558 surface->FlushSubmitCpuSync();
559 return surface->SnapshotToImage();
560 }
561
562 mutable std::shared_ptr<DlText> test_text_;
563 std::shared_ptr<DlText> MakeTestText() const {
564 sk_sp<SkTextBlob> blob = MakeTextBlob("Testing", kTextFontHeight);
565 if (provider_->TargetsImpeller()) {
566#ifdef IMPELLER_SUPPORTS_RENDERING
567 return DlTextImpeller::MakeFromBlob(blob);
568#else // IMPELLER_SUPPORTS_RENDERING
569 return nullptr;
570#endif // IMPELLER_SUPPORTS_RENDERING
571 } else {
572 return DlTextSkia::Make(blob);
573 }
574 }
575
576 static sk_sp<SkTextBlob> MakeTextBlob(const std::string& string,
577 DlScalar font_height) {
578 SkFont font = CreateTestFontOfSize(font_height);
579 sk_sp<SkTypeface> face = font.refTypeface();
580 FML_CHECK(face);
581 FML_CHECK(face->countGlyphs() > 0) << "No glyphs in font";
582 return SkTextBlob::MakeFromText(string.c_str(), string.size(), font,
583 SkTextEncoding::kUTF8);
584 }
585};
586
588 public:
589 explicit CaseParameters(std::string info)
591
593 : CaseParameters(std::move(info),
594 dl_setup,
596 DlColor::kTransparent(),
597 false,
598 false,
599 false) {}
600
601 CaseParameters(std::string info,
604 DlColor bg,
605 bool has_diff_clip,
608 : info_(std::move(info)),
609 bg_(bg),
610 dl_setup_(dl_setup),
611 dl_restore_(dl_restore),
612 has_diff_clip_(has_diff_clip),
613 has_mutating_save_layer_(has_mutating_save_layer),
614 fuzzy_compare_components_(fuzzy_compare_components) {}
615
617 bool mutating_layer,
618 bool fuzzy_compare_components = false) {
619 return CaseParameters(info_, dl_setup_, dl_restore, bg_, has_diff_clip_,
620 mutating_layer, fuzzy_compare_components);
621 }
622
624 return CaseParameters(info_, dl_setup_, dl_restore_, bg, has_diff_clip_,
625 has_mutating_save_layer_, fuzzy_compare_components_);
626 }
627
629 return CaseParameters(info_, dl_setup_, dl_restore_, bg_, true,
630 has_mutating_save_layer_, fuzzy_compare_components_);
631 }
632
633 std::string info() const { return info_; }
634 DlColor bg() const { return bg_; }
635 bool has_diff_clip() const { return has_diff_clip_; }
636 bool has_mutating_save_layer() const { return has_mutating_save_layer_; }
637 bool fuzzy_compare_components() const { return fuzzy_compare_components_; }
638
639 DlSetup dl_setup() const { return dl_setup_; }
640 DlRenderer dl_restore() const { return dl_restore_; }
641
642 private:
643 const std::string info_;
644 const DlColor bg_;
645 const DlSetup dl_setup_;
646 const DlRenderer dl_restore_;
647 const bool has_diff_clip_;
648 const bool has_mutating_save_layer_;
649 const bool fuzzy_compare_components_;
650};
651
653 public:
656 : dl_renderer_(dl_renderer), flags_(flags) {}
657
658 bool uses_paint() const { return !flags_.ignores_paint(); }
659 bool uses_gradient() const { return flags_.applies_shader(); }
660
661 bool impeller_compatible(const DlPaint& paint) const {
662 if (is_draw_text_blob()) {
663 // Non-color text is rendered as paths
664 if (paint.getColorSourcePtr()) {
665 return false;
666 }
667 // Non-filled text (stroke or stroke and fill) is rendered as paths
668 if (paint.getDrawStyle() != DlDrawStyle::kFill) {
669 return false;
670 }
671 }
672 return true;
673 }
674
676 const CaseParameters& caseP,
677 const DlPaint& attr,
678 const MatrixClipJobRenderer& renderer) const {
679 if (caseP.has_mutating_save_layer()) {
680 return false;
681 }
682 if (env.GetReferenceClipBounds() != renderer.GetSetupClipBounds() ||
683 caseP.has_diff_clip()) {
684 return false;
685 }
686 if (env.GetReferenceMatrix() != renderer.GetSetupMatrix() &&
687 !flags_.is_flood()) {
688 return false;
689 }
690 if (flags_.ignores_paint()) {
691 return true;
692 }
693 const DlPaint& ref_attr = env.GetReferencePaint();
694 if (flags_.applies_anti_alias() && //
695 ref_attr.isAntiAlias() != attr.isAntiAlias()) {
696 if (env.GetProvider()->TargetsImpeller()) {
697 // Impeller only does MSAA, ignoring the AA attribute
698 // https://github.com/flutter/flutter/issues/104721
699 } else {
700 return false;
701 }
702 }
703 if (flags_.applies_color() && //
704 ref_attr.getColor() != attr.getColor()) {
705 return false;
706 }
707 if (flags_.applies_blend() && //
708 ref_attr.getBlendMode() != attr.getBlendMode()) {
709 return false;
710 }
711 if (flags_.applies_color_filter() && //
712 (ref_attr.isInvertColors() != attr.isInvertColors() ||
713 NotEquals(ref_attr.getColorFilter(), attr.getColorFilter()))) {
714 return false;
715 }
716 if (flags_.applies_mask_filter() && //
717 NotEquals(ref_attr.getMaskFilter(), attr.getMaskFilter())) {
718 return false;
719 }
720 if (flags_.applies_image_filter() && //
721 ref_attr.getImageFilter() != attr.getImageFilter()) {
722 return false;
723 }
724 if (flags_.applies_shader() && //
725 NotEquals(ref_attr.getColorSource(), attr.getColorSource())) {
726 return false;
727 }
728
729 bool is_stroked = flags_.is_stroked(attr.getDrawStyle());
730 if (flags_.is_stroked(ref_attr.getDrawStyle()) != is_stroked) {
731 return false;
732 }
733 if (!is_stroked) {
734 return true;
735 }
736 if (ref_attr.getStrokeWidth() != attr.getStrokeWidth()) {
737 return false;
738 }
740 flags_.GeometryFlags(is_stroked);
741 if (geo_flags.may_have_end_caps() && //
742 getCap(ref_attr, geo_flags) != getCap(attr, geo_flags)) {
743 return false;
744 }
745 if (geo_flags.may_have_joins()) {
746 if (ref_attr.getStrokeJoin() != attr.getStrokeJoin()) {
747 return false;
748 }
749 if (ref_attr.getStrokeJoin() == DlStrokeJoin::kMiter) {
750 DlScalar ref_miter = ref_attr.getStrokeMiter();
751 DlScalar test_miter = attr.getStrokeMiter();
752 // miter limit < 1.4 affects right angles
753 if (geo_flags.may_have_acute_joins() || //
754 ref_miter < 1.4 || test_miter < 1.4) {
755 if (ref_miter != test_miter) {
756 return false;
757 }
758 }
759 }
760 }
761 return true;
762 }
763
765 DisplayListSpecialGeometryFlags geo_flags) const {
766 DlStrokeCap cap = attr.getStrokeCap();
767 if (geo_flags.butt_cap_becomes_square() && cap == DlStrokeCap::kButt) {
769 }
770 return cap;
771 }
772
773 const BoundsTolerance adjust(const BoundsTolerance& tolerance,
774 const DlPaint& paint,
775 const DlMatrix& matrix) const {
776 if (is_draw_text_blob() && tolerance.discrete_offset() > 0) {
777 // drawTextBlob needs just a little more leeway when using a
778 // discrete path effect.
779 return tolerance.addBoundsPadding(2, 2);
780 }
781 if (is_draw_line()) {
782 return lineAdjust(tolerance, paint, matrix);
783 }
784 if (is_draw_arc_center()) {
785 if (paint.getDrawStyle() != DlDrawStyle::kFill &&
787 // the miter join at the center of an arc does not really affect
788 // its bounds in any of our test cases, but the bounds code needs
789 // to take it into account for the cases where it might, so we
790 // relax our tolerance to reflect the miter bounds padding.
791 DlScalar miter_pad =
792 paint.getStrokeMiter() * paint.getStrokeWidth() * 0.5f;
793 return tolerance.addBoundsPadding(miter_pad, miter_pad);
794 }
795 }
796 return tolerance;
797 }
798
800 const DlPaint& paint,
801 const DlMatrix& matrix) const {
802 DlScalar adjust = 0.0;
803 DlScalar half_width = paint.getStrokeWidth() * 0.5f;
804 if (tolerance.discrete_offset() > 0) {
805 // When a discrete path effect is added, the bounds calculations must
806 // allow for miters in any direction, but a horizontal line will not
807 // have miters in the horizontal direction, similarly for vertical
808 // lines, and diagonal lines will have miters off at a "45 degree"
809 // angle that don't expand the bounds much at all.
810 // Also, the discrete offset will not move any points parallel with
811 // the line, so provide tolerance for both miters and offset.
812 adjust =
813 half_width * paint.getStrokeMiter() + tolerance.discrete_offset();
814 }
815
816 DisplayListSpecialGeometryFlags geo_flags = flags_.GeometryFlags(true);
817 if (paint.getStrokeCap() == DlStrokeCap::kButt &&
818 !geo_flags.butt_cap_becomes_square()) {
819 adjust = std::max(adjust, half_width);
820 }
821 if (adjust == 0) {
822 return tolerance;
823 }
824 DlScalar h_tolerance;
825 DlScalar v_tolerance;
826 if (is_horizontal_line()) {
828 h_tolerance = adjust;
829 v_tolerance = 0;
830 } else if (is_vertical_line()) {
831 h_tolerance = 0;
832 v_tolerance = adjust;
833 } else {
834 // The perpendicular miters just do not impact the bounds of
835 // diagonal lines at all as they are aimed in the wrong direction
836 // to matter. So allow tolerance in both axes.
837 h_tolerance = v_tolerance = adjust;
838 }
839 BoundsTolerance new_tolerance =
840 tolerance.addBoundsPadding(h_tolerance, v_tolerance);
841 return new_tolerance;
842 }
843
844 const DlRenderer& dl_renderer() const { return dl_renderer_; }
845
846 // Tests that call drawTextBlob with an sk_ref paint attribute will cause
847 // those attributes to be stored in an internal Skia cache so we need
848 // to expect that the |sk_ref.unique()| call will fail in those cases.
849 // See: (TBD(flar) - file Skia bug)
850 bool is_draw_text_blob() const { return is_draw_text_blob_; }
851 bool is_draw_display_list() const { return is_draw_display_list_; }
852 bool is_draw_line() const { return is_draw_line_; }
853 bool is_draw_arc_center() const { return is_draw_arc_center_; }
854 bool is_draw_path() const { return is_draw_path_; }
855 bool is_horizontal_line() const { return is_horizontal_line_; }
856 bool is_vertical_line() const { return is_vertical_line_; }
857 bool ignores_dashes() const { return ignores_dashes_; }
858
859 DisplayListAttributeFlags flags() const { return flags_; }
860
862 is_draw_text_blob_ = true;
863 return *this;
864 }
866 is_draw_display_list_ = true;
867 return *this;
868 }
870 is_draw_line_ = true;
871 return *this;
872 }
874 is_draw_arc_center_ = true;
875 return *this;
876 }
878 is_draw_path_ = true;
879 return *this;
880 }
882 ignores_dashes_ = true;
883 return *this;
884 }
886 is_horizontal_line_ = true;
887 return *this;
888 }
890 is_vertical_line_ = true;
891 return *this;
892 }
893
894 private:
895 const DlRenderer dl_renderer_;
896 const DisplayListAttributeFlags flags_;
897
898 bool is_draw_text_blob_ = false;
899 bool is_draw_display_list_ = false;
900 bool is_draw_line_ = false;
901 bool is_draw_arc_center_ = false;
902 bool is_draw_path_ = false;
903 bool ignores_dashes_ = false;
904 bool is_horizontal_line_ = false;
905 bool is_vertical_line_ = false;
906};
907
909 private:
910 static std::string failure_image_directory_;
911 static bool save_failure_images_;
912 static std::vector<std::string> failure_image_filenames_;
913
914 public:
915 static void EnableSaveImagesOnFailures() { save_failure_images_ = true; }
916
918 if (failure_image_filenames_.empty()) {
919 return;
920 }
921 FML_LOG(INFO);
922 FML_LOG(INFO) << failure_image_filenames_.size() << " images saved in "
923 << failure_image_directory_;
924 for (const std::string& filename : failure_image_filenames_) {
925 FML_LOG(INFO) << " " << filename;
926 }
927 FML_LOG(INFO);
928 }
929
931
932 static void RenderAll(const std::unique_ptr<DlSurfaceProvider>& provider,
933 const TestParameters& params,
934 const BoundsTolerance& tolerance = DefaultTolerance) {
935 RenderEnvironment env = RenderEnvironment::MakeN32(provider.get());
936 env.InitializeReference(kEmptyDlSetup, params.dl_renderer());
937
938 RenderWithTransforms(params, env, tolerance);
939 RenderWithClips(params, env, tolerance);
940 RenderWithSaveRestore(params, env, tolerance);
941 // Only test attributes if the canvas version uses the paint object
942 if (params.uses_paint()) {
943 RenderWithAttributes(params, env, tolerance);
944 }
945 }
946
947 static void RenderWithSaveRestore(const TestParameters& testP,
948 const RenderEnvironment& env,
949 const BoundsTolerance& tolerance) {
950 DlRect clip =
953 DlColor alpha_layer_color = DlColor::kCyan().withAlpha(0x7f);
954 DlRenderer dl_safe_restore = [=](const DlRenderContext& ctx) {
955 // Draw another primitive to disable peephole optimizations
956 // As the rendering op rejection in the DisplayList Builder
957 // gets smarter and smarter, this operation has had to get
958 // sneakier and sneakier about specifying an operation that
959 // won't practically show up in the output, but technically
960 // can't be culled.
961 ctx.canvas->DrawRect(
963 DlPaint());
964 ctx.canvas->Restore();
965 };
966 DlRenderer dl_opt_restore = [=](const DlRenderContext& ctx) {
967 // Just a simple restore to allow peephole optimizations to occur
968 ctx.canvas->Restore();
969 };
970 DlRect layer_bounds = kRenderBounds.Expand(-15, -15);
971 // clang-format off
972 // The following section gets re-formatted on every commit even if it
973 // doesn't change.
974 RenderWith(testP, env, tolerance,
976 "With prior save/clip/restore",
977 [=](const DlSetupContext& ctx) {
978 ctx.canvas->Save();
979 ctx.canvas->ClipRect(clip, DlClipOp::kIntersect, false);
980 DlPaint p2;
981 ctx.canvas->DrawRect(rect, p2);
982 p2.setBlendMode(DlBlendMode::kClear);
983 ctx.canvas->DrawRect(rect, p2);
984 ctx.canvas->Restore();
985 }));
986 RenderWith(testP, env, tolerance,
988 "saveLayer no paint, no bounds",
989 [=](const DlSetupContext& ctx) {
990 ctx.canvas->SaveLayer(std::nullopt, nullptr);
991 })
992 .with_restore(dl_safe_restore, false));
993 RenderWith(testP, env, tolerance,
995 "saveLayer no paint, with bounds",
996 [=](const DlSetupContext& ctx) {
997 ctx.canvas->SaveLayer(layer_bounds, nullptr);
998 })
999 .with_restore(dl_safe_restore, true));
1000 RenderWith(testP, env, tolerance,
1002 "saveLayer with alpha, no bounds",
1003 [=](const DlSetupContext& ctx) {
1004 DlPaint save_p;
1005 save_p.setColor(alpha_layer_color);
1006 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1007 })
1008 .with_restore(dl_safe_restore, true));
1009 RenderWith(testP, env, tolerance,
1011 "saveLayer with peephole alpha, no bounds",
1012 [=](const DlSetupContext& ctx) {
1013 DlPaint save_p;
1014 save_p.setColor(alpha_layer_color);
1015 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1016 })
1017 .with_restore(dl_opt_restore, true, true));
1018 RenderWith(testP, env, tolerance,
1020 "saveLayer with alpha and bounds",
1021 [=](const DlSetupContext& ctx) {
1022 DlPaint save_p;
1023 save_p.setColor(alpha_layer_color);
1024 ctx.canvas->SaveLayer(layer_bounds, &save_p);
1025 })
1026 .with_restore(dl_safe_restore, true));
1027 // clang-format on
1028 {
1029 // Being able to see a backdrop blur requires a non-default background
1030 // so we create a new environment for these tests that has a checkerboard
1031 // background that can be blurred by the backdrop filter. We also want
1032 // to avoid the rendered primitive from obscuring the blurred background
1033 // so we set an alpha value which works for all primitives except for
1034 // drawColor which can override the alpha with its color, but it now uses
1035 // a non-opaque color to avoid that problem.
1036 RenderEnvironment backdrop_env =
1038 DlSetup dl_backdrop_setup = [=](const DlSetupContext& ctx) {
1039 DlPaint setup_p;
1041 ctx.canvas->DrawPaint(setup_p);
1042 };
1043 DlSetup dl_content_setup = [=](const DlSetupContext& ctx) {
1044 ctx.paint.setAlpha(ctx.paint.getAlpha() / 2);
1045 };
1046 backdrop_env.InitializeReference(dl_backdrop_setup, testP.dl_renderer());
1047
1048 DlBlurImageFilter dl_backdrop(5, 5, DlTileMode::kDecal);
1049 // clang-format off
1050 // The following section gets re-formatted on every commit even if it
1051 // doesn't change.
1052 RenderWith(
1053 testP, backdrop_env, tolerance,
1055 "saveLayer with backdrop",
1056 [=](const DlSetupContext& ctx) {
1057 dl_backdrop_setup(ctx);
1058 ctx.canvas->SaveLayer(std::nullopt, nullptr, &dl_backdrop);
1059 dl_content_setup(ctx);
1060 })
1061 .with_restore(dl_safe_restore, true));
1062 RenderWith(testP, backdrop_env, tolerance,
1064 "saveLayer with bounds and backdrop",
1065 [=](const DlSetupContext& ctx) {
1066 dl_backdrop_setup(ctx);
1067 ctx.canvas->SaveLayer(layer_bounds, nullptr,
1068 &dl_backdrop);
1069 dl_content_setup(ctx);
1070 })
1071 .with_restore(dl_safe_restore, true));
1072 RenderWith(
1073 testP, backdrop_env, tolerance,
1075 "clipped saveLayer with backdrop",
1076 [=](const DlSetupContext& ctx) {
1077 dl_backdrop_setup(ctx);
1078 ctx.canvas->ClipRect(layer_bounds);
1079 ctx.canvas->SaveLayer(std::nullopt, nullptr, &dl_backdrop);
1080 dl_content_setup(ctx);
1081 })
1082 .with_restore(dl_safe_restore, true));
1083 // clang-format on
1084 }
1085
1086 {
1087 // clang-format off
1088 constexpr float rotate_alpha_color_matrix[20] = {
1089 0, 1, 0, 0 , 0,
1090 0, 0, 1, 0 , 0,
1091 1, 0, 0, 0 , 0,
1092 0, 0, 0, 0.5, 0,
1093 };
1094 // clang-format on
1095 std::shared_ptr<const DlColorFilter> dl_alpha_rotate_filter =
1096 DlColorFilter::MakeMatrix(rotate_alpha_color_matrix);
1097 // clang-format off
1098 // The following section gets re-formatted on every commit even if it
1099 // doesn't change.
1100 {
1101 RenderWith(testP, env, tolerance,
1103 "saveLayer ColorFilter, no bounds",
1104 [=](const DlSetupContext& ctx) {
1105 DlPaint save_p;
1106 save_p.setColorFilter(dl_alpha_rotate_filter);
1107 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1108 ctx.paint.setStrokeWidth(5.0);
1109 })
1110 .with_restore(dl_safe_restore, true));
1111 }
1112 {
1113 RenderWith(testP, env, tolerance,
1115 "saveLayer ColorFilter and bounds",
1116 [=](const DlSetupContext& ctx) {
1117 DlPaint save_p;
1118 save_p.setColorFilter(dl_alpha_rotate_filter);
1119 ctx.canvas->SaveLayer(kRenderBounds, &save_p);
1120 ctx.paint.setStrokeWidth(5.0);
1121 })
1122 .with_restore(dl_safe_restore, true));
1123 }
1124 // clang-format on
1125 }
1126
1127 {
1128 // clang-format off
1129 constexpr float color_matrix[20] = {
1130 0.5, 0, 0, 0, 0.5,
1131 0, 0.5, 0, 0, 0.5,
1132 0, 0, 0.5, 0, 0.5,
1133 0, 0, 0, 1, 0,
1134 };
1135 // clang-format on
1136 std::shared_ptr<const DlColorFilter> dl_color_filter =
1137 DlColorFilter::MakeMatrix(color_matrix);
1138 std::shared_ptr<DlImageFilter> dl_cf_image_filter =
1139 DlImageFilter::MakeColorFilter(dl_color_filter);
1140 // clang-format off
1141 // The following section gets re-formatted on every commit even if it
1142 // doesn't change.
1143 {
1144 RenderWith(testP, env, tolerance,
1146 "saveLayer ImageFilter, no bounds",
1147 [=](const DlSetupContext& ctx) {
1148 DlPaint save_p;
1149 save_p.setImageFilter(dl_cf_image_filter);
1150 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1151 ctx.paint.setStrokeWidth(5.0);
1152 })
1153 .with_restore(dl_safe_restore, true));
1154 }
1155 {
1156 RenderWith(testP, env, tolerance,
1158 "saveLayer ImageFilter and bounds",
1159 [=](const DlSetupContext& ctx) {
1160 DlPaint save_p;
1161 save_p.setImageFilter(dl_cf_image_filter);
1162 ctx.canvas->SaveLayer(kRenderBounds, &save_p);
1163 ctx.paint.setStrokeWidth(5.0);
1164 })
1165 .with_restore(dl_safe_restore, true));
1166 }
1167 // clang-format on
1168 }
1169 }
1170
1171 static void RenderWithAttributes(const TestParameters& testP,
1172 const RenderEnvironment& env,
1173 const BoundsTolerance& tolerance) {
1174 RenderWith(testP, env, tolerance, CaseParameters("Defaults Test"));
1175
1176 {
1177 // CPU renderer with default line width of 0 does not show antialiasing
1178 // for stroked primitives, so we make a new reference with a non-trivial
1179 // stroke width to demonstrate the differences
1181 // Tweak the bounds tolerance for the displacement of 1/10 of a pixel
1182 const BoundsTolerance aa_tolerance = tolerance.addBoundsPadding(1, 1);
1183 auto dl_aa_setup = [=](DlSetupContext ctx, bool is_aa) -> void {
1184 ctx.canvas->Translate(0.1, 0.1);
1185 ctx.paint.setAntiAlias(is_aa);
1186 ctx.paint.setStrokeWidth(5.0);
1187 };
1188 aa_env.InitializeReference(
1189 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); },
1190 testP.dl_renderer());
1191
1192 // clang-format off
1193 // The following section gets re-formatted on every commit even if it
1194 // doesn't change.
1195 RenderWith(
1196 testP, aa_env, aa_tolerance,
1198 "AntiAlias == True",
1199 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, true); }));
1200 RenderWith(
1201 testP, aa_env, aa_tolerance,
1203 "AntiAlias == False",
1204 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); }));
1205 // clang-format on
1206 }
1207
1208 // clang-format off
1209 // The following section gets re-formatted on every commit even if it
1210 // doesn't change.
1211 RenderWith( //
1212 testP, env, tolerance,
1214 "Color == Blue",
1215 [=](const DlSetupContext& ctx) {
1217 }));
1218 RenderWith( //
1219 testP, env, tolerance,
1221 "Color == Green",
1222 [=](const DlSetupContext& ctx) {
1224 }));
1225 // clang-format on
1226
1227 RenderWithStrokes(testP, env, tolerance);
1228
1229 {
1230 // half opaque cyan
1231 DlColor blendable_color = DlColor::kCyan().withAlpha(0x7f);
1232 DlColor bg = DlColor::kWhite();
1233
1234 // clang-format off
1235 // The following section gets re-formatted on every commit even if it
1236 // doesn't change.
1237 RenderWith(testP, env, tolerance,
1239 "Blend == SrcIn",
1240 [=](const DlSetupContext& ctx) {
1241 ctx.paint.setBlendMode(DlBlendMode::kSrcIn);
1242 ctx.paint.setColor(blendable_color);
1243 })
1244 .with_bg(bg));
1245 RenderWith(testP, env, tolerance,
1247 "Blend == DstIn",
1248 [=](const DlSetupContext& ctx) {
1249 ctx.paint.setBlendMode(DlBlendMode::kDstIn);
1250 ctx.paint.setColor(blendable_color);
1251 })
1252 .with_bg(bg));
1253 // clang-format on
1254 }
1255
1256 {
1257 // Being able to see a blur requires some non-default attributes,
1258 // like a non-trivial stroke width and a shader rather than a color
1259 // (for drawPaint) so we create a new environment for these tests.
1260 RenderEnvironment blur_env =
1262 DlSetup dl_blur_setup = [=](const DlSetupContext& ctx) {
1264 ctx.paint.setStrokeWidth(5.0);
1265 };
1266 blur_env.InitializeReference(dl_blur_setup, testP.dl_renderer());
1267
1268 DlBlurImageFilter dl_filter_decal_5(5.0, 5.0, DlTileMode::kDecal);
1269 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1270 {
1271 // clang-format off
1272 // The following section gets re-formatted on every commit even if it
1273 // doesn't change.
1274 RenderWith(testP, blur_env, blur_5_tolerance,
1276 "ImageFilter == Decal Blur 5",
1277 [=](const DlSetupContext& ctx) {
1278 dl_blur_setup(ctx);
1279 ctx.paint.setImageFilter(&dl_filter_decal_5);
1280 }));
1281 // clang-format on
1282 }
1283 DlBlurImageFilter dl_filter_clamp_5(5.0, 5.0, DlTileMode::kClamp);
1284 {
1285 // clang-format off
1286 // The following section gets re-formatted on every commit even if it
1287 // doesn't change.
1288 RenderWith(testP, blur_env, blur_5_tolerance,
1290 "ImageFilter == Clamp Blur 5",
1291 [=](const DlSetupContext& ctx) {
1292 dl_blur_setup(ctx);
1293 ctx.paint.setImageFilter(&dl_filter_clamp_5);
1294 }));
1295 // clang-format on
1296 }
1297 }
1298
1299 {
1300 // Being able to see a dilate requires some non-default attributes,
1301 // like a non-trivial stroke width and a shader rather than a color
1302 // (for drawPaint) so we create a new environment for these tests.
1303 RenderEnvironment dilate_env =
1305 DlSetup dl_dilate_setup = [=](const DlSetupContext& ctx) {
1307 ctx.paint.setStrokeWidth(5.0);
1308 };
1309 dilate_env.InitializeReference(dl_dilate_setup, testP.dl_renderer());
1310
1311 DlDilateImageFilter dl_dilate_filter_5(5.0, 5.0);
1312 // clang-format off
1313 // The following section gets re-formatted on every commit even if it
1314 // doesn't change.
1315 RenderWith(testP, dilate_env, tolerance,
1317 "ImageFilter == Dilate 5",
1318 [=](const DlSetupContext& ctx) {
1319 dl_dilate_setup(ctx);
1320 ctx.paint.setImageFilter(&dl_dilate_filter_5);
1321 }));
1322 // clang-format on
1323 }
1324
1325 {
1326 // Being able to see an erode requires some non-default attributes,
1327 // like a non-trivial stroke width and a shader rather than a color
1328 // (for drawPaint) so we create a new environment for these tests.
1329 RenderEnvironment erode_env =
1331 DlSetup dl_erode_setup = [=](const DlSetupContext& ctx) {
1333 ctx.paint.setStrokeWidth(6.0);
1334 };
1335 erode_env.InitializeReference(dl_erode_setup, testP.dl_renderer());
1336
1337 // do not erode too much, because some tests assert there are enough
1338 // pixels that are changed.
1339 DlErodeImageFilter dl_erode_filter_1(1.0, 1.0);
1340 // clang-format off
1341 // The following section gets re-formatted on every commit even if it
1342 // doesn't change.
1343 RenderWith(testP, erode_env, tolerance,
1345 "ImageFilter == Erode 1",
1346 [=](const DlSetupContext& ctx) {
1347 dl_erode_setup(ctx);
1348 ctx.paint.setImageFilter(&dl_erode_filter_1);
1349 }));
1350 // clang-format on
1351 }
1352
1353 {
1354 // clang-format off
1355 constexpr float rotate_color_matrix[20] = {
1356 0, 1, 0, 0, 0,
1357 0, 0, 1, 0, 0,
1358 1, 0, 0, 0, 0,
1359 0, 0, 0, 1, 0,
1360 };
1361 // clang-format on
1362 std::shared_ptr<const DlColorFilter> dl_color_filter =
1363 DlColorFilter::MakeMatrix(rotate_color_matrix);
1364 {
1365 DlColor bg = DlColor::kWhite();
1366 // clang-format off
1367 // The following section gets re-formatted on every commit even if it
1368 // doesn't change.
1369 RenderWith(testP, env, tolerance,
1371 "ColorFilter == RotateRGB",
1372 [=](const DlSetupContext& ctx) {
1374 ctx.paint.setColorFilter(dl_color_filter);
1375 })
1376 .with_bg(bg));
1377 // clang-format on
1378 }
1379 {
1380 DlColor bg = DlColor::kWhite();
1381 // clang-format off
1382 // The following section gets re-formatted on every commit even if it
1383 // doesn't change.
1384 RenderWith(testP, env, tolerance,
1386 "ColorFilter == Invert",
1387 [=](const DlSetupContext& ctx) {
1389 ctx.paint.setInvertColors(true);
1390 })
1391 .with_bg(bg));
1392 // clang-format on
1393 }
1394 }
1395
1396 {
1397 const DlBlurMaskFilter dl_mask_filter(DlBlurStyle::kNormal, 5.0);
1398 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1399 {
1400 // clang-format off
1401 // The following section gets re-formatted on every commit even if it
1402 // doesn't change.
1403 RenderWith(testP, env, blur_5_tolerance,
1405 "MaskFilter == Blur 5",
1406 [=](const DlSetupContext& ctx) {
1407 // Stroked primitives need some non-trivial stroke
1408 // width to be blurred
1409 ctx.paint.setStrokeWidth(5.0);
1410 ctx.paint.setMaskFilter(&dl_mask_filter);
1411 }));
1412 // clang-format on
1413 }
1414 }
1415
1416 {
1417 DlPoint dl_end_points[] = {
1420 };
1421 DlColor dl_colors[] = {
1425 };
1426 float stops[] = {
1427 0.0,
1428 0.5,
1429 1.0,
1430 };
1431 std::shared_ptr<DlColorSource> dl_gradient =
1432 DlColorSource::MakeLinear(dl_end_points[0], dl_end_points[1], 3,
1433 dl_colors, stops, DlTileMode::kMirror);
1434 {
1435 // clang-format off
1436 // The following section gets re-formatted on every commit even if it
1437 // doesn't change.
1438 RenderWith(testP, env, tolerance,
1440 "LinearGradient GYB",
1441 [=](const DlSetupContext& ctx) {
1442 ctx.paint.setColorSource(dl_gradient);
1443 }));
1444 // clang-format on
1445 }
1446 }
1447 }
1448
1449 static void RenderWithStrokes(const TestParameters& testP,
1450 const RenderEnvironment& env,
1451 const BoundsTolerance& tolerance_in) {
1452 // The test cases were generated with geometry that will try to fill
1453 // out the various miter limits used for testing, but they can be off
1454 // by a couple of pixels so we will relax bounds testing for strokes by
1455 // a couple of pixels.
1456
1457 // clang-format off
1458 // The following section gets re-formatted on every commit even if it
1459 // doesn't change.
1460 BoundsTolerance tolerance = tolerance_in.addBoundsPadding(2, 2);
1461 RenderWith(testP, env, tolerance,
1463 "Fill",
1464 [=](const DlSetupContext& ctx) {
1466 }));
1467 // clang-format on
1468
1469 // Skia on HW produces a strong miter consistent with width=1.0
1470 // for any width less than a pixel, but the bounds computations of
1471 // both DL and SkPicture do not account for this. We will get
1472 // OOB pixel errors for the highly mitered drawPath geometry if
1473 // we don't set stroke width to 1.0 for that test on HW.
1474 // See https://bugs.chromium.org/p/skia/issues/detail?id=14046
1475 bool no_hairlines =
1476 testP.is_draw_path() &&
1477 env.GetProvider()->GetBackendType() != BackendType::kSkiaSoftware;
1478 // clang-format off
1479 // The following section gets re-formatted on every commit even if it
1480 // doesn't change.
1481 RenderWith(testP, env, tolerance,
1483 "Stroke + defaults",
1484 [=](const DlSetupContext& ctx) {
1485 if (no_hairlines) {
1486 ctx.paint.setStrokeWidth(1.0);
1487 }
1489 }));
1490
1491 RenderWith(testP, env, tolerance,
1493 "Fill + unnecessary StrokeWidth 10",
1494 [=](const DlSetupContext& ctx) {
1496 ctx.paint.setStrokeWidth(10.0);
1497 }));
1498 // clang-format on
1499
1500 RenderEnvironment stroke_base_env =
1502 DlSetup dl_stroke_setup = [=](const DlSetupContext& ctx) {
1504 ctx.paint.setStrokeWidth(5.0);
1505 };
1506 stroke_base_env.InitializeReference(dl_stroke_setup, testP.dl_renderer());
1507
1508 // clang-format off
1509 // The following section gets re-formatted on every commit even if it
1510 // doesn't change.
1511 RenderWith(testP, stroke_base_env, tolerance,
1513 "Stroke Width 10",
1514 [=](const DlSetupContext& ctx) {
1516 ctx.paint.setStrokeWidth(10.0);
1517 }));
1518 RenderWith(testP, stroke_base_env, tolerance,
1520 "Stroke Width 5",
1521 [=](const DlSetupContext& ctx) {
1523 ctx.paint.setStrokeWidth(5.0);
1524 }));
1525
1526 RenderWith(testP, stroke_base_env, tolerance,
1528 "Stroke Width 5, Square Cap",
1529 [=](const DlSetupContext& ctx) {
1531 ctx.paint.setStrokeWidth(5.0);
1533 }));
1534 RenderWith(testP, stroke_base_env, tolerance,
1536 "Stroke Width 5, Round Cap",
1537 [=](const DlSetupContext& ctx) {
1539 ctx.paint.setStrokeWidth(5.0);
1541 }));
1542
1543 RenderWith(testP, stroke_base_env, tolerance,
1545 "Stroke Width 5, Bevel Join",
1546 [=](const DlSetupContext& ctx) {
1548 ctx.paint.setStrokeWidth(5.0);
1550 }));
1551 RenderWith(testP, stroke_base_env, tolerance,
1553 "Stroke Width 5, Round Join",
1554 [=](const DlSetupContext& ctx) {
1556 ctx.paint.setStrokeWidth(5.0);
1558 }));
1559
1560 RenderWith(testP, stroke_base_env, tolerance,
1562 "Stroke Width 5, Miter 10",
1563 [=](const DlSetupContext& ctx) {
1565 ctx.paint.setStrokeWidth(5.0);
1566 ctx.paint.setStrokeMiter(10.0);
1568 }));
1569
1570 RenderWith(testP, stroke_base_env, tolerance,
1572 "Stroke Width 5, Miter 0",
1573 [=](const DlSetupContext& ctx) {
1575 ctx.paint.setStrokeWidth(5.0);
1576 ctx.paint.setStrokeMiter(0.0);
1578 }));
1579 // clang-format on
1580 }
1581
1582 static void RenderWithTransforms(const TestParameters& testP,
1583 const RenderEnvironment& env,
1584 const BoundsTolerance& tolerance) {
1585 // If the rendering method does not fill the corners of the original
1586 // bounds, then the estimate under rotation or skewing will be off
1587 // so we scale the padding by about 5% to compensate.
1588 BoundsTolerance skewed_tolerance = tolerance.mulScale(1.05, 1.05);
1589 RenderWith( //
1590 testP, env, tolerance,
1592 "Translate 5, 10", //
1593 [=](const DlSetupContext& ctx) { ctx.canvas->Translate(5, 10); }));
1594 RenderWith( //
1595 testP, env, tolerance,
1597 "Scale +5%", //
1598 [=](const DlSetupContext& ctx) { ctx.canvas->Scale(1.05, 1.05); }));
1599 RenderWith( //
1600 testP, env, skewed_tolerance,
1602 "Rotate 5 degrees", //
1603 [=](const DlSetupContext& ctx) { ctx.canvas->Rotate(5); }));
1604 RenderWith( //
1605 testP, env, skewed_tolerance,
1607 "Skew 5%", //
1608 [=](const DlSetupContext& ctx) { ctx.canvas->Skew(0.05, 0.05); }));
1609 {
1610 // This rather odd transform can cause slight differences in
1611 // computing in-bounds samples depending on which base rendering
1612 // routine Skia uses. Making sure our matrix values are powers
1613 // of 2 reduces, but does not eliminate, these slight differences
1614 // in calculation when we are comparing rendering with an alpha
1615 // to rendering opaque colors in the group opacity tests, for
1616 // example.
1617 DlScalar tweak = 1.0 / 16.0;
1618 DlMatrix matrix = DlMatrix::MakeRow(
1619 // clang-format off
1620 1.0 + tweak, tweak, 0, 5,
1621 tweak, 1.0 + tweak, 0, 10,
1622 0, 0, 1, 0,
1623 0, 0, 0, 1
1624 // clang-format on
1625 );
1626 // clang-format off
1627 // The following section gets re-formatted on every commit even if it
1628 // doesn't change.
1629 RenderWith(
1630 testP, env, skewed_tolerance,
1632 "Transform 2D Affine Matrix",
1633 [=](const DlSetupContext& ctx) {
1634 ctx.canvas->Transform(matrix);
1635 }));
1636 RenderWith(
1637 testP, env, skewed_tolerance,
1639 "Transform 2D Affine inline",
1640 [=](const DlSetupContext& ctx) {
1641 ctx.canvas->Transform2DAffine(1.0 + tweak, tweak, 5,
1642 tweak, 1.0 + tweak, 10);
1643 }));
1644 // clang-format on
1645 }
1646 {
1647 DlMatrix matrix = DlMatrix::MakeRow(1.0f, 0.0f, 0.0f, kRenderCenterX, //
1648 0.0f, 1.0f, 0.0f, kRenderCenterY, //
1649 0.0f, 0.0f, 1.0f, 0.0f, //
1650 0.0f, 0.0f, .001f, 1.0f);
1651 matrix = matrix * DlMatrix::MakeRotationX(DlDegrees(3));
1652 matrix = matrix * DlMatrix::MakeRotationY(DlDegrees(4));
1653 matrix = matrix.Translate({-kRenderCenterX, -kRenderCenterY, 0.0f});
1654 // clang-format off
1655 // The following section gets re-formatted on every commit even if it
1656 // doesn't change.
1657 RenderWith(
1658 testP, env, skewed_tolerance,
1660 "Transform Full Perspective Matrix",
1661 [=](const DlSetupContext& ctx) {
1662 ctx.canvas->Transform(matrix);
1663 }));
1664 RenderWith(
1665 testP, env, skewed_tolerance,
1667 "Transform Full Perspective inline",
1668 [=](const DlSetupContext& ctx) {
1670 // These values match what ends up in matrix above
1671 0.997564, 0.000000, 0.069756, 0.243591,
1672 0.003651, 0.998630, -0.052208, -0.228027,
1673 -0.069661, 0.052336, 0.996197, 1.732491,
1674 -0.000070, 0.000052, 0.000996, 1.001732
1675 );
1676 }));
1677 // clang-format on
1678 }
1679 }
1680
1681 static void RenderWithClips(const TestParameters& testP,
1682 const RenderEnvironment& env,
1683 const BoundsTolerance& diff_tolerance) {
1684 // We used to use an inset of 15.5 pixels here, but since Skia's rounding
1685 // behavior at the center of pixels does not match between HW and SW, we
1686 // ended up with some clips including different pixels between the two
1687 // destinations and this interacted poorly with the carefully chosen
1688 // geometry in some of the tests which was designed to have just the
1689 // right features fully filling the clips based on the SW rounding. By
1690 // moving to a 15.4 inset, the edge of the clip is never on the "rounding
1691 // edge" of a pixel.
1692 DlRect r_clip = kRenderBounds.Expand(-15.4, -15.4);
1693 BoundsTolerance intersect_tolerance = diff_tolerance.clip(r_clip);
1694 intersect_tolerance = intersect_tolerance.addPostClipPadding(1, 1);
1695 // clang-format off
1696 // The following section gets re-formatted on every commit even if it
1697 // doesn't change.
1698 RenderWith(testP, env, intersect_tolerance,
1700 "Hard ClipRect inset by 15.4",
1701 [=](const DlSetupContext& ctx) {
1702 ctx.canvas->ClipRect(r_clip, DlClipOp::kIntersect, false);
1703 }));
1704 RenderWith(testP, env, intersect_tolerance,
1706 "AntiAlias ClipRect inset by 15.4",
1707 [=](const DlSetupContext& ctx) {
1708 ctx.canvas->ClipRect(r_clip, DlClipOp::kIntersect, true);
1709 }));
1710 RenderWith(testP, env, diff_tolerance,
1712 "Hard ClipRect Diff, inset by 15.4",
1713 [=](const DlSetupContext& ctx) {
1714 ctx.canvas->ClipRect(r_clip, DlClipOp::kDifference, false);
1715 })
1716 .with_diff_clip());
1717 RenderWith(testP, env, intersect_tolerance,
1719 "Hard ClipOval",
1720 [=](const DlSetupContext& ctx) {
1721 ctx.canvas->ClipOval(r_clip, DlClipOp::kIntersect, false);
1722 }));
1723 RenderWith(testP, env, intersect_tolerance,
1725 "AntiAlias ClipOval",
1726 [=](const DlSetupContext& ctx) {
1727 ctx.canvas->ClipOval(r_clip, DlClipOp::kIntersect, true);
1728 }));
1729 RenderWith(testP, env, diff_tolerance,
1731 "Hard ClipOval Diff",
1732 [=](const DlSetupContext& ctx) {
1733 ctx.canvas->ClipOval(r_clip, DlClipOp::kDifference, false);
1734 })
1735 .with_diff_clip());
1736 // clang-format on
1737
1738 // This test RR clip used to use very small radii, but due to
1739 // optimizations in the HW rrect rasterization, this caused small
1740 // bulges in the corners of the RRect which were interpreted as
1741 // "clip overruns" by the clip OOB pixel testing code. Using less
1742 // abusively small radii fixes the problem.
1743 DlRoundRect rr_clip = DlRoundRect::MakeRectXY(r_clip, 9, 9);
1744 // clang-format off
1745 // The following section gets re-formatted on every commit even if it
1746 // doesn't change.
1747 RenderWith(testP, env, intersect_tolerance,
1749 "Hard ClipRRect with radius of 9",
1750 [=](const DlSetupContext& ctx) {
1752 false);
1753 }));
1754 RenderWith(testP, env, intersect_tolerance,
1756 "AntiAlias ClipRRect with radius of 9",
1757 [=](const DlSetupContext& ctx) {
1759 true);
1760 }));
1761 RenderWith(testP, env, diff_tolerance,
1763 "Hard ClipRRect Diff, with radius of 9",
1764 [=](const DlSetupContext& ctx) {
1766 false);
1767 })
1768 .with_diff_clip());
1769 // clang-format on
1770
1771 DlPathBuilder path_builder;
1772 path_builder.SetFillType(DlPathFillType::kOdd);
1773 path_builder.AddRect(r_clip);
1774 path_builder.AddCircle(DlPoint(kRenderCenterX, kRenderCenterY), 1.0f);
1775 DlPath path_clip = path_builder.TakePath();
1776 // clang-format off
1777 // The following section gets re-formatted on every commit even if it
1778 // doesn't change.
1779 RenderWith(testP, env, intersect_tolerance,
1781 "Hard ClipPath inset by 15.4",
1782 [=](const DlSetupContext& ctx) {
1783 ctx.canvas->ClipPath(path_clip, DlClipOp::kIntersect,
1784 false);
1785 }));
1786 RenderWith(testP, env, intersect_tolerance,
1788 "AntiAlias ClipPath inset by 15.4",
1789 [=](const DlSetupContext& ctx) {
1790 ctx.canvas->ClipPath(path_clip, DlClipOp::kIntersect,
1791 true);
1792 }));
1793 RenderWith(testP, env, diff_tolerance,
1795 "Hard ClipPath Diff, inset by 15.4",
1796 [=](const DlSetupContext& ctx) {
1797 ctx.canvas->ClipPath(path_clip, DlClipOp::kDifference,
1798 false);
1799 })
1800 .with_diff_clip());
1801 // clang-format off
1802 }
1803
1804 enum class DirectoryStatus {
1805 kExisted,
1806 kCreated,
1807 kFailed,
1808 };
1809
1810 static DirectoryStatus CheckDir(const std::string& dir) {
1811 fml::UniqueFD ret =
1813 if (ret.is_valid()) {
1815 }
1816 ret =
1818 if (ret.is_valid()) {
1820 }
1821 FML_LOG(ERROR) << "Could not create directory (" << dir
1822 << ") for impeller failure images" << ", ret = " << ret.get()
1823 << ", errno = " << errno;
1825 }
1826
1828 std::string base_dir = "./failure_images";
1829 if (CheckDir(base_dir) == DirectoryStatus::kFailed) {
1830 return;
1831 }
1832 for (int i = 0; i < 10000; i++) {
1833 std::string sub_dir = std::to_string(i);
1834 while (sub_dir.length() < 4) {
1835 sub_dir = "0" + sub_dir;
1836 }
1837 std::string try_dir = base_dir + "/" + sub_dir;
1838 switch (CheckDir(try_dir)) {
1840 break;
1842 failure_image_directory_ = try_dir;
1843 return;
1845 return;
1846 }
1847 }
1848 FML_LOG(ERROR) << "Too many output directories for failure images";
1849 }
1850
1851 static void save_to_png(const RenderResult& result,
1852 const std::string& op_desc,
1853 const std::string& reason) {
1854 if (!save_failure_images_) {
1855 return;
1856 }
1857 if (failure_image_directory_.length() == 0) {
1859 if (failure_image_directory_.length() == 0) {
1860 save_failure_images_ = false;
1861 return;
1862 }
1863 }
1864
1865 std::string filename = failure_image_directory_ + "/";
1866 for (const char& ch : op_desc) {
1867 filename += (ch == ':' || ch == ' ') ? '_' : ch;
1868 }
1869 filename = filename + ".png";
1870 if (!result.pixel_data->write(filename)) {
1871 FML_LOG(ERROR) << "Could not write output to " << filename;
1872 }
1873 failure_image_filenames_.push_back(filename);
1874 FML_LOG(ERROR) << reason << ": " << filename;
1875 }
1876
1877 /// Run a suite of tests on the indicated parameters to determine if the
1878 /// output matches various expectations, including:
1879 /// - The rendering does not exceed the bounds computed by the DisplayList
1880 /// into which the operation was recorded.
1881 /// - If the parameters indicate an attribute (color, filter, stroke) or
1882 /// and environmental condition (clip, transform, save layer) which
1883 /// should affect the rendering, that it does affect the rendering,
1884 /// and conversely that it does not if it should not.
1885 ///
1886 /// testP - The parameters of the basic rendering operation being tested
1887 /// such as DrawRect, DrawPath, DrawText, etc.
1888 /// env - The parameters of the test environment for this suite of tests
1889 /// such as the Surface Provider that determines which backend is
1890 /// being used.
1891 /// tolerance_in - A first approximation of how tight the bounds might
1892 /// be for the indicated test and case parameters. Some
1893 /// issues that might require a higher bounds tolerance
1894 /// would include the fact that text glyphs do not consume
1895 /// most of their measured bounds, or that antialiasing is
1896 /// enabled which allows pixels outside the theoretical
1897 /// bounds of the operation's geometry to be rendered.
1898 /// caseP - The parameters under which the test is being rendered, which
1899 /// includes information such as transform, clip, and attributes.
1900 static void RenderWith(const TestParameters& testP,
1901 const RenderEnvironment& env,
1902 const BoundsTolerance& tolerance_in,
1903 const CaseParameters& caseP) {
1904 std::string test_name =
1905 ::testing::UnitTest::GetInstance()->current_test_info()->name();
1906 const std::string info =
1907 env.GetBackendName() + ": " + test_name + " (" + caseP.info() + ")";
1908 const DlColor bg = caseP.bg();
1909 RenderJobInfo base_info = {
1910 .bg = bg,
1911 };
1912
1913 // This is the basic rendering of the specified job. We combine the
1914 // rendering from the test, with the attribute and environment mutations
1915 // from the case
1916 DlJobRenderer dl_job(caseP.dl_setup(), //
1917 testP.dl_renderer(), //
1918 caseP.dl_restore());
1919 RenderResult dl_result = env.GetResult(base_info, dl_job);
1920
1921 ASSERT_EQ(dl_result.pixel_data->width(), kTestWidth) << info;
1922 ASSERT_EQ(dl_result.pixel_data->height(), kTestHeight) << info;
1923
1924 // We construct a display list from the rendering operations which will
1925 // estimate the bounds we expect from the operation, among other properties.
1926 const sk_sp<DisplayList> display_list =
1927 dl_job.MakeDisplayList(env, base_info);
1928
1929 // We now test the result of rendering the operations to verify that:
1930 // - it did render something (pixels touched > 0)
1931 // - no pixels were rendered outside the computed bounds (pixels_oob == 0)
1932 DlRect dl_bounds = display_list->GetBounds();
1933 bool success = checkPixels(dl_result, dl_bounds,
1934 info + " (DisplayList reference)", bg);
1935
1936 // Now we test if the operation should have rendered something different
1937 // compared to the default reference rendering that has no attributes
1938 // applied. Some operations ignore some attributes and so we need to
1939 // examine the test case properties to see if the rendering should or
1940 // should not match the default reference rendering.
1941 //
1942 // quickCompareToReference does both jobs (matches or does not match)
1943 // and we provide it with a boolean indicating if we expect the two
1944 // results to match and a string that prints out the expectation that
1945 // was violated.
1946 if (testP.should_match(env, caseP, dl_job.GetSetupPaint(), dl_job)) {
1947 success = quickCompareToReference(
1948 env.GetReferenceResult(), dl_result, true,
1949 info + " (attribute should not have effect)") &&
1950 success;
1951 } else {
1952 success = quickCompareToReference(
1953 env.GetReferenceResult(), dl_result, false,
1954 info + " (attribute should affect rendering)") &&
1955 success;
1956 }
1957
1958 if (save_failure_images_ && !success) {
1959 FML_LOG(ERROR) << "Rendering issue encountered for: " << *display_list;
1960 save_to_png(dl_result, info + " (Test Result)", "output saved in");
1961 save_to_png(env.GetReferenceResult(), info + " (Test Reference)",
1962 "compare to reference without attributes");
1963 }
1964
1965 // We now determine if the display list is compatible with distributing
1966 // a group opacity to its individual rendering operations. If the
1967 // display list believes that can be done, we double check by asking
1968 // the canvas to render the display list with a group opacity value
1969 // and then see if the results really match a faded version of the
1970 // original rendering results.
1971 //
1972 // If the display list does not promise that it can apply group opacity
1973 // then we do not verify that condition, we allow it the freedom to
1974 // answer "false" conservatively.
1975 if (display_list->can_apply_group_opacity()) {
1976 checkGroupOpacity(env, display_list, dl_result,
1977 info + " with Group Opacity", bg);
1978 }
1979 }
1980
1981 static bool fuzzyCompare(uint32_t pixel_a, uint32_t pixel_b, int fudge) {
1982 for (int i = 0; i < 32; i += 8) {
1983 int comp_a = (pixel_a >> i) & 0xff;
1984 int comp_b = (pixel_b >> i) & 0xff;
1985 if (std::abs(comp_a - comp_b) > fudge) {
1986 return false;
1987 }
1988 }
1989 return true;
1990 }
1991
1993 if (env.GetPixelFormat() == PixelFormat::k565) {
1994 return 9;
1995 }
1996 if (env.GetProvider()->GetBackendType() == BackendType::kSkiaOpenGL) {
1997 // OpenGL gets a little fuzzy at times. Still, "within 5" (aka +/-4)
1998 // for byte samples is not bad, though the other backends give +/-1
1999 return 5;
2000 }
2001 return 2;
2002 }
2003
2004 static void checkGroupOpacity(const RenderEnvironment& env,
2005 const sk_sp<DisplayList>& display_list,
2006 const RenderResult& ref_result,
2007 const std::string& info,
2008 DlColor bg) {
2009 DlScalar opacity = 128.0 / 255.0;
2010
2011 if (opacity > 0) {
2012 return;
2013 }
2014 DisplayListJobRenderer opacity_job(display_list);
2015 RenderJobInfo opacity_info = {
2016 .bg = bg,
2017 .opacity = opacity,
2018 };
2019 RenderResult group_opacity_result =
2020 env.GetResult(opacity_info, opacity_job);
2021
2022 ASSERT_EQ(group_opacity_result.pixel_data->width(), kTestWidth) << info;
2023 ASSERT_EQ(group_opacity_result.pixel_data->height(), kTestHeight) << info;
2024
2025 ASSERT_EQ(ref_result.pixel_data->width(), kTestWidth) << info;
2026 ASSERT_EQ(ref_result.pixel_data->height(), kTestHeight) << info;
2027
2028 int pixels_touched = 0;
2029 int pixels_different = 0;
2030 int max_diff = 0;
2031 // We need to allow some slight differences per component due to the
2032 // fact that rearranging discrete calculations can compound round off
2033 // errors. Off-by-2 is enough for 8 bit components, but for the 565
2034 // tests we allow at least 9 which is the maximum distance between
2035 // samples when converted to 8 bits. (You might think it would be a
2036 // max step of 8 converting 5 bits to 8 bits, but it is really
2037 // converting 31 steps to 255 steps with an average step size of
2038 // 8.23 - 24 of the steps are by 8, but 7 of them are by 9.)
2039 int fudge = groupOpacityFudgeFactor(env);
2040 for (uint32_t y = 0; y < kTestHeight; y++) {
2041 const uint32_t* ref_row = ref_result.pixel_data->addr32(0, y);
2042 const uint32_t* test_row = group_opacity_result.pixel_data->addr32(0, y);
2043 for (uint32_t x = 0; x < kTestWidth; x++) {
2044 uint32_t ref_pixel = ref_row[x];
2045 uint32_t test_pixel = test_row[x];
2046 if (ref_pixel != bg.argb() || test_pixel != bg.argb()) {
2047 pixels_touched++;
2048 for (int i = 0; i < 32; i += 8) {
2049 int ref_comp = (ref_pixel >> i) & 0xff;
2050 int bg_comp = (bg.argb() >> i) & 0xff;
2051 DlScalar faded_comp = bg_comp + (ref_comp - bg_comp) * opacity;
2052 int test_comp = (test_pixel >> i) & 0xff;
2053 if (std::abs(faded_comp - test_comp) > fudge) {
2054 int diff = std::abs(faded_comp - test_comp);
2055 if (max_diff < diff) {
2056 max_diff = diff;
2057 }
2058 pixels_different++;
2059 break;
2060 }
2061 }
2062 }
2063 }
2064 }
2065 ASSERT_GT(pixels_touched, 20) << info;
2066 if (pixels_different > 1) {
2067 FML_LOG(ERROR) << "max diff == " << max_diff << " for " << info;
2068 }
2069 ASSERT_LE(pixels_different, 1) << info;
2070 }
2071
2072 static bool checkPixels(const RenderResult& ref_result,
2073 const DlRect ref_bounds,
2074 const std::string& info,
2075 const DlColor bg = DlColor::kTransparent()) {
2076 uint32_t untouched = PremultipliedArgb(bg);
2077 int pixels_touched = 0;
2078 int pixels_oob = 0;
2079 DlIRect i_bounds = DlIRect::RoundOut(ref_bounds);
2080 EXPECT_EQ(ref_result.pixel_data->width(), kTestWidth) << info;
2081 EXPECT_EQ(ref_result.pixel_data->height(), kTestWidth) << info;
2082 for (uint32_t y = 0; y < kTestHeight; y++) {
2083 const uint32_t* ref_row = ref_result.pixel_data->addr32(0, y);
2084 for (uint32_t x = 0; x < kTestWidth; x++) {
2085 if (ref_row[x] != untouched) {
2086 pixels_touched++;
2087 if (!i_bounds.Contains(DlIPoint(x, y))) {
2088 pixels_oob++;
2089 }
2090 }
2091 }
2092 }
2093 EXPECT_EQ(pixels_oob, 0) << info;
2094 EXPECT_GT(pixels_touched, 0) << info;
2095 return pixels_oob == 0 && pixels_touched > 0;
2096 }
2097
2098 static int countModifiedTransparentPixels(const RenderResult& ref_result,
2099 const RenderResult& test_result) {
2100 int count = 0;
2101 for (uint32_t y = 0; y < kTestHeight; y++) {
2102 const uint32_t* ref_row = ref_result.pixel_data->addr32(0, y);
2103 const uint32_t* test_row = test_result.pixel_data->addr32(0, y);
2104 for (uint32_t x = 0; x < kTestWidth; x++) {
2105 if (ref_row[x] != test_row[x]) {
2106 if (ref_row[x] == 0) {
2107 count++;
2108 }
2109 }
2110 }
2111 }
2112 return count;
2113 }
2114
2115 static bool quickCompareToReference(const RenderResult& ref_result,
2116 const RenderResult& test_result,
2117 bool should_match,
2118 const std::string& info) {
2119 uint32_t w = test_result.pixel_data->width();
2120 uint32_t h = test_result.pixel_data->height();
2121 EXPECT_EQ(w, ref_result.pixel_data->width()) << info;
2122 EXPECT_EQ(h, ref_result.pixel_data->height()) << info;
2123 int pixels_different = 0;
2124 for (uint32_t y = 0; y < h; y++) {
2125 const uint32_t* ref_row = ref_result.pixel_data->addr32(0, y);
2126 const uint32_t* test_row = test_result.pixel_data->addr32(0, y);
2127 for (uint32_t x = 0; x < w; x++) {
2128 if (ref_row[x] != test_row[x]) {
2129 if (should_match && pixels_different < 5) {
2130 FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
2131 }
2132 pixels_different++;
2133 }
2134 }
2135 }
2136 if (should_match) {
2137 EXPECT_EQ(pixels_different, 0) << info;
2138 return pixels_different == 0;
2139 } else {
2140 EXPECT_NE(pixels_different, 0) << info;
2141 return pixels_different != 0;
2142 }
2143 }
2144
2145 static void compareToReference(const RenderResult& test_result,
2146 const RenderResult& ref_result,
2147 const std::string& info,
2148 const DlRect* bounds,
2149 const BoundsTolerance* tolerance,
2150 const DlColor bg,
2151 bool fuzzyCompares = false,
2152 uint32_t width = kTestWidth,
2153 uint32_t height = kTestHeight,
2154 bool printMismatches = false) {
2155 uint32_t untouched = PremultipliedArgb(bg);
2156 ASSERT_EQ(test_result.pixel_data->width(), width) << info;
2157 ASSERT_EQ(test_result.pixel_data->height(), height) << info;
2158 DlIRect i_bounds =
2159 bounds ? DlIRect::RoundOut(*bounds) : DlIRect::MakeWH(width, height);
2160
2161 int pixels_different = 0;
2162 int pixels_oob = 0;
2163 uint32_t min_x = width;
2164 uint32_t min_y = height;
2165 uint32_t max_x = 0;
2166 uint32_t max_y = 0;
2167 for (uint32_t y = 0; y < height; y++) {
2168 const uint32_t* ref_row = ref_result.pixel_data->addr32(0, y);
2169 const uint32_t* test_row = test_result.pixel_data->addr32(0, y);
2170 for (uint32_t x = 0; x < width; x++) {
2171 if (bounds && test_row[x] != untouched) {
2172 if (min_x > x) {
2173 min_x = x;
2174 }
2175 if (min_y > y) {
2176 min_y = y;
2177 }
2178 if (max_x <= x) {
2179 max_x = x + 1;
2180 }
2181 if (max_y <= y) {
2182 max_y = y + 1;
2183 }
2184 if (!i_bounds.Contains(DlIPoint(x, y))) {
2185 pixels_oob++;
2186 }
2187 }
2188 bool match = fuzzyCompares ? fuzzyCompare(test_row[x], ref_row[x], 1)
2189 : test_row[x] == ref_row[x];
2190 if (!match) {
2191 if (printMismatches && pixels_different < 5) {
2192 FML_LOG(ERROR) << "pix[" << x << ", " << y
2193 << "] mismatch: " << std::hex << test_row[x]
2194 << "(test) != (ref)" << ref_row[x] << std::dec;
2195 }
2196 pixels_different++;
2197 }
2198 }
2199 }
2200 if (pixels_oob > 0) {
2201 FML_LOG(ERROR) << "pix bounds["
2202 << DlIRect::MakeLTRB(min_x, min_y, max_x, max_y) //
2203 << "]";
2204 FML_LOG(ERROR) << "dl_bounds[" << bounds << "]";
2205 } else if (bounds) {
2206 showBoundsOverflow(info, i_bounds, tolerance, min_x, min_y, max_x, max_y);
2207 }
2208 ASSERT_EQ(pixels_oob, 0) << info;
2209 ASSERT_EQ(pixels_different, 0) << info;
2210 }
2211
2212 static void showBoundsOverflow(const std::string& info,
2213 DlIRect& bounds,
2214 const BoundsTolerance* tolerance,
2215 int pixLeft,
2216 int pixTop,
2217 int pixRight,
2218 int pixBottom) {
2219 int pad_left = std::max(0, pixLeft - bounds.GetLeft());
2220 int pad_top = std::max(0, pixTop - bounds.GetTop());
2221 int pad_right = std::max(0, bounds.GetRight() - pixRight);
2222 int pad_bottom = std::max(0, bounds.GetBottom() - pixBottom);
2223 DlIRect pix_bounds =
2224 DlIRect::MakeLTRB(pixLeft, pixTop, pixRight, pixBottom);
2225 DlISize pix_size = pix_bounds.GetSize();
2226 int pix_width = pix_size.width;
2227 int pix_height = pix_size.height;
2228 int worst_pad_x = std::max(pad_left, pad_right);
2229 int worst_pad_y = std::max(pad_top, pad_bottom);
2230 if (tolerance->overflows(pix_bounds, worst_pad_x, worst_pad_y)) {
2231 FML_LOG(ERROR) << "Computed bounds for " << info;
2232 FML_LOG(ERROR) << "pix bounds[" //
2233 << pixLeft << ", " << pixTop << " => " //
2234 << pixRight << ", " << pixBottom //
2235 << "]";
2236 FML_LOG(ERROR) << "dl_bounds[" << bounds << "]";
2237 FML_LOG(ERROR) << "Bounds overly conservative by up to " //
2238 << worst_pad_x << ", " << worst_pad_y //
2239 << " (" << (worst_pad_x * 100.0 / pix_width) //
2240 << "%, " << (worst_pad_y * 100.0 / pix_height) << "%)";
2241 int pix_area = pix_size.Area();
2242 int dl_area = bounds.Area();
2243 FML_LOG(ERROR) << "Total overflow area: " << (dl_area - pix_area) //
2244 << " (+" << (dl_area * 100.0 / pix_area - 100.0) //
2245 << "% larger)";
2246 FML_LOG(ERROR);
2247 }
2248 }
2249};
2250
2251std::string CanvasCompareTester::failure_image_directory_ = "";
2252bool CanvasCompareTester::save_failure_images_ = false;
2253std::vector<std::string> CanvasCompareTester::failure_image_filenames_;
2254
2256 BoundsTolerance().addAbsolutePadding(1, 1);
2257
2258// Eventually this bare bones testing::Test fixture will subsume the
2259// CanvasCompareTester and the TestParameters could then become just
2260// configuration calls made upon the fixture.
2261class DisplayListRendering : public ::testing::Test,
2262 protected DisplayListOpFlags {
2263 public:
2265
2266 static void SetUpTestSuite() {
2267 // Multiple test suites use this test base. Make sure that they don't
2268 // double-register the supported providers.
2269 test_backends_.clear();
2270
2271 std::vector<std::string> args = ::testing::internal::GetArgvs();
2272 fml::CommandLine command_line =
2273 fml::CommandLineFromIterators(args.begin(), args.end());
2274
2275 if (command_line.HasOption("--save-failure-images")) {
2277 }
2278
2279 std::vector<BackendType> enable_backends =
2280 ParseBackendList(command_line.GetOptionValues("enable"));
2281 for (BackendType backend : enable_backends) {
2282 AddProvider(backend);
2283 }
2284
2285 std::vector<BackendType> disable_backends =
2286 ParseBackendList(command_line.GetOptionValues("disable"));
2287 for (BackendType backend : disable_backends) {
2288 RemoveProvider(backend);
2289 }
2290
2291 if (GetTestBackends().empty()) {
2292 AddProvider(BackendType::kSkiaSoftware);
2293 }
2294
2295 std::string providers = "";
2296 for (BackendType back_end : GetTestBackends()) {
2297 providers += " " + DlSurfaceProvider::BackendName(back_end);
2298 }
2299 FML_LOG(INFO) << "Running tests on [" << providers << " ]";
2300 }
2301
2305
2306 static const std::vector<BackendType>& GetTestBackends() {
2307 return test_backends_;
2308 }
2309
2310 static std::unique_ptr<DlSurfaceProvider> GetProvider(BackendType type) {
2311 std::unique_ptr<DlSurfaceProvider> provider =
2313 if (provider == nullptr) {
2314 FML_LOG(ERROR) << "provider " << DlSurfaceProvider::BackendName(type)
2315 << " not supported (ignoring)";
2316 return nullptr;
2317 }
2318 provider->InitializeSurface(kTestWidth, kTestHeight,
2319 PixelFormat::kN32Premul);
2320 return provider;
2321 }
2322
2323 static void RenderAll(const TestParameters& params,
2324 const BoundsTolerance& tolerance =
2326 for (BackendType backend : test_backends_) {
2327 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(backend);
2328 CanvasCompareTester::RenderAll(provider, params, tolerance);
2329 }
2330 }
2331
2332 private:
2333 static std::vector<BackendType> test_backends_;
2334
2335 static bool AddProvider(BackendType type) {
2336 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(type);
2337 if (!provider) {
2338 // Error already reported by GetProvider.
2339 return false;
2340 }
2341 for (BackendType existing : test_backends_) {
2342 if (existing == type) {
2343 FML_LOG(ERROR) << "Backend " << provider->GetBackendName()
2344 << " already added";
2345 return false;
2346 }
2347 }
2348 test_backends_.push_back(type);
2349 return true;
2350 }
2351
2352 static bool RemoveProvider(BackendType type) {
2353 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(type);
2354 if (!provider) {
2355 // Error already reported by GetProvider.
2356 return false;
2357 }
2358 for (auto it = test_backends_.begin(); it < test_backends_.end(); it++) {
2359 if (*it == type) {
2360 test_backends_.erase(it);
2361 return true;
2362 }
2363 }
2364 FML_LOG(ERROR) << "Backend " << provider->GetBackendName()
2365 << " was not present to remove";
2366 return false;
2367 }
2368
2369 static std::vector<BackendType> ParseBackendList(
2370 const std::vector<std::string_view>& arg_list) {
2371 std::vector<BackendType> value_list;
2372 for (const std::string_view& name_list : arg_list) {
2373 std::vector<std::string> names = absl::StrSplit(name_list, ',');
2374 for (const std::string& name : names) {
2375 std::optional<BackendType> backend =
2377 if (backend.has_value()) {
2378 value_list.push_back(backend.value());
2379 } else {
2380 FML_LOG(ERROR) << "Unrecognized backend name: " << name;
2381 }
2382 }
2383 }
2384 return value_list;
2385 }
2386
2388};
2389
2390std::vector<BackendType> DisplayListRendering::test_backends_;
2391
2393 RenderAll( //
2395 [=](const DlRenderContext& ctx) { //
2396 ctx.canvas->DrawPaint(ctx.paint);
2397 },
2398 kDrawPaintFlags));
2399}
2400
2401TEST_F(DisplayListRendering, DrawOpaqueColor) {
2402 // We use a non-opaque color to avoid obliterating any backdrop filter output
2403 RenderAll( //
2405 [=](const DlRenderContext& ctx) {
2406 // DrawColor is not tested against attributes because it is supposed
2407 // to ignore them. So, if the paint has an alpha, it is because we
2408 // are doing a saveLayer+backdrop test and we need to not flood over
2409 // the backdrop output with a solid color. So, we transfer the alpha
2410 // from the paint for that case only.
2411 ctx.canvas->DrawColor(
2412 DlColor::kMagenta().withAlpha(ctx.paint.getAlpha()));
2413 },
2414 kDrawColorFlags));
2415}
2416
2418 // We use a non-opaque color to avoid obliterating any backdrop filter output
2419 RenderAll( //
2421 [=](const DlRenderContext& ctx) {
2422 ctx.canvas->DrawColor(DlColor(0x7FFF00FF));
2423 },
2424 kDrawColorFlags));
2425}
2426
2427TEST_F(DisplayListRendering, DrawDiagonalLines) {
2432 // Adding some edge to edge diagonals that run through the points about
2433 // 16 units in from the center of that edge.
2434 // Adding some edge center to edge center diagonals to better fill
2435 // out the RRect Clip so bounds checking sees less empty bounds space.
2440
2441 RenderAll( //
2443 [=](const DlRenderContext& ctx) { //
2444 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2445 ctx.canvas->DrawLine(p3, p4, ctx.paint);
2446 ctx.canvas->DrawLine(p5, p6, ctx.paint);
2447 ctx.canvas->DrawLine(p7, p8, ctx.paint);
2448 },
2449 kDrawLineFlags)
2450 .set_draw_line());
2451}
2452
2453TEST_F(DisplayListRendering, DrawHorizontalLines) {
2460
2461 RenderAll( //
2463 [=](const DlRenderContext& ctx) { //
2464 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2465 ctx.canvas->DrawLine(p3, p4, ctx.paint);
2466 ctx.canvas->DrawLine(p5, p6, ctx.paint);
2467 },
2468 kDrawHVLineFlags)
2469 .set_draw_line()
2471}
2472
2473TEST_F(DisplayListRendering, DrawVerticalLines) {
2480
2481 RenderAll( //
2483 [=](const DlRenderContext& ctx) { //
2484 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2485 ctx.canvas->DrawLine(p3, p4, ctx.paint);
2486 ctx.canvas->DrawLine(p5, p6, ctx.paint);
2487 },
2488 kDrawHVLineFlags)
2489 .set_draw_line()
2491}
2492
2493TEST_F(DisplayListRendering, DrawDiagonalDashedLines) {
2498 // Adding some edge to edge diagonals that run through the points about
2499 // 16 units in from the center of that edge.
2500 // Adding some edge center to edge center diagonals to better fill
2501 // out the RRect Clip so bounds checking sees less empty bounds space.
2506
2507 // Full diagonals are 100x100 which are 140 in length
2508 // Dashing them with 25 on, 5 off means that the last
2509 // dash goes from 120 to 145 which means both ends of the
2510 // diagonals will be in an "on" dash for maximum bounds
2511
2512 // Edge to edge diagonals are 50x50 which are 70 in length
2513 // Dashing them with 25 on, 5 off means that the last
2514 // dash goes from 60 to 85 which means both ends of the
2515 // edge diagonals will be in a dash segment
2516
2517 RenderAll( //
2519 [=](const DlRenderContext& ctx) { //
2520 ctx.canvas->DrawDashedLine(p1, p2, 25.0f, 5.0f, ctx.paint);
2521 ctx.canvas->DrawDashedLine(p3, p4, 25.0f, 5.0f, ctx.paint);
2522 ctx.canvas->DrawDashedLine(p5, p6, 25.0f, 5.0f, ctx.paint);
2523 ctx.canvas->DrawDashedLine(p7, p8, 25.0f, 5.0f, ctx.paint);
2524 },
2525 kDrawLineFlags)
2526 .set_draw_line());
2527}
2528
2530 // Bounds are offset by 0.5 pixels to induce AA
2531 DlRect rect = kRenderBounds.Shift(0.5f, 0.5f);
2532
2533 RenderAll( //
2535 [=](const DlRenderContext& ctx) { //
2536 ctx.canvas->DrawRect(rect, ctx.paint);
2537 },
2538 kDrawRectFlags));
2539}
2540
2542 DlRect rect = kRenderBounds.Expand(0, -10);
2543
2544 RenderAll( //
2546 [=](const DlRenderContext& ctx) { //
2547 ctx.canvas->DrawOval(rect, ctx.paint);
2548 },
2549 kDrawOvalFlags));
2550}
2551
2553 DlPoint center = kRenderBounds.GetCenter();
2554
2555 RenderAll( //
2557 [=](const DlRenderContext& ctx) { //
2558 ctx.canvas->DrawCircle(center, kRenderRadius, ctx.paint);
2559 },
2560 kDrawCircleFlags));
2561}
2562
2567
2568 RenderAll( //
2570 [=](const DlRenderContext& ctx) { //
2571 ctx.canvas->DrawRoundRect(rrect, ctx.paint);
2572 },
2573 kDrawRRectFlags));
2574}
2575
2576TEST_F(DisplayListRendering, DrawDiffRoundRect) {
2580 DlRect inner_bounds = kRenderBounds.Expand(-30.0f, -30.0f);
2581 DlRoundRect inner = DlRoundRect::MakeRectXY(inner_bounds, //
2584
2585 RenderAll( //
2587 [=](const DlRenderContext& ctx) { //
2588 ctx.canvas->DrawDiffRoundRect(outer, inner, ctx.paint);
2589 },
2590 kDrawDRRectFlags));
2591}
2592
2594 DlPathBuilder path_builder;
2595
2596 // unclosed lines to show some caps
2597 path_builder.MoveTo(DlPoint(kRenderLeft + 15, kRenderTop + 15));
2598 path_builder.LineTo(DlPoint(kRenderRight - 15, kRenderBottom - 15));
2599 path_builder.MoveTo(DlPoint(kRenderLeft + 15, kRenderBottom - 15));
2600 path_builder.LineTo(DlPoint(kRenderRight - 15, kRenderTop + 15));
2601
2602 path_builder.AddRect(kRenderBounds);
2603
2604 // miter diamonds horizontally and vertically to show miters
2605 path_builder.MoveTo(kVerticalMiterDiamondPoints[0]);
2606 for (int i = 1; i < kVerticalMiterDiamondPointCount; i++) {
2607 path_builder.LineTo(kVerticalMiterDiamondPoints[i]);
2608 }
2609 path_builder.Close();
2610 path_builder.MoveTo(kHorizontalMiterDiamondPoints[0]);
2611 for (int i = 1; i < kHorizontalMiterDiamondPointCount; i++) {
2612 path_builder.LineTo(kHorizontalMiterDiamondPoints[i]);
2613 }
2614 path_builder.Close();
2615
2616 DlPath path = path_builder.TakePath();
2617
2618 RenderAll( //
2620 [=](const DlRenderContext& ctx) { //
2621 ctx.canvas->DrawPath(path, ctx.paint);
2622 },
2623 kDrawPathFlags)
2624 .set_draw_path());
2625}
2626
2628 RenderAll( //
2630 [=](const DlRenderContext& ctx) { //
2631 ctx.canvas->DrawArc(kRenderBounds, 60, 330, false, ctx.paint);
2632 },
2633 kDrawArcNoCenterFlags));
2634}
2635
2637 // Center arcs that inscribe nearly a whole circle except for a small
2638 // arc extent gap have 2 angles that may appear or disappear at the
2639 // various miter limits tested (0, 4, and 10).
2640 // The center angle here is 12 degrees which shows a miter
2641 // at limit=10, but not 0 or 4.
2642 // The arcs at the corners where it turns in towards the
2643 // center show miters at 4 and 10, but not 0.
2644 // Limit == 0, neither corner does a miter
2645 // Limit == 4, only the edge "turn-in" corners miter
2646 // Limit == 10, edge and center corners all miter
2647 RenderAll( //
2649 [=](const DlRenderContext& ctx) { //
2650 ctx.canvas->DrawArc(kRenderBounds, 60, 360 - 12, true, ctx.paint);
2651 },
2652 kDrawArcWithCenterFlags)
2653 .set_draw_arc_center());
2654}
2655
2656TEST_F(DisplayListRendering, DrawPointsAsPoints) {
2657 // The +/- 16 points are designed to fall just inside the clips
2658 // that are tested against so we avoid lots of undrawn pixels
2659 // in the accumulated bounds.
2660 const DlScalar x0 = kRenderLeft;
2661 const DlScalar x1 = kRenderLeft + 16;
2662 const DlScalar x2 = (kRenderLeft + kRenderCenterX) * 0.5;
2663 const DlScalar x3 = kRenderCenterX + 0.1;
2664 const DlScalar x4 = (kRenderRight + kRenderCenterX) * 0.5;
2665 const DlScalar x5 = kRenderRight - 16;
2666 const DlScalar x6 = kRenderRight - 1;
2667
2668 const DlScalar y0 = kRenderTop;
2669 const DlScalar y1 = kRenderTop + 16;
2670 const DlScalar y2 = (kRenderTop + kRenderCenterY) * 0.5;
2671 const DlScalar y3 = kRenderCenterY + 0.1;
2672 const DlScalar y4 = (kRenderBottom + kRenderCenterY) * 0.5;
2673 const DlScalar y5 = kRenderBottom - 16;
2674 const DlScalar y6 = kRenderBottom - 1;
2675
2676 // clang-format off
2677 const DlPoint points[] = {
2678 {x0, y0}, {x1, y0}, {x2, y0}, {x3, y0}, {x4, y0}, {x5, y0}, {x6, y0},
2679 {x0, y1}, {x1, y1}, {x2, y1}, {x3, y1}, {x4, y1}, {x5, y1}, {x6, y1},
2680 {x0, y2}, {x1, y2}, {x2, y2}, {x3, y2}, {x4, y2}, {x5, y2}, {x6, y2},
2681 {x0, y3}, {x1, y3}, {x2, y3}, {x3, y3}, {x4, y3}, {x5, y3}, {x6, y3},
2682 {x0, y4}, {x1, y4}, {x2, y4}, {x3, y4}, {x4, y4}, {x5, y4}, {x6, y4},
2683 {x0, y5}, {x1, y5}, {x2, y5}, {x3, y5}, {x4, y5}, {x5, y5}, {x6, y5},
2684 {x0, y6}, {x1, y6}, {x2, y6}, {x3, y6}, {x4, y6}, {x5, y6}, {x6, y6},
2685 };
2686 // clang-format on
2687 const int count = sizeof(points) / sizeof(points[0]);
2688
2689 RenderAll( //
2691 [=](const DlRenderContext& ctx) {
2693 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
2694 },
2695 kDrawPointsAsPointsFlags)
2696 .set_draw_line()
2698}
2699
2700TEST_F(DisplayListRendering, DrawPointsAsLines) {
2701 const DlScalar x0 = kRenderLeft + 1;
2702 const DlScalar x1 = kRenderLeft + 16;
2703 const DlScalar x2 = kRenderRight - 16;
2704 const DlScalar x3 = kRenderRight - 1;
2705
2706 const DlScalar y0 = kRenderTop;
2707 const DlScalar y1 = kRenderTop + 16;
2708 const DlScalar y2 = kRenderBottom - 16;
2709 const DlScalar y3 = kRenderBottom - 1;
2710
2711 // clang-format off
2712 const DlPoint points[] = {
2713 // Outer box
2714 {x0, y0}, {x3, y0},
2715 {x3, y0}, {x3, y3},
2716 {x3, y3}, {x0, y3},
2717 {x0, y3}, {x0, y0},
2718
2719 // Diagonals
2720 {x0, y0}, {x3, y3}, {x3, y0}, {x0, y3},
2721
2722 // Inner box
2723 {x1, y1}, {x2, y1},
2724 {x2, y1}, {x2, y2},
2725 {x2, y2}, {x1, y2},
2726 {x1, y2}, {x1, y1},
2727 };
2728 // clang-format on
2729
2730 const int count = sizeof(points) / sizeof(points[0]);
2731 ASSERT_TRUE((count & 1) == 0);
2732 RenderAll( //
2734 [=](const DlRenderContext& ctx) {
2736 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
2737 },
2738 kDrawPointsAsLinesFlags));
2739}
2740
2741TEST_F(DisplayListRendering, DrawPointsAsPolygon) {
2742 const DlPoint points1[] = {
2743 // RenderBounds box with a diamond
2749
2755 };
2756 const int count1 = sizeof(points1) / sizeof(points1[0]);
2757
2758 RenderAll( //
2760 [=](const DlRenderContext& ctx) {
2762 ctx.canvas->DrawPoints(mode, count1, points1, ctx.paint);
2763 },
2764 kDrawPointsAsPolygonFlags));
2765}
2766
2767TEST_F(DisplayListRendering, DrawVerticesWithColors) {
2768 // Cover as many sides of the box with only 6 vertices:
2769 // +----------+
2770 // |xxxxxxxxxx|
2771 // | xxxxxx|
2772 // | xxx|
2773 // |xxx |
2774 // |xxxxxx |
2775 // |xxxxxxxxxx|
2776 // +----------|
2777 const DlPoint pts[6] = {
2778 // Upper-Right corner, full top, half right coverage
2782 // Lower-Left corner, full bottom, half left coverage
2786 };
2787 const DlColor dl_colors[6] = {
2790 };
2791 const std::shared_ptr<DlVertices> dl_vertices =
2792 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, nullptr, dl_colors);
2793
2794 RenderAll( //
2796 [=](const DlRenderContext& ctx) {
2797 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
2798 ctx.paint);
2799 },
2800 kDrawVerticesFlags));
2801}
2802
2803TEST_F(DisplayListRendering, DrawVerticesWithImage) {
2804 // Cover as many sides of the box with only 6 vertices:
2805 // +----------+
2806 // |xxxxxxxxxx|
2807 // | xxxxxx|
2808 // | xxx|
2809 // |xxx |
2810 // |xxxxxx |
2811 // |xxxxxxxxxx|
2812 // +----------|
2813 const DlPoint pts[6] = {
2814 // Upper-Right corner, full top, half right coverage
2818 // Lower-Left corner, full bottom, half left coverage
2822 };
2823 const DlPoint tex[6] = {
2824 DlPoint(kRenderWidth / 2.0, 0),
2828 DlPoint(0, 0),
2830 };
2831 const std::shared_ptr<DlVertices> dl_vertices =
2832 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, tex, nullptr);
2833
2834 RenderAll( //
2836 [=](const DlRenderContext& ctx) { //
2837 DlPaint v_paint = ctx.paint;
2838 if (v_paint.getColorSource() == nullptr) {
2839 v_paint.setColorSource(MakeColorSource(ctx.env.GetTestImage()));
2840 }
2841 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
2842 v_paint);
2843 },
2844 kDrawVerticesFlags));
2845}
2846
2847TEST_F(DisplayListRendering, DrawImageNearest) {
2848 RenderAll( //
2850 [=](const DlRenderContext& ctx) {
2851 ctx.canvas->DrawImage(
2854 },
2855 kDrawImageWithPaintFlags));
2856}
2857
2858TEST_F(DisplayListRendering, DrawImageNearestNoPaint) {
2859 RenderAll( //
2861 [=](const DlRenderContext& ctx) {
2862 ctx.canvas->DrawImage(ctx.env.GetTestImage(),
2865 },
2866 kDrawImageFlags));
2867}
2868
2869TEST_F(DisplayListRendering, DrawImageLinear) {
2870 RenderAll( //
2872 [=](const DlRenderContext& ctx) {
2873 ctx.canvas->DrawImage(ctx.env.GetTestImage(),
2876 },
2877 kDrawImageWithPaintFlags));
2878}
2879
2880TEST_F(DisplayListRendering, DrawImageRectNearest) {
2882 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2883 RenderAll( //
2885 [=](const DlRenderContext& ctx) {
2886 ctx.canvas->DrawImageRect(ctx.env.GetTestImage(), src, dst,
2889 },
2890 kDrawImageRectWithPaintFlags));
2891}
2892
2893TEST_F(DisplayListRendering, DrawImageRectNearestNoPaint) {
2895 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2896 RenderAll( //
2898 [=](const DlRenderContext& ctx) {
2899 ctx.canvas->DrawImageRect(ctx.env.GetTestImage(), src, dst,
2902 },
2903 kDrawImageRectFlags));
2904}
2905
2906TEST_F(DisplayListRendering, DrawImageRectLinear) {
2908 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2909 RenderAll( //
2911 [=](const DlRenderContext& ctx) { //
2912 ctx.canvas->DrawImageRect(ctx.env.GetTestImage(), src, dst,
2915 },
2916 kDrawImageRectWithPaintFlags));
2917}
2918
2919TEST_F(DisplayListRendering, DrawImageNineNearest) {
2921 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2922 RenderAll( //
2924 [=](const DlRenderContext& ctx) {
2925 ctx.canvas->DrawImageNine(ctx.env.GetTestImage(), src, dst,
2927 },
2928 kDrawImageNineWithPaintFlags));
2929}
2930
2931TEST_F(DisplayListRendering, DrawImageNineNearestNoPaint) {
2933 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2934 RenderAll( //
2936 [=](const DlRenderContext& ctx) {
2937 ctx.canvas->DrawImageNine(ctx.env.GetTestImage(), src, dst,
2938 DlFilterMode::kNearest, nullptr);
2939 },
2940 kDrawImageNineFlags));
2941}
2942
2943TEST_F(DisplayListRendering, DrawImageNineLinear) {
2945 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
2946 RenderAll( //
2948 [=](const DlRenderContext& ctx) {
2949 ctx.canvas->DrawImageNine(ctx.env.GetTestImage(), src, dst,
2951 },
2952 kDrawImageNineWithPaintFlags));
2953}
2954
2955TEST_F(DisplayListRendering, DrawAtlasNearest) {
2956 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
2957 DlScalar relative_right,
2958 DlScalar relative_bottom) -> DlRect {
2959 return DlRect::MakeLTRB(
2960 kRenderWidth * relative_left, kRenderHeight * relative_top,
2961 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
2962 };
2963
2964 const DlRSTransform dl_xform[] = {
2965 // clang-format off
2966 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
2967 { 0.0f, 1.2f, kRenderRight, kRenderTop},
2968 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
2969 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
2970 // clang-format on
2971 };
2972 const DlRect tex[] = {
2973 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
2974 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
2975 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
2976 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
2977 };
2978 const DlColor dl_colors[] = {
2983 };
2985 RenderAll( //
2987 [=](const DlRenderContext& ctx) {
2988 ctx.canvas->DrawAtlas(ctx.env.GetTestImage(), dl_xform, tex,
2989 dl_colors, 4, DlBlendMode::kSrcOver,
2990 dl_sampling, nullptr, &ctx.paint);
2991 },
2992 kDrawAtlasWithPaintFlags));
2993}
2994
2995TEST_F(DisplayListRendering, DrawAtlasNearestNoPaint) {
2996 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
2997 DlScalar relative_right,
2998 DlScalar relative_bottom) -> DlRect {
2999 return DlRect::MakeLTRB(
3000 kRenderWidth * relative_left, kRenderHeight * relative_top,
3001 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
3002 };
3003
3004 const DlRSTransform dl_xform[] = {
3005 // clang-format off
3006 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3007 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3008 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3009 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3010 // clang-format on
3011 };
3012 const DlRect tex[] = {
3013 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
3014 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
3015 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
3016 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
3017 };
3018 const DlColor dl_colors[] = {
3023 };
3025 RenderAll( //
3027 [=](const DlRenderContext& ctx) {
3028 ctx.canvas->DrawAtlas(ctx.env.GetTestImage(), dl_xform, tex,
3029 dl_colors, 4, DlBlendMode::kSrcOver,
3030 dl_sampling, nullptr, nullptr);
3031 },
3032 kDrawAtlasFlags));
3033}
3034
3035TEST_F(DisplayListRendering, DrawAtlasLinear) {
3036 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
3037 DlScalar relative_right,
3038 DlScalar relative_bottom) -> DlRect {
3039 return DlRect::MakeLTRB(
3040 kRenderWidth * relative_left, kRenderHeight * relative_top,
3041 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
3042 };
3043
3044 const DlRSTransform dl_xform[] = {
3045 // clang-format off
3046 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3047 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3048 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3049 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3050 // clang-format on
3051 };
3052 const DlRect tex[] = {
3053 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
3054 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
3055 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
3056 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
3057 };
3058 const DlColor dl_colors[] = {
3063 };
3064 const DlImageSampling dl_sampling = DlImageSampling::kLinear;
3065 RenderAll( //
3067 [=](const DlRenderContext& ctx) {
3068 ctx.canvas->DrawAtlas(ctx.env.GetTestImage(), dl_xform, tex,
3069 dl_colors, 2, DlBlendMode::kSrcOver,
3070 dl_sampling, nullptr, &ctx.paint);
3071 },
3072 kDrawAtlasWithPaintFlags));
3073}
3074
3075sk_sp<DisplayList> makeTestDisplayList() {
3076 DisplayListBuilder builder;
3077 DlPaint paint;
3079 paint.setColor(DlColor(SK_ColorRED));
3082 paint);
3083 paint.setColor(DlColor(SK_ColorBLUE));
3086 paint);
3087 paint.setColor(DlColor(SK_ColorGREEN));
3090 paint);
3091 paint.setColor(DlColor(SK_ColorYELLOW));
3094 paint);
3095 return builder.Build();
3096}
3097
3098TEST_F(DisplayListRendering, DrawDisplayList) {
3099 sk_sp<DisplayList> display_list = makeTestDisplayList();
3100 RenderAll( //
3102 [=](const DlRenderContext& ctx) { //
3103 ctx.canvas->DrawDisplayList(display_list);
3104 },
3105 kDrawDisplayListFlags)
3106 .set_draw_display_list());
3107}
3108
3110 // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the
3111 // performance overlay can use Fuchsia's font manager instead of the empty
3112 // default.
3113#if defined(OS_FUCHSIA)
3114 GTEST_SKIP() << "Rendering comparisons require a valid default font manager";
3115#else
3116 DlScalar render_y_1_3 = kRenderTop + kRenderHeight * 0.3;
3117 DlScalar render_y_2_3 = kRenderTop + kRenderHeight * 0.6;
3118 RenderAll( //
3120 [=](const DlRenderContext& ctx) {
3121 DlPaint paint = ctx.paint;
3123 render_y_1_3, paint);
3125 render_y_2_3, paint);
3127 kRenderBottom, paint);
3128 },
3129 kDrawTextFlags)
3130 .set_draw_text_blob(),
3131 // From examining the bounds differential for the "Default" case, the
3132 // SkTextBlob adds a padding of ~32 on the left, ~30 on the right,
3133 // ~12 on top and ~8 on the bottom, so we add 33h & 13v allowed
3134 // padding to the tolerance
3136#endif // OS_FUCHSIA
3137}
3138
3140 DlPathBuilder path_builder;
3143 kRenderRight - 10, kRenderBottom - 20),
3145 DlPath path = path_builder.TakePath();
3146
3147 const DlColor color = DlColor::kDarkGrey();
3148 const DlScalar elevation = 7;
3149
3150 RenderAll( //
3152 [=](const DlRenderContext& ctx) { //
3153 ctx.canvas->DrawShadow(path, color, elevation, false, 1.0);
3154 },
3155 kDrawShadowFlags),
3156 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3157}
3158
3159TEST_F(DisplayListRendering, DrawShadowTransparentOccluder) {
3160 DlPathBuilder path_builder;
3163 kRenderRight - 10, kRenderBottom - 20),
3165 DlPath path = path_builder.TakePath();
3166
3167 const DlColor color = DlColor::kDarkGrey();
3168 const DlScalar elevation = 7;
3169
3170 RenderAll( //
3172 [=](const DlRenderContext& ctx) { //
3173 ctx.canvas->DrawShadow(path, color, elevation, true, 1.0);
3174 },
3175 kDrawShadowFlags),
3176 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3177}
3178
3180 DlPathBuilder path_builder;
3183 kRenderRight - 10, kRenderBottom - 20),
3185 DlPath path = path_builder.TakePath();
3186
3187 const DlColor color = DlColor::kDarkGrey();
3188 const DlScalar elevation = 7;
3189
3190 RenderAll( //
3192 [=](const DlRenderContext& ctx) { //
3193 ctx.canvas->DrawShadow(path, color, elevation, false, 1.5);
3194 },
3195 kDrawShadowFlags),
3196 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3197}
3198
3199TEST_F(DisplayListRendering, SaveLayerClippedContentStillFilters) {
3200 // draw rect is just outside of render bounds on the right
3201 const DlRect draw_rect = DlRect::MakeLTRB( //
3202 kRenderRight + 1, //
3203 kRenderTop, //
3205 kRenderBottom //
3206 );
3207 TestParameters test_params(
3208 [=](const DlRenderContext& ctx) {
3209 std::shared_ptr<DlImageFilter> layer_filter =
3211 DlPaint layer_paint;
3212 layer_paint.setImageFilter(layer_filter);
3213 ctx.canvas->Save();
3215 ctx.canvas->SaveLayer(kTestBounds2, &layer_paint);
3216 ctx.canvas->DrawRect(draw_rect, ctx.paint);
3217 ctx.canvas->Restore();
3218 ctx.canvas->Restore();
3219 },
3220 kSaveLayerWithPaintFlags);
3221 CaseParameters case_params("Filtered SaveLayer with clipped content");
3222 BoundsTolerance tolerance = BoundsTolerance().addAbsolutePadding(6.0f, 6.0f);
3223
3224 for (BackendType back_end : GetTestBackends()) {
3225 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3226 RenderEnvironment env = RenderEnvironment::MakeN32(provider.get());
3227 env.InitializeReference(kEmptyDlSetup, test_params.dl_renderer());
3228
3229 CanvasCompareTester::RenderWith(test_params, env, tolerance, case_params);
3230 }
3231}
3232
3233TEST_F(DisplayListRendering, SaveLayerConsolidation) {
3234 float commutable_color_matrix[]{
3235 // clang-format off
3236 0, 1, 0, 0, 0,
3237 0, 0, 1, 0, 0,
3238 1, 0, 0, 0, 0,
3239 0, 0, 0, 1, 0,
3240 // clang-format on
3241 };
3242 float non_commutable_color_matrix[]{
3243 // clang-format off
3244 0, 1, 0, .1, 0,
3245 0, 0, 1, .1, 0,
3246 1, 0, 0, .1, 0,
3247 0, 0, 0, .7, 0,
3248 // clang-format on
3249 };
3250 DlMatrix contract_matrix;
3251 contract_matrix.Translate({kRenderCenterX, kRenderCenterY});
3252 contract_matrix.Scale({0.9f, 0.9f});
3253 contract_matrix.Translate({kRenderCenterX, kRenderCenterY});
3254
3255 std::vector<DlScalar> opacities = {
3256 0,
3257 0.5f,
3258 SK_Scalar1,
3259 };
3260 std::vector<std::shared_ptr<const DlColorFilter>> color_filters = {
3261 DlColorFilter::MakeBlend(DlColor::kCyan(), DlBlendMode::kSrcATop),
3262 DlColorFilter::MakeMatrix(commutable_color_matrix),
3263 DlColorFilter::MakeMatrix(non_commutable_color_matrix),
3266 };
3267 std::vector<std::shared_ptr<DlImageFilter>> image_filters = {
3269 DlImageFilter::MakeDilate(5.0f, 5.0f),
3270 DlImageFilter::MakeErode(5.0f, 5.0f),
3272 };
3273
3274 auto render_content = [](DisplayListBuilder& builder) -> void {
3275 builder.DrawRect(DlRect::MakeLTRB(kRenderLeft, kRenderTop, //
3278 builder.DrawRect(DlRect::MakeLTRB(kRenderCenterX, kRenderTop, //
3281 builder.DrawRect(DlRect::MakeLTRB(kRenderLeft, kRenderCenterY, //
3284 builder.DrawRect(DlRect::MakeLTRB(kRenderCenterX, kRenderCenterY, //
3286 DlPaint(DlColor::kRed().modulateOpacity(0.5f)));
3287 };
3288
3289 // clang-format off
3290 // The following section gets re-formatted on every commit even if it
3291 // doesn't change.
3292 auto test_attributes_env =
3293 [render_content](DlPaint& paint1, DlPaint& paint2,
3294 const DlPaint& paint_both, bool same, bool rev_same,
3295 const std::string& desc1, const std::string& desc2,
3296 const RenderEnvironment* env) -> void {
3297 DisplayListBuilder nested_builder;
3298 nested_builder.SaveLayer(kTestBounds2, &paint1);
3299 nested_builder.SaveLayer(kTestBounds2, &paint2);
3300 render_content(nested_builder);
3301 RenderResult nested_results = env->GetResult(nested_builder.Build());
3302
3303 DisplayListBuilder reverse_builder;
3304 reverse_builder.SaveLayer(kTestBounds2, &paint2);
3305 reverse_builder.SaveLayer(kTestBounds2, &paint1);
3306 render_content(reverse_builder);
3307 RenderResult reverse_results = env->GetResult(reverse_builder.Build());
3308
3309 DisplayListBuilder combined_builder;
3310 combined_builder.SaveLayer(kTestBounds2, &paint_both);
3311 render_content(combined_builder);
3312 RenderResult combined_results =
3313 env->GetResult(combined_builder.Build());
3314
3315 // Set this boolean to true to test if combinations that are marked
3316 // as incompatible actually are compatible despite our predictions.
3317 // Some of the combinations that we treat as incompatible actually
3318 // are compatible with swapping the order of the operations, but
3319 // it would take a bit of new infrastructure to really identify
3320 // those combinations. The only hard constraint to test here is
3321 // when we claim that they are compatible and they aren't.
3322 const bool always = false;
3323
3324 // In some circumstances, Skia can combine image filter evaluations
3325 // and elide a renderpass. In this case rounding and precision of inputs
3326 // to color filters may cause the output to differ by 1.
3327 if (always || same) {
3329 nested_results, combined_results,
3330 "nested " + desc1 + " then " + desc2, /*bounds=*/nullptr,
3331 /*tolerance=*/nullptr, DlColor::kTransparent(),
3332 /*fuzzyCompares=*/true, combined_results.pixel_data->width(),
3333 combined_results.pixel_data->height(), /*printMismatches=*/true);
3334 }
3335 if (always || rev_same) {
3337 reverse_results, combined_results,
3338 "nested " + desc2 + " then " + desc1, /*bounds=*/nullptr,
3339 /*tolerance=*/nullptr, DlColor::kTransparent(),
3340 /*fuzzyCompares=*/true, combined_results.pixel_data->width(),
3341 combined_results.pixel_data->height(), /*printMismatches=*/true);
3342 }
3343 };
3344 // clang-format on
3345
3346 auto test_attributes = [test_attributes_env](
3347 DlPaint& paint1, DlPaint& paint2,
3348 const DlPaint& paint_both, bool same,
3349 bool rev_same, const std::string& desc1,
3350 const std::string& desc2) -> void {
3351 for (BackendType back_end : GetTestBackends()) {
3352 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3353 std::unique_ptr<RenderEnvironment> env =
3354 std::make_unique<RenderEnvironment>(provider.get(),
3355 PixelFormat::kN32Premul);
3356 test_attributes_env(paint1, paint2, paint_both, //
3357 same, rev_same, desc1, desc2, env.get());
3358 }
3359 };
3360
3361 // CF then Opacity should always work.
3362 // The reverse sometimes works.
3363 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
3364 std::shared_ptr<const DlColorFilter>& color_filter = color_filters[cfi];
3365 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
3366 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
3367
3368 for (size_t oi = 0; oi < opacities.size(); oi++) {
3369 DlScalar opacity = opacities[oi];
3370 std::string op_desc = "opacity " + std::to_string(opacity);
3371 DlPaint nested_paint2 = DlPaint().setOpacity(opacity);
3372
3373 DlPaint combined_paint = nested_paint1;
3374 combined_paint.setOpacity(opacity);
3375
3376 bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 ||
3377 color_filter->can_commute_with_opacity();
3378
3379 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
3380 op_then_cf_works, cf_desc, op_desc);
3381 }
3382 }
3383
3384 // Opacity then IF should always work.
3385 // The reverse can also work for some values of opacity.
3386 // The reverse should also theoretically work for some IFs, but we
3387 // get some rounding errors that are more than just trivial.
3388 for (size_t oi = 0; oi < opacities.size(); oi++) {
3389 DlScalar opacity = opacities[oi];
3390 std::string op_desc = "opacity " + std::to_string(opacity);
3391 DlPaint nested_paint1 = DlPaint().setOpacity(opacity);
3392
3393 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
3394 std::shared_ptr<DlImageFilter>& image_filter = image_filters[ifi];
3395 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
3396 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
3397
3398 DlPaint combined_paint = nested_paint1;
3399 combined_paint.setImageFilter(image_filter);
3400
3401 bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0;
3402 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
3403 if_then_op_works, op_desc, if_desc);
3404 }
3405 }
3406
3407 // CF then IF should always work.
3408 // The reverse might work, but we lack the infrastructure to check it.
3409 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
3410 std::shared_ptr<const DlColorFilter>& color_filter = color_filters[cfi];
3411 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
3412 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
3413
3414 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
3415 std::shared_ptr<DlImageFilter>& image_filter = image_filters[ifi];
3416 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
3417 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
3418
3419 DlPaint combined_paint = nested_paint1;
3420 combined_paint.setImageFilter(image_filter);
3421
3422 test_attributes(nested_paint1, nested_paint2, combined_paint, true, false,
3423 cf_desc, if_desc);
3424 }
3425 }
3426}
3427
3428TEST_F(DisplayListRendering, MatrixColorFilterModifyTransparencyCheck) {
3429 auto test_matrix = [](int element, DlScalar value) -> void {
3430 // clang-format off
3431 float matrix[] = {
3432 1, 0, 0, 0, 0,
3433 0, 1, 0, 0, 0,
3434 0, 0, 1, 0, 0,
3435 0, 0, 0, 1, 0,
3436 };
3437 // clang-format on
3438 std::string desc =
3439 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
3440 float original_value = matrix[element];
3441 matrix[element] = value;
3442 // Here we instantiate a DlMatrixColorFilter directly so that it is
3443 // not affected by the "NOP" detection in the factory. We sould not
3444 // need to do this if we tested by just rendering the filter color
3445 // over the source color with the filter blend mode instead of
3446 // rendering via a ColorFilter, but this test is more "black box".
3447 DlMatrixColorFilter filter(matrix);
3448 std::shared_ptr<const DlColorFilter> dl_filter =
3450 bool is_identity = (dl_filter == nullptr || original_value == value);
3451
3452 DlPaint paint(DlColor(0x7f7f7f7f));
3453 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
3454
3455 DisplayListBuilder builder1;
3457 builder1.Rotate(45);
3458 builder1.Translate(-kTestCenter2.x, -kTestCenter2.y);
3459 builder1.DrawRect(kRenderBounds, paint);
3460 sk_sp<DisplayList> display_list1 = builder1.Build();
3461
3462 DisplayListBuilder builder2;
3464 builder2.Rotate(45);
3465 builder2.Translate(-kTestCenter2.x, -kTestCenter2.y);
3466 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
3467 builder2.DrawRect(kRenderBounds, paint);
3468 builder2.Restore();
3469 sk_sp<DisplayList> display_list2 = builder2.Build();
3470
3471 for (BackendType back_end : GetTestBackends()) {
3472 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3473 std::unique_ptr<RenderEnvironment> env =
3474 std::make_unique<RenderEnvironment>(provider.get(),
3475 PixelFormat::kN32Premul);
3476 RenderResult results1 = env->GetResult(display_list1);
3477 RenderResult results2 = env->GetResult(display_list2);
3479 results1, results2, is_identity, desc + " filter affects rendering");
3480 int modified_transparent_pixels =
3482 results2);
3483 EXPECT_EQ(filter.modifies_transparent_black(),
3484 modified_transparent_pixels != 0)
3485 << desc;
3486 }
3487 };
3488
3489 // Tests identity (matrix[0] already == 1 in an identity filter)
3490 test_matrix(0, 1);
3491 // test_matrix(19, 1);
3492 for (int i = 0; i < 20; i++) {
3493 test_matrix(i, -0.25);
3494 test_matrix(i, 0);
3495 test_matrix(i, 0.25);
3496 test_matrix(i, 1);
3497 test_matrix(i, 1.25);
3498 test_matrix(i, SK_ScalarNaN);
3499 test_matrix(i, SK_ScalarInfinity);
3500 test_matrix(i, -SK_ScalarInfinity);
3501 }
3502}
3503
3504TEST_F(DisplayListRendering, MatrixColorFilterOpacityCommuteCheck) {
3505 auto test_matrix = [](int element, DlScalar value) -> void {
3506 // clang-format off
3507 float matrix[] = {
3508 1, 0, 0, 0, 0,
3509 0, 1, 0, 0, 0,
3510 0, 0, 1, 0, 0,
3511 0, 0, 0, 1, 0,
3512 };
3513 // clang-format on
3514 std::string desc =
3515 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
3516 matrix[element] = value;
3517 std::shared_ptr<const DlColorFilter> filter =
3519 EXPECT_EQ(std::isfinite(value), filter != nullptr);
3520
3521 DlPaint paint(DlColor(0x80808080));
3522 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
3523 DlPaint filter_save_paint = DlPaint().setColorFilter(filter);
3524
3525 DisplayListBuilder builder1;
3526 builder1.SaveLayer(kTestBounds2, &opacity_save_paint);
3527 builder1.SaveLayer(kTestBounds2, &filter_save_paint);
3528 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
3529 builder1.DrawRect(kRenderBounds, paint);
3530 builder1.Restore();
3531 builder1.Restore();
3532 sk_sp<DisplayList> display_list1 = builder1.Build();
3533
3534 DisplayListBuilder builder2;
3535 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
3536 builder2.SaveLayer(kTestBounds2, &opacity_save_paint);
3537 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
3538 builder2.DrawRect(kRenderBounds, paint);
3539 builder2.Restore();
3540 builder2.Restore();
3541 sk_sp<DisplayList> display_list2 = builder2.Build();
3542
3543 for (BackendType back_end : GetTestBackends()) {
3544 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3545 std::unique_ptr<RenderEnvironment> env =
3546 std::make_unique<RenderEnvironment>(provider.get(),
3547 PixelFormat::kN32Premul);
3548 RenderResult results1 = env->GetResult(display_list1);
3549 RenderResult results2 = env->GetResult(display_list2);
3550 if (!filter || filter->can_commute_with_opacity()) {
3552 results2, results1, desc, nullptr, nullptr, DlColor::kTransparent(),
3553 true, kTestWidth, kTestHeight, true);
3554 } else {
3555 CanvasCompareTester::quickCompareToReference(results1, results2, false,
3556 desc);
3557 }
3558 }
3559 };
3560
3561 // Tests identity (matrix[0] already == 1 in an identity filter)
3562 test_matrix(0, 1);
3563 // test_matrix(19, 1);
3564 for (int i = 0; i < 20; i++) {
3565 test_matrix(i, -0.25);
3566 test_matrix(i, 0);
3567 test_matrix(i, 0.25);
3568 test_matrix(i, 1);
3569 test_matrix(i, 1.1);
3570 test_matrix(i, SK_ScalarNaN);
3571 test_matrix(i, SK_ScalarInfinity);
3572 test_matrix(i, -SK_ScalarInfinity);
3573 }
3574}
3575
3576#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
3577 FUNC(kClear) \
3578 FUNC(kSrc) \
3579 FUNC(kDst) \
3580 FUNC(kSrcOver) \
3581 FUNC(kDstOver) \
3582 FUNC(kSrcIn) \
3583 FUNC(kDstIn) \
3584 FUNC(kSrcOut) \
3585 FUNC(kDstOut) \
3586 FUNC(kSrcATop) \
3587 FUNC(kDstATop) \
3588 FUNC(kXor) \
3589 FUNC(kPlus) \
3590 FUNC(kModulate) \
3591 FUNC(kScreen) \
3592 FUNC(kOverlay) \
3593 FUNC(kDarken) \
3594 FUNC(kLighten) \
3595 FUNC(kColorDodge) \
3596 FUNC(kColorBurn) \
3597 FUNC(kHardLight) \
3598 FUNC(kSoftLight) \
3599 FUNC(kDifference) \
3600 FUNC(kExclusion) \
3601 FUNC(kMultiply) \
3602 FUNC(kHue) \
3603 FUNC(kSaturation) \
3604 FUNC(kColor) \
3605 FUNC(kLuminosity)
3606
3607TEST_F(DisplayListRendering, BlendColorFilterModifyTransparencyCheck) {
3608 auto test_mode_color = [](DlBlendMode mode, DlColor color) -> void {
3609 std::stringstream desc_str;
3610 std::string mode_string = BlendModeToString(mode);
3611 desc_str << "blend[" << mode_string << ", " << color << "]";
3612 std::string desc = desc_str.str();
3613 DlBlendColorFilter filter(color, mode);
3614 if (filter.modifies_transparent_black()) {
3615 ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc;
3616 }
3617
3618 DlPaint paint(DlColor(0x7f7f7f7f));
3619 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
3620
3621 DisplayListBuilder builder1;
3623 builder1.Rotate(45);
3624 builder1.Translate(-kTestCenter2.x, -kTestCenter2.y);
3625 builder1.DrawRect(kRenderBounds, paint);
3626 sk_sp<DisplayList> display_list1 = builder1.Build();
3627
3628 DisplayListBuilder builder2;
3630 builder2.Rotate(45);
3631 builder2.Translate(-kTestCenter2.x, -kTestCenter2.y);
3632 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
3633 builder2.DrawRect(kRenderBounds, paint);
3634 builder2.Restore();
3635 sk_sp<DisplayList> display_list2 = builder2.Build();
3636
3637 for (BackendType back_end : GetTestBackends()) {
3638 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3639 std::unique_ptr<RenderEnvironment> env =
3640 std::make_unique<RenderEnvironment>(provider.get(),
3641 PixelFormat::kN32Premul);
3642 RenderResult results1 = env->GetResult(display_list1);
3643 RenderResult results2 = env->GetResult(display_list2);
3644 int modified_transparent_pixels =
3646 results2);
3647 EXPECT_EQ(filter.modifies_transparent_black(),
3648 modified_transparent_pixels != 0)
3649 << provider->GetBackendName() << ": " << desc;
3650 }
3651 };
3652
3653 auto test_mode = [&test_mode_color](DlBlendMode mode) -> void {
3654 test_mode_color(mode, DlColor::kTransparent());
3655 test_mode_color(mode, DlColor::kWhite());
3656 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
3657 test_mode_color(mode, DlColor::kBlack());
3658 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
3659 };
3660
3661#define TEST_MODE(V) test_mode(DlBlendMode::V);
3663#undef TEST_MODE
3664}
3665
3666TEST_F(DisplayListRendering, BlendColorFilterOpacityCommuteCheck) {
3667 auto test_mode_color = [](DlBlendMode mode, DlColor color) -> void {
3668 std::stringstream desc_str;
3669 std::string mode_string = BlendModeToString(mode);
3670 desc_str << "blend[" << mode_string << ", " << color << "]";
3671 std::string desc = desc_str.str();
3672 DlBlendColorFilter filter(color, mode);
3673 if (filter.can_commute_with_opacity()) {
3674 // If it can commute with opacity, then it might also be a NOP,
3675 // so we won't necessarily get a non-null return from |::Make()|
3676 } else {
3677 ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc;
3678 }
3679
3680 DlPaint paint(DlColor(0x80808080));
3681 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
3682 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
3683
3684 DisplayListBuilder builder1;
3685 builder1.SaveLayer(kTestBounds2, &opacity_save_paint);
3686 builder1.SaveLayer(kTestBounds2, &filter_save_paint);
3687 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
3688 builder1.DrawRect(kRenderBounds, paint);
3689 builder1.Restore();
3690 builder1.Restore();
3691 sk_sp<DisplayList> display_list1 = builder1.Build();
3692
3693 DisplayListBuilder builder2;
3694 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
3695 builder2.SaveLayer(kTestBounds2, &opacity_save_paint);
3696 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
3697 builder2.DrawRect(kRenderBounds, paint);
3698 builder2.Restore();
3699 builder2.Restore();
3700 sk_sp<DisplayList> display_list2 = builder2.Build();
3701
3702 for (BackendType back_end : GetTestBackends()) {
3703 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
3704 std::string provider_desc = " " + provider->GetBackendName() + " " + desc;
3705 std::unique_ptr<RenderEnvironment> env =
3706 std::make_unique<RenderEnvironment>(provider.get(),
3707 PixelFormat::kN32Premul);
3708
3709 RenderResult results1 = env->GetResult(display_list1);
3710 RenderResult results2 = env->GetResult(display_list2);
3711 if (filter.can_commute_with_opacity()) {
3713 results2, results1, provider_desc, nullptr, nullptr,
3715 } else {
3716 CanvasCompareTester::quickCompareToReference(results1, results2, false,
3717 provider_desc);
3718 }
3719 }
3720 };
3721
3722 auto test_mode = [&test_mode_color](DlBlendMode mode) -> void {
3723 test_mode_color(mode, DlColor::kTransparent());
3724 test_mode_color(mode, DlColor::kWhite());
3725 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
3726 test_mode_color(mode, DlColor::kBlack());
3727 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
3728 };
3729
3730#define TEST_MODE(V) test_mode(DlBlendMode::V);
3732#undef TEST_MODE
3733}
3734
3736 // The following code uses the acronym MTB for "modifies_transparent_black"
3737
3738 protected:
3740 test_src_colors = {
3741 DlColor::kBlack().withAlpha(0), // transparent black
3742 DlColor::kBlack().withAlpha(0x7f), // half transparent black
3743 DlColor::kWhite().withAlpha(0x7f), // half transparent white
3744 DlColor::kBlack(), // opaque black
3745 DlColor::kWhite(), // opaque white
3746 DlColor::kRed(), // opaque red
3747 DlColor::kGreen(), // opaque green
3748 DlColor::kBlue(), // opaque blue
3749 DlColor::kDarkGrey(), // dark grey
3750 DlColor::kLightGrey(), // light grey
3751 };
3752
3753 // We test against a color cube of 3x3x3x3 colors [55,aa,ff]
3754 // plus transparency as the first color/pixel
3756 const int step = 0x55;
3757 static_assert(step * 3 == 255);
3758 for (int a = step; a < 256; a += step) {
3759 for (int r = step; r < 256; r += step) {
3760 for (int g = step; g < 256; g += step) {
3761 for (int b = step; b < 256; b += step) {
3762 test_dst_colors.push_back(DlColor(a << 24 | r << 16 | g << 8 | b));
3763 }
3764 }
3765 }
3766 }
3767
3768 static constexpr float color_filter_matrix_nomtb[] = {
3769 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3770 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3771 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3772 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3773 };
3774 static constexpr float color_filter_matrix_mtb[] = {
3775 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3776 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3777 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
3778 0.0001, 0.0001, 0.0001, 0.9997, 0.1, //
3779 };
3780 color_filter_nomtb = DlColorFilter::MakeMatrix(color_filter_matrix_nomtb);
3781 color_filter_mtb = DlColorFilter::MakeMatrix(color_filter_matrix_mtb);
3782 EXPECT_FALSE(color_filter_nomtb->modifies_transparent_black());
3783 EXPECT_TRUE(color_filter_mtb->modifies_transparent_black());
3784 }
3785
3786 struct TestData {
3787 // A 1-row image containing every color in test_dst_colors
3788 // RenderResult test_data;
3789 sk_sp<DlImage> test_image_1d;
3791
3792 // A square image containing test_data duplicated in each row
3793 // RenderResult test_image_dst_data;
3794 sk_sp<DlImage> dst_image_2d;
3796
3797 // A square image containing test_data duplicated in each column
3798 // RenderResult test_image_src_data;
3799 sk_sp<DlImage> src_image_2d;
3801 };
3802
3804 std::shared_ptr<DlSurfaceInstance> test_surface = get_output(
3805 provider, test_dst_colors.size(), 1, true, [this](DlCanvas* canvas) {
3806 int x = 0;
3807 DlPaint paint;
3808 paint.setBlendMode(DlBlendMode::kSrc);
3809 for (DlColor color : test_dst_colors) {
3810 paint.setColor(color);
3811 canvas->DrawRect(DlRect::MakeXYWH(x, 0, 1, 1), paint);
3812 x++;
3813 }
3814 });
3815 sk_sp<DlImage> test_image = test_surface->SnapshotToImage();
3816 std::shared_ptr<DlPixelData> test_pixels =
3817 test_surface->SnapshotToPixelData();
3818
3819 // For image-on-image tests, the src and dest images will have repeated
3820 // rows/columns that have every color, but laid out at right angles to
3821 // each other so we see an interaction with every test color against
3822 // every other test color.
3823 int data_count = test_image->width();
3824 std::shared_ptr<DlSurfaceInstance> dst_surface =
3825 get_output(provider, data_count, data_count, true,
3826 [&test_image, data_count](DlCanvas* canvas) {
3827 ASSERT_EQ(test_image->width(), data_count);
3828 ASSERT_EQ(test_image->height(), 1);
3829 for (int y = 0; y < data_count; y++) {
3830 canvas->DrawImage(test_image, DlPoint(0, y),
3832 }
3833 });
3834 std::shared_ptr<DlPixelData> dst_pixels =
3835 dst_surface->SnapshotToPixelData();
3836
3837 std::shared_ptr<DlSurfaceInstance> src_surface =
3838 get_output(provider, data_count, data_count, true,
3839 [&test_image, data_count](DlCanvas* canvas) {
3840 ASSERT_EQ(test_image->width(), data_count);
3841 ASSERT_EQ(test_image->height(), 1);
3842 canvas->Translate(data_count, 0);
3843 canvas->Rotate(90);
3844 for (int y = 0; y < data_count; y++) {
3845 canvas->DrawImage(test_image, DlPoint(0, y),
3847 }
3848 });
3849 std::shared_ptr<DlPixelData> src_pixels =
3850 src_surface->SnapshotToPixelData();
3851
3852 // Double check that the pixel data is laid out in orthogonal stripes
3853 for (int y = 0; y < data_count; y++) {
3854 for (int x = 0; x < data_count; x++) {
3855 EXPECT_EQ(*dst_pixels->addr32(x, y), *test_pixels->addr32(x, 0));
3856 EXPECT_EQ(*src_pixels->addr32(x, y), *test_pixels->addr32(y, 0));
3857 }
3858 }
3859
3860 return {
3861 .test_image_1d = test_surface->SnapshotToImage(),
3862 .test_pixels = RenderResult::Make(test_pixels),
3863
3864 .dst_image_2d = dst_surface->SnapshotToImage(),
3865 .dst_pixels = RenderResult::Make(dst_pixels),
3866
3867 .src_image_2d = src_surface->SnapshotToImage(),
3868 .src_pixels = RenderResult::Make(src_pixels),
3869 };
3870 }
3871
3872 // These flags are 0 by default until they encounter a counter-example
3873 // result and get set.
3874 static constexpr int kWasNotNop = 0x1; // Some tested pixel was modified
3875 static constexpr int kWasMTB = 0x2; // A transparent pixel was modified
3876
3877 std::vector<DlColor> test_src_colors;
3878 std::vector<DlColor> test_dst_colors;
3879
3880 std::shared_ptr<const DlColorFilter> color_filter_nomtb;
3881 std::shared_ptr<const DlColorFilter> color_filter_mtb;
3882
3883 std::map<DlSurfaceProvider::BackendType, TestData> test_datas;
3884
3886 auto entry = test_datas.find(provider->GetBackendType());
3887 if (entry == test_datas.end()) {
3888 TestData test_data = make_test_data(provider);
3889 test_datas[provider->GetBackendType()] = test_data;
3890 return test_data;
3891 }
3892 return entry->second;
3893 }
3894
3895 std::shared_ptr<DlSurfaceInstance> get_output(
3896 DlSurfaceProvider* provider,
3897 int w,
3898 int h,
3899 bool snapshot,
3900 const std::function<void(DlCanvas*)>& renderer) {
3901 std::shared_ptr<DlSurfaceInstance> surface =
3903 DisplayListBuilder builder;
3904 renderer(&builder);
3905 surface->RenderDisplayList(builder.Build());
3906 surface->FlushSubmitCpuSync();
3907 return surface;
3908 }
3909
3911 DlColor result_color,
3912 int x,
3913 int y,
3914 const sk_sp<DisplayList>& dl,
3915 const std::string& desc) {
3916 int ret = 0;
3917 bool is_error = false;
3918 if (dst_color.isTransparent() && !result_color.isTransparent()) {
3919 ret |= kWasMTB;
3920 is_error = !dl->modifies_transparent_black();
3921 }
3922 if (result_color != dst_color) {
3923 ret |= kWasNotNop;
3924 is_error = (dl->op_count() == 0u);
3925 }
3926 if (is_error) {
3927 FML_LOG(ERROR) << std::hex << dst_color //
3928 << std::dec << " at " << x << ", " << y //
3929 << " filters to " << std::hex << result_color //
3930 << desc;
3931 }
3932 return ret;
3933 }
3934
3936 const RenderResult& result_data,
3937 const sk_sp<DisplayList>& dl,
3938 const std::string& desc) {
3939 EXPECT_EQ(dst_data.pixel_data->width(), result_data.pixel_data->width());
3940 EXPECT_EQ(dst_data.pixel_data->height(), result_data.pixel_data->height());
3941 int all_flags = 0;
3942 for (uint32_t y = 0; y < dst_data.pixel_data->height(); y++) {
3943 const uint32_t* dst_pixels = dst_data.pixel_data->addr32(0, y);
3944 const uint32_t* result_pixels = result_data.pixel_data->addr32(0, y);
3945 for (uint32_t x = 0; x < dst_data.pixel_data->width(); x++) {
3946 all_flags |= check_color_result(
3947 DlColor(dst_pixels[x]), DlColor(result_pixels[x]), x, y, dl, desc);
3948 }
3949 }
3950 return all_flags;
3951 }
3952
3953 void report_results(int all_flags,
3954 const sk_sp<DisplayList>& dl,
3955 const std::string& desc) {
3956 if (!dl->modifies_transparent_black()) {
3957 EXPECT_TRUE((all_flags & kWasMTB) == 0);
3958 } else if ((all_flags & kWasMTB) == 0) {
3959 FML_LOG(INFO) << "combination does not affect transparency: " << desc;
3960 }
3961 if (dl->op_count() == 0u) {
3962 EXPECT_TRUE((all_flags & kWasNotNop) == 0);
3963 } else if ((all_flags & kWasNotNop) == 0) {
3964 FML_LOG(INFO) << "combination could be classified as a nop: " << desc;
3965 }
3966 };
3967
3969 std::stringstream desc_stream;
3970 desc_stream << " using SkColorFilter::filterColor() with: ";
3971 desc_stream << BlendModeToString(mode);
3972 desc_stream << "/" << color;
3973 std::string desc = desc_stream.str();
3974 DisplayListBuilder builder(DlRect::MakeWH(100.0f, 100.0f));
3975 DlPaint paint = DlPaint(color).setBlendMode(mode);
3976 builder.DrawRect(DlRect::MakeLTRB(0.0f, 0.0f, 10.0f, 10.0f), paint);
3977 sk_sp<DisplayList> dl = builder.Build();
3978 if (dl->modifies_transparent_black()) {
3979 ASSERT_TRUE(dl->op_count() != 0u);
3980 }
3981
3982 SkBlendMode sk_mode = static_cast<SkBlendMode>(mode);
3983 sk_sp<SkColorFilter> sk_color_filter =
3984 SkColorFilters::Blend(ToSkColor4f(color), nullptr, sk_mode);
3985 sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
3986 int all_flags = 0;
3987 if (sk_color_filter) {
3988 for (DlColor dst_color : test_dst_colors) {
3989 SkColor4f dst_color_f = ToSkColor4f(dst_color);
3990 DlColor result = DlColor(
3991 sk_color_filter->filterColor4f(dst_color_f, srgb.get(), srgb.get())
3992 .toSkColor());
3993 all_flags |=
3994 check_color_result(dst_color, result, /*x=*/0, /*y=*/0, dl, desc);
3995 }
3996 if ((all_flags & kWasMTB) != 0) {
3997 EXPECT_FALSE(sk_color_filter->isAlphaUnchanged());
3998 }
3999 }
4000 report_results(all_flags, dl, desc);
4001 };
4002
4004 std::stringstream desc_stream;
4005 desc_stream << " rendering with: ";
4006 desc_stream << BlendModeToString(mode);
4007 desc_stream << "/" << color;
4008 std::string desc = desc_stream.str();
4009 DisplayListBuilder builder_for_properties;
4010 DlPaint dl_paint = DlPaint(color).setBlendMode(mode);
4011 builder_for_properties.DrawRect(DlRect::MakeWH(100, 100), dl_paint);
4012 sk_sp<DisplayList> properties_display_list = builder_for_properties.Build();
4013 bool dl_is_elided = properties_display_list->op_count() == 0u;
4014 bool dl_affects_transparent_pixels =
4015 properties_display_list->modifies_transparent_black();
4016 ASSERT_TRUE(!dl_is_elided || !dl_affects_transparent_pixels);
4017
4018 DlPaint paint;
4019 paint.setBlendMode(mode);
4020 paint.setColor(color);
4021 for (BackendType back_end : GetTestBackends()) {
4022 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
4023 const TestData test_data = GetTestData(provider.get());
4024 std::string provider_desc = " " + provider->GetBackendName() + desc;
4025
4026 sk_sp<DlImage> test_image = test_data.test_image_1d;
4027 DlRect test_bounds =
4028 DlRect::MakeWH(test_image->width(), test_image->height());
4029 std::shared_ptr<DlSurfaceInstance> result_surface =
4030 provider->MakeOffscreenSurface(test_image->width(),
4031 test_image->height(),
4033 DisplayListBuilder builder_for_rendering;
4034 builder_for_rendering.Clear(DlColor::kTransparent());
4035 builder_for_rendering.DrawImage(test_image, DlPoint(0, 0),
4037 builder_for_rendering.DrawRect(test_bounds, paint);
4038 result_surface->RenderDisplayList(builder_for_rendering.Build());
4039 result_surface->FlushSubmitCpuSync();
4040 RenderResult result_pixels = RenderResult::Make(result_surface);
4041
4042 int all_flags =
4043 check_image_result(test_data.test_pixels, result_pixels,
4044 properties_display_list, provider_desc);
4045 report_results(all_flags, properties_display_list, provider_desc);
4046 }
4047 };
4048
4050 DlColor color,
4051 const DlColorFilter* color_filter,
4052 DlImageFilter* image_filter) {
4053 // if (true) { return; }
4054 std::stringstream desc_stream;
4055 desc_stream << " rendering with: ";
4056 desc_stream << BlendModeToString(mode);
4057 desc_stream << "/" << color;
4058 std::string cf_mtb = color_filter
4059 ? color_filter->modifies_transparent_black()
4060 ? "modifies transparency"
4061 : "preserves transparency"
4062 : "no filter";
4063 desc_stream << ", CF: " << cf_mtb;
4064 std::string if_mtb = image_filter
4065 ? image_filter->modifies_transparent_black()
4066 ? "modifies transparency"
4067 : "preserves transparency"
4068 : "no filter";
4069 desc_stream << ", IF: " << if_mtb;
4070 std::string desc = desc_stream.str();
4071
4072 for (BackendType back_end : GetTestBackends()) {
4073 std::unique_ptr<DlSurfaceProvider> provider = GetProvider(back_end);
4074 const TestData test_data = GetTestData(provider.get());
4075 std::string provider_desc = " " + provider->GetBackendName() + desc;
4076
4077 DisplayListBuilder builder_for_properties(DlRect::MakeWH(100.0f, 100.0f));
4078 DlPaint paint = DlPaint(color) //
4079 .setBlendMode(mode) //
4080 .setColorFilter(color_filter) //
4081 .setImageFilter(image_filter);
4082 builder_for_properties.DrawImage(test_data.src_image_2d, DlPoint(0, 0),
4084 &paint);
4085 sk_sp<DisplayList> properties_display_list =
4086 builder_for_properties.Build();
4087
4088 int w = test_data.src_image_2d->width();
4089 int h = test_data.src_image_2d->height();
4090 std::shared_ptr<DlSurfaceInstance> result_surface =
4091 provider->MakeOffscreenSurface(w, h, DlSurfaceProvider::kN32Premul);
4092 DisplayListBuilder builder_for_rendering;
4093 builder_for_rendering.Clear(DlColor::kTransparent());
4094 builder_for_rendering.DrawImage(test_data.dst_image_2d, DlPoint(0, 0),
4096 builder_for_rendering.DrawImage(test_data.src_image_2d, DlPoint(0, 0),
4098 &paint);
4099 result_surface->RenderDisplayList(builder_for_rendering.Build());
4100 result_surface->FlushSubmitCpuSync();
4101 RenderResult result_pixels = RenderResult::Make(result_surface);
4102
4103 int all_flags =
4104 check_image_result(test_data.dst_pixels, result_pixels,
4105 properties_display_list, provider_desc);
4106 report_results(all_flags, properties_display_list, provider_desc);
4107 }
4108 };
4109};
4110
4111TEST_F(DisplayListNopTest, BlendModeAndColorViaColorFilter) {
4112 auto test_mode_filter = [this](DlBlendMode mode) -> void {
4113 for (DlColor color : test_src_colors) {
4114 test_mode_color_via_filter(mode, color);
4115 }
4116 };
4117
4118#define TEST_MODE(V) test_mode_filter(DlBlendMode::V);
4120#undef TEST_MODE
4121}
4122
4123TEST_F(DisplayListNopTest, BlendModeAndColorByRendering) {
4124 auto test_mode_render = [this](DlBlendMode mode) -> void {
4125 // First check rendering a variety of colors onto image
4126 for (DlColor color : test_src_colors) {
4127 test_mode_color_via_rendering(mode, color);
4128 }
4129 };
4130
4131#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4133#undef TEST_MODE
4134}
4135
4136TEST_F(DisplayListNopTest, BlendModeAndColorAndFiltersByRendering) {
4137 auto test_mode_render = [this](DlBlendMode mode) -> void {
4138 DlColorFilterImageFilter image_filter_nomtb(color_filter_nomtb);
4139 DlColorFilterImageFilter image_filter_mtb(color_filter_mtb);
4140 for (DlColor color : test_src_colors) {
4141 test_attributes_image(mode, color, nullptr, nullptr);
4142 test_attributes_image(mode, color, color_filter_nomtb.get(), nullptr);
4143 test_attributes_image(mode, color, color_filter_mtb.get(), nullptr);
4144 test_attributes_image(mode, color, nullptr, &image_filter_nomtb);
4145 test_attributes_image(mode, color, nullptr, &image_filter_mtb);
4146 }
4147 };
4148
4149#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4151#undef TEST_MODE
4152}
4153
4154#undef FOR_EACH_BLEND_MODE_ENUM
4155
4156} // namespace testing
4157} // namespace flutter
constexpr bool applies_color() const
constexpr bool is_stroked(DlDrawStyle style=DlDrawStyle::kStroke) const
constexpr bool applies_color_filter() const
constexpr bool ignores_paint() const
constexpr bool applies_image_filter() const
constexpr bool applies_shader() const
const DisplayListSpecialGeometryFlags GeometryFlags(bool is_stroked) const
constexpr bool applies_anti_alias() const
constexpr bool applies_mask_filter() const
constexpr bool applies_blend() const
The primitive honors the DlBlendMode.
constexpr bool is_flood() const
void DrawImage(const sk_sp< DlImage > &image, const DlPoint &point, DlImageSampling sampling, const DlPaint *paint=nullptr) override
void SaveLayer(const std::optional< DlRect > &bounds, const DlPaint *paint=nullptr, const DlImageFilter *backdrop=nullptr, std::optional< int64_t > backdrop_id=std::nullopt) override
void Rotate(DlScalar degrees) override
void Scale(DlScalar sx, DlScalar sy) override
void Translate(DlScalar tx, DlScalar ty) override
sk_sp< DisplayList > Build()
void DrawRect(const DlRect &rect, const DlPaint &paint) override
constexpr bool may_have_joins() const
The geometry may have segments connect non-continuously.
constexpr bool may_have_end_caps() const
The geometry may have segments that end without closing the path.
constexpr bool may_have_acute_joins() const
constexpr bool butt_cap_becomes_square() const
Mainly for drawPoints(PointMode) where Butt caps are rendered as squares.
bool can_commute_with_opacity() const override
bool modifies_transparent_black() const override
Developer-facing API for rendering anything within the engine.
Definition dl_canvas.h:32
virtual void Transform(const DlMatrix &matrix)=0
virtual void ClipRect(const DlRect &rect, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false)=0
virtual void DrawPaint(const DlPaint &paint)=0
virtual void DrawCircle(const DlPoint &center, DlScalar radius, const DlPaint &paint)=0
virtual void ClipRoundRect(const DlRoundRect &rrect, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false)=0
virtual DlRect GetDestinationClipCoverage() const =0
virtual void DrawRoundRect(const DlRoundRect &rrect, const DlPaint &paint)=0
virtual void DrawDisplayList(const sk_sp< DisplayList > display_list, DlScalar opacity=SK_Scalar1)=0
virtual DlISize GetBaseLayerDimensions() const =0
virtual void TransformFullPerspective(DlScalar mxx, DlScalar mxy, DlScalar mxz, DlScalar mxt, DlScalar myx, DlScalar myy, DlScalar myz, DlScalar myt, DlScalar mzx, DlScalar mzy, DlScalar mzz, DlScalar mzt, DlScalar mwx, DlScalar mwy, DlScalar mwz, DlScalar mwt)=0
virtual void DrawImageNine(const sk_sp< DlImage > &image, const DlIRect &center, const DlRect &dst, DlFilterMode filter, const DlPaint *paint=nullptr)=0
virtual void SaveLayer(const std::optional< DlRect > &bounds, const DlPaint *paint=nullptr, const DlImageFilter *backdrop=nullptr, std::optional< int64_t > backdrop_id=std::nullopt)=0
virtual void DrawLine(const DlPoint &p0, const DlPoint &p1, const DlPaint &paint)=0
virtual void DrawDiffRoundRect(const DlRoundRect &outer, const DlRoundRect &inner, const DlPaint &paint)=0
virtual void DrawRect(const DlRect &rect, const DlPaint &paint)=0
virtual void DrawVertices(const std::shared_ptr< DlVertices > &vertices, DlBlendMode mode, const DlPaint &paint)=0
virtual void ClipOval(const DlRect &bounds, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false)=0
virtual void Transform2DAffine(DlScalar mxx, DlScalar mxy, DlScalar mxt, DlScalar myx, DlScalar myy, DlScalar myt)=0
virtual void DrawText(const std::shared_ptr< DlText > &text, DlScalar x, DlScalar y, const DlPaint &paint)=0
virtual void DrawImage(const sk_sp< DlImage > &image, const DlPoint &point, DlImageSampling sampling, const DlPaint *paint=nullptr)=0
virtual void DrawOval(const DlRect &bounds, const DlPaint &paint)=0
virtual void Translate(DlScalar tx, DlScalar ty)=0
virtual void DrawColor(DlColor color, DlBlendMode mode=DlBlendMode::kSrcOver)=0
virtual DlMatrix GetMatrix() const =0
virtual void Rotate(DlScalar degrees)=0
virtual void DrawDashedLine(const DlPoint &p0, const DlPoint &p1, DlScalar on_length, DlScalar off_length, const DlPaint &paint)=0
virtual void DrawPath(const DlPath &path, const DlPaint &paint)=0
virtual void Restore()=0
virtual void DrawPoints(DlPointMode mode, uint32_t count, const DlPoint pts[], const DlPaint &paint)=0
virtual void ClipPath(const DlPath &path, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false)=0
void Clear(DlColor color)
Definition dl_canvas.h:104
virtual void DrawArc(const DlRect &bounds, DlScalar start, DlScalar sweep, bool useCenter, const DlPaint &paint)=0
virtual void DrawAtlas(const sk_sp< DlImage > &atlas, const DlRSTransform xform[], const DlRect tex[], const DlColor colors[], int count, DlBlendMode mode, DlImageSampling sampling, const DlRect *cullRect, const DlPaint *paint=nullptr)=0
virtual void Skew(DlScalar sx, DlScalar sy)=0
virtual void DrawImageRect(const sk_sp< DlImage > &image, const DlRect &src, const DlRect &dst, DlImageSampling sampling, const DlPaint *paint=nullptr, DlSrcRectConstraint constraint=DlSrcRectConstraint::kFast)=0
virtual void Scale(DlScalar sx, DlScalar sy)=0
virtual void DrawShadow(const DlPath &path, const DlColor color, const DlScalar elevation, bool transparent_occluder, DlScalar dpr)=0
Draws the shadow of the given |path| rendered in the provided |color| (which is only consulted for it...
virtual void Save()=0
static std::shared_ptr< const DlColorFilter > MakeBlend(DlColor color, DlBlendMode mode)
static std::shared_ptr< const DlColorFilter > MakeLinearToSrgbGamma()
virtual bool modifies_transparent_black() const =0
static std::shared_ptr< const DlColorFilter > MakeMatrix(const float matrix[20])
static std::shared_ptr< const DlColorFilter > MakeSrgbToLinearGamma()
static std::shared_ptr< DlColorSource > MakeLinear(const DlPoint start_point, const DlPoint end_point, uint32_t stop_count, const DlColor *colors, const float *stops, DlTileMode tile_mode, const DlMatrix *matrix=nullptr)
static std::shared_ptr< DlImageFilter > MakeDilate(DlScalar radius_x, DlScalar radius_y)
static std::shared_ptr< DlImageFilter > MakeBlur(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode)
static std::shared_ptr< DlImageFilter > MakeColorFilter(const std::shared_ptr< const DlColorFilter > &filter)
virtual bool modifies_transparent_black() const =0
static std::shared_ptr< DlImageFilter > MakeMatrix(const DlMatrix &matrix, DlImageSampling sampling)
static std::shared_ptr< DlImageFilter > MakeErode(DlScalar radius_x, DlScalar radius_y)
bool modifies_transparent_black() const override
bool isAntiAlias() const
Definition dl_paint.h:57
DlStrokeCap getStrokeCap() const
Definition dl_paint.h:98
DlColor getColor() const
Definition dl_paint.h:69
DlPaint & setColor(DlColor color)
Definition dl_paint.h:70
DlPaint & setAntiAlias(bool isAntiAlias)
Definition dl_paint.h:58
DlPaint & setInvertColors(bool isInvertColors)
Definition dl_paint.h:64
DlBlendMode getBlendMode() const
Definition dl_paint.h:82
float getStrokeMiter() const
Definition dl_paint.h:120
DlStrokeJoin getStrokeJoin() const
Definition dl_paint.h:106
uint8_t getAlpha() const
Definition dl_paint.h:75
const DlColorSource * getColorSourcePtr() const
Definition dl_paint.h:129
DlPaint & setStrokeCap(DlStrokeCap cap)
Definition dl_paint.h:101
DlPaint & setStrokeWidth(float width)
Definition dl_paint.h:115
DlPaint & setAlpha(uint8_t alpha)
Definition dl_paint.h:76
DlPaint & setStrokeMiter(float miter)
Definition dl_paint.h:121
DlPaint & setBlendMode(DlBlendMode mode)
Definition dl_paint.h:85
const std::shared_ptr< const DlMaskFilter > & getMaskFilter() const
Definition dl_paint.h:180
DlDrawStyle getDrawStyle() const
Definition dl_paint.h:90
DlPaint & setImageFilter(std::nullptr_t filter)
Definition dl_paint.h:167
const std::shared_ptr< const DlColorSource > & getColorSource() const
Definition dl_paint.h:126
const std::shared_ptr< DlImageFilter > & getImageFilter() const
Definition dl_paint.h:162
float getStrokeWidth() const
Definition dl_paint.h:114
const std::shared_ptr< const DlColorFilter > & getColorFilter() const
Definition dl_paint.h:144
DlPaint & setMaskFilter(std::nullptr_t filter)
Definition dl_paint.h:185
DlPaint & setDrawStyle(DlDrawStyle style)
Definition dl_paint.h:93
DlPaint & setStrokeJoin(DlStrokeJoin join)
Definition dl_paint.h:109
DlPaint & setOpacity(DlScalar opacity)
Definition dl_paint.h:78
DlPaint & setColorFilter(std::nullptr_t filter)
Definition dl_paint.h:149
DlPaint & setColorSource(std::nullptr_t source)
Definition dl_paint.h:131
bool isInvertColors() const
Definition dl_paint.h:63
DlPathBuilder & LineTo(DlPoint p2)
Draw a line from the current point to the indicated point p2.
DlPathBuilder & MoveTo(DlPoint p2)
Start a new contour that will originate at the indicated point p2.
DlPathBuilder & SetFillType(DlPathFillType fill_type)
Set the fill type that should be used to determine the interior of this path to the indicated |fill_t...
const DlPath TakePath()
Returns the path constructed by this path builder and resets its internal state to the default state ...
DlPathBuilder & AddCircle(DlPoint center, DlScalar radius)
Append a closed circular contour to the path centered on the provided point at the provided radius.
DlPathBuilder & AddRect(const DlRect &rect)
Append a closed rectangular contour to the path.
DlPathBuilder & AddRoundRect(const DlRoundRect &round_rect)
Append a closed rounded rect contour to the path.
DlPathBuilder & Close()
The path is closed back to the location of the most recent MoveTo call. Contours that are filled are ...
static std::shared_ptr< DlTextImpeller > MakeFromBlob(const sk_sp< SkTextBlob > &blob)
static std::shared_ptr< DlTextSkia > Make(const sk_sp< SkTextBlob > &blob)
static std::shared_ptr< DlVertices > Make(DlVertexMode mode, int vertex_count, const DlPoint vertices[], const DlPoint texture_coordinates[], const DlColor colors[], int index_count=0, const uint16_t indices[]=nullptr, const DlRect *bounds=nullptr)
Constructs a DlVector with compact inline storage for all of its required and optional lists of data.
bool overflows(DlIRect pix_bounds, int worst_bounds_pad_x, int worst_bounds_pad_y) const
BoundsTolerance addBoundsPadding(DlScalar bounds_pad_x, DlScalar bounds_pad_y) const
static DlRect Scale(const DlRect &rect, const DlPoint &scales)
BoundsTolerance addPostClipPadding(DlScalar absolute_pad_x, DlScalar absolute_pad_y) const
BoundsTolerance mulScale(DlScalar scale_x, DlScalar scale_y) const
BoundsTolerance(const BoundsTolerance &)=default
BoundsTolerance clip(DlRect clip) const
BoundsTolerance addAbsolutePadding(DlScalar absolute_pad_x, DlScalar absolute_pad_y) const
bool operator==(BoundsTolerance const &other) const
BoundsTolerance addDiscreteOffset(DlScalar discrete_offset) const
static void RenderWithAttributes(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static void checkGroupOpacity(const RenderEnvironment &env, const sk_sp< DisplayList > &display_list, const RenderResult &ref_result, const std::string &info, DlColor bg)
static void compareToReference(const RenderResult &test_result, const RenderResult &ref_result, const std::string &info, const DlRect *bounds, const BoundsTolerance *tolerance, const DlColor bg, bool fuzzyCompares=false, uint32_t width=kTestWidth, uint32_t height=kTestHeight, bool printMismatches=false)
static void RenderWithStrokes(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance_in)
static void RenderWithSaveRestore(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static void RenderAll(const std::unique_ptr< DlSurfaceProvider > &provider, const TestParameters &params, const BoundsTolerance &tolerance=DefaultTolerance)
static DirectoryStatus CheckDir(const std::string &dir)
static void RenderWithClips(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &diff_tolerance)
static void RenderWith(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance_in, const CaseParameters &caseP)
static void showBoundsOverflow(const std::string &info, DlIRect &bounds, const BoundsTolerance *tolerance, int pixLeft, int pixTop, int pixRight, int pixBottom)
static bool checkPixels(const RenderResult &ref_result, const DlRect ref_bounds, const std::string &info, const DlColor bg=DlColor::kTransparent())
static bool fuzzyCompare(uint32_t pixel_a, uint32_t pixel_b, int fudge)
static void save_to_png(const RenderResult &result, const std::string &op_desc, const std::string &reason)
static bool quickCompareToReference(const RenderResult &ref_result, const RenderResult &test_result, bool should_match, const std::string &info)
static void RenderWithTransforms(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static int countModifiedTransparentPixels(const RenderResult &ref_result, const RenderResult &test_result)
static int groupOpacityFudgeFactor(const RenderEnvironment &env)
CaseParameters(std::string info, DlSetup &dl_setup)
CaseParameters(std::string info, DlSetup &dl_setup, DlRenderer &dl_restore, DlColor bg, bool has_diff_clip, bool has_mutating_save_layer, bool fuzzy_compare_components)
CaseParameters with_restore(DlRenderer &dl_restore, bool mutating_layer, bool fuzzy_compare_components=false)
std::shared_ptr< DlSurfaceInstance > get_output(DlSurfaceProvider *provider, int w, int h, bool snapshot, const std::function< void(DlCanvas *)> &renderer)
const TestData make_test_data(DlSurfaceProvider *provider)
std::map< DlSurfaceProvider::BackendType, TestData > test_datas
void report_results(int all_flags, const sk_sp< DisplayList > &dl, const std::string &desc)
int check_image_result(const RenderResult &dst_data, const RenderResult &result_data, const sk_sp< DisplayList > &dl, const std::string &desc)
std::shared_ptr< const DlColorFilter > color_filter_nomtb
void test_mode_color_via_rendering(DlBlendMode mode, DlColor color)
std::shared_ptr< const DlColorFilter > color_filter_mtb
void test_mode_color_via_filter(DlBlendMode mode, DlColor color)
void test_attributes_image(DlBlendMode mode, DlColor color, const DlColorFilter *color_filter, DlImageFilter *image_filter)
int check_color_result(DlColor dst_color, DlColor result_color, int x, int y, const sk_sp< DisplayList > &dl, const std::string &desc)
const TestData GetTestData(DlSurfaceProvider *provider)
static std::unique_ptr< DlSurfaceProvider > GetProvider(BackendType type)
static void RenderAll(const TestParameters &params, const BoundsTolerance &tolerance=CanvasCompareTester::DefaultTolerance)
static const std::vector< BackendType > & GetTestBackends()
virtual bool TargetsImpeller() const =0
static std::optional< BackendType > NameToBackend(const std::string &name)
virtual std::unique_ptr< DlSurfaceInstance > MakeOffscreenSurface(size_t width, size_t height, PixelFormat format=kN32Premul) const =0
static std::string BackendName(BackendType type)
static std::unique_ptr< DlSurfaceProvider > Create(BackendType backend_type)
virtual bool SupportsPixelFormat(PixelFormat format) const =0
virtual const std::string GetBackendName() const =0
virtual BackendType GetBackendType() const =0
const sk_sp< DlImage > GetTestImage() const
RenderResult GetResult(const RenderJobInfo &info, JobRenderer &renderer) const
static RenderEnvironment MakeN32(const DlSurfaceProvider *provider)
const std::shared_ptr< DlText > GetTestText() const
const DlSurfaceProvider * GetProvider() const
const RenderResult & GetReferenceResult() const
RenderEnvironment(const DlSurfaceProvider *provider, PixelFormat format)
static RenderEnvironment Make565(const DlSurfaceProvider *provider)
RenderResult GetResult(const sk_sp< DisplayList > &dl) const
void InitializeReference(DlSetup &dl_setup, DlRenderer &dl_renderer, DlColor bg=DlColor::kTransparent())
TestParameters(const DlRenderer &dl_renderer, const DisplayListAttributeFlags &flags)
const BoundsTolerance lineAdjust(const BoundsTolerance &tolerance, const DlPaint &paint, const DlMatrix &matrix) const
DisplayListAttributeFlags flags() const
const BoundsTolerance adjust(const BoundsTolerance &tolerance, const DlPaint &paint, const DlMatrix &matrix) const
bool impeller_compatible(const DlPaint &paint) const
bool should_match(const RenderEnvironment &env, const CaseParameters &caseP, const DlPaint &attr, const MatrixClipJobRenderer &renderer) const
DlStrokeCap getCap(const DlPaint &attr, DisplayListSpecialGeometryFlags geo_flags) const
std::vector< std::string_view > GetOptionValues(std::string_view name) const
bool HasOption(std::string_view name, size_t *index=nullptr) const
bool is_valid() const
const T & get() const
int32_t value
int32_t x
#define FOR_EACH_BLEND_MODE_ENUM(FUNC)
#define TEST_MODE(V)
const EmbeddedViewParams * params
FlutterVulkanImage * image
VkSurfaceKHR surface
Definition main.cc:65
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t uint32_t * format
#define FML_LOG(severity)
Definition logging.h:101
#define FML_CHECK(condition)
Definition logging.h:104
#define FML_DCHECK(condition)
Definition logging.h:122
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
double y
DlSurfaceProvider::BackendType BackendType
TEST_F(DisplayListTest, Defaults)
constexpr DlScalar kYOffsetU3
constexpr DlRect kRenderBounds
const std::function< void(const DlSetupContext &)> DlSetup
static void DrawCheckerboard(DlCanvas *canvas)
constexpr uint32_t kTestWidth
constexpr DlScalar kTextFontHeight
constexpr DlScalar kMiter4DiamondOffsetX
const DlPoint kHorizontalMiterDiamondPoints[]
constexpr DlRect kTestBounds2
constexpr DlScalar kYOffsetD3
static const DlSetup kEmptyDlSetup
constexpr uint32_t kRenderHeight
SkFont CreateTestFontOfSize(DlScalar scalar)
constexpr DlScalar kYOffsetU2
sk_sp< DisplayList > makeTestDisplayList()
const int kHorizontalMiterDiamondPointCount
constexpr DlScalar kRenderCenterY
static std::shared_ptr< DlImageColorSource > MakeColorSource(const sk_sp< DlImage > &image)
constexpr DlScalar kRenderCenterX
constexpr DlScalar kYOffsetU1
constexpr DlScalar kMiter4DiamondOffsetY
constexpr DlScalar kRenderLeft
constexpr DlScalar kRenderBottom
constexpr DlScalar kXOffsetR2
constexpr DlScalar kYOffset0
DlSurfaceProvider::PixelFormat PixelFormat
constexpr uint32_t kTestHeight
constexpr DlScalar kXOffset0
constexpr DlScalar kRenderTop
constexpr DlScalar kMiterExtremeDiamondOffsetY
constexpr DlScalar kXOffsetL1
constexpr DlScalar kYOffsetD1
constexpr DlScalar kMiter10DiamondOffsetY
constexpr DlScalar kXOffsetL2
constexpr DlScalar kXOffsetL3
constexpr DlScalar kRenderCornerRadius
constexpr DlScalar kXOffsetR3
const int kVerticalMiterDiamondPointCount
constexpr uint32_t kRenderWidth
constexpr DlScalar kMiterExtremeDiamondOffsetX
static const DlRenderer kEmptyDlRenderer
constexpr DlScalar kRenderRight
constexpr DlScalar kMiter10DiamondOffsetX
constexpr DlPoint kVerticalMiterDiamondPoints[]
const std::function< void(const DlRenderContext &)> DlRenderer
constexpr DlScalar kXOffsetR1
constexpr DlScalar kYOffsetD2
constexpr DlScalar kRenderRadius
impeller::Scalar DlScalar
@ kMiter
extends to miter limit
@ kBevel
connects outside edges
DlStrokeCap
Definition dl_paint.h:28
@ kRound
adds circle
@ kButt
no stroke extension
@ kSquare
adds square
impeller::Matrix DlMatrix
impeller::Rect DlRect
impeller::Degrees DlDegrees
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
DlPointMode
Definition dl_types.h:15
@ kLines
draw each separate pair of points as a line segment
@ kPolygon
draw each pair of overlapping points as a line segment
@ kPoints
draw each point separately
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
SkColor4f ToSkColor4f(DlColor color)
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
@ kTriangles
The vertices are taken 3 at a time to form a triangle.
@ kStroke
strokes boundary of shapes
@ kFill
fills interior of shapes
impeller::IRect32 DlIRect
impeller::IPoint32 DlIPoint
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
@ kNormal
fuzzy inside and outside
bool NotEquals(const T *a, const U *b)
TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder h
Definition switch_defs.h:54
impeller::Point DlPoint
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition file_posix.cc:97
CommandLine CommandLineFromIterators(InputIterator first, InputIterator last)
const char * BlendModeToString(BlendMode blend_mode)
Definition color.cc:45
BlendMode
Definition color.h:58
Definition ref_ptr.h:261
impeller::ShaderType type
int32_t height
int32_t width
static constexpr DlColor kMagenta()
Definition dl_color.h:75
static constexpr DlColor kWhite()
Definition dl_color.h:70
static constexpr DlColor kBlue()
Definition dl_color.h:73
static constexpr DlColor kBlack()
Definition dl_color.h:69
static constexpr DlColor kYellow()
Definition dl_color.h:76
static constexpr DlColor kLightGrey()
Definition dl_color.h:79
constexpr DlScalar getRedF() const
Definition dl_color.h:114
constexpr DlScalar getAlphaF() const
Definition dl_color.h:113
constexpr DlScalar getBlueF() const
Definition dl_color.h:116
constexpr DlScalar getGreenF() const
Definition dl_color.h:115
static constexpr DlColor kTransparent()
Definition dl_color.h:68
static constexpr DlColor kRed()
Definition dl_color.h:71
static constexpr DlColor kGreen()
Definition dl_color.h:72
constexpr bool isTransparent() const
Definition dl_color.h:98
static constexpr DlColor kDarkGrey()
Definition dl_color.h:77
static constexpr DlColor kCyan()
Definition dl_color.h:74
uint32_t argb() const
Definition dl_color.h:158
constexpr bool isOpaque() const
Definition dl_color.h:97
DlColor withAlpha(uint8_t alpha) const
Definition dl_color.h:120
void Render(const RenderEnvironment &env, DlCanvas *canvas, const RenderJobInfo &info) override
DisplayListJobRenderer(sk_sp< DisplayList > display_list)
void Render(const RenderEnvironment &env, DlCanvas *canvas, const RenderJobInfo &info) override
DlJobRenderer(const DlSetup &dl_setup, const DlRenderer &dl_render, const DlRenderer &dl_restore)
sk_sp< DisplayList > MakeDisplayList(const RenderEnvironment &env, const RenderJobInfo &info)
virtual void Render(const RenderEnvironment &env, DlCanvas *canvas, const RenderJobInfo &info)=0
static RenderResult Make(const std::shared_ptr< DlSurfaceInstance > &surface)
static RenderResult Make(const std::shared_ptr< DlPixelData > &data)
std::shared_ptr< DlPixelData > pixel_data
static RenderResult Make(const std::shared_ptr< DlPixelData > &data, const DlRect &bounds)
A 4x4 matrix using column-major storage.
Definition matrix.h:37
constexpr Matrix Translate(const Vector3 &t) const
Definition matrix.h:263
static Matrix MakeRotationY(Radians r)
Definition matrix.h:208
static constexpr Matrix MakeRow(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:83
constexpr Matrix Scale(const Vector3 &s) const
Definition matrix.h:275
static Matrix MakeRotationX(Radians r)
Definition matrix.h:193
static RoundRect MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
Definition round_rect.h:31
constexpr auto GetBottom() const
Definition rect.h:391
static constexpr TRect MakeWH(Type width, Type height)
Definition rect.h:140
constexpr auto GetTop() const
Definition rect.h:387
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 T Area() const
Get the area of the rectangle, equivalent to |GetSize().Area()|.
Definition rect.h:410
constexpr bool Contains(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the half-open interior of this rectangle.
Definition rect.h:255
static constexpr std::enable_if_t< std::is_floating_point_v< FT >, TRect > Make(const TRect< U > &rect)
Definition rect.h:181
constexpr auto GetLeft() const
Definition rect.h:385
constexpr TPoint< T > GetLeftTop() const
Definition rect.h:393
RoundOut(const TRect< U > &r)
Definition rect.h:713
constexpr auto GetRight() const
Definition rect.h:389
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
constexpr TPoint< T > GetRightBottom() const
Definition rect.h:405
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
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition rect.h:416
constexpr TRect IntersectionOrEmpty(const TRect &o) const
Definition rect.h:576
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition rect.h:636
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
constexpr Type Area() const
Definition size.h:120
Type height
Definition size.h:29
Type width
Definition size.h:28
std::vector< Point > points
#define DrawText