Flutter Engine
The Flutter Engine
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 "flutter/display_list/display_list.h"
8#include "flutter/display_list/dl_builder.h"
9#include "flutter/display_list/dl_op_flags.h"
10#include "flutter/display_list/dl_sampling_options.h"
11#include "flutter/display_list/skia/dl_sk_canvas.h"
12#include "flutter/display_list/skia/dl_sk_conversions.h"
13#include "flutter/display_list/skia/dl_sk_dispatcher.h"
14#include "flutter/display_list/testing/dl_test_snippets.h"
15#include "flutter/display_list/testing/dl_test_surface_provider.h"
16#include "flutter/display_list/utils/dl_comparable.h"
17#include "flutter/fml/file.h"
18#include "flutter/fml/math.h"
19#include "flutter/testing/display_list_testing.h"
20#include "flutter/testing/testing.h"
21#ifdef IMPELLER_SUPPORTS_RENDERING
22#include "flutter/impeller/typographer/backends/skia/text_frame_skia.h"
23#endif // IMPELLER_SUPPORTS_RENDERING
24
40#include "txt/platform.h"
41
42namespace flutter {
43namespace testing {
44
47
48constexpr int kTestWidth = 200;
49constexpr int kTestHeight = 200;
50constexpr int kRenderWidth = 100;
51constexpr int kRenderHeight = 100;
52constexpr int kRenderHalfWidth = 50;
53constexpr int kRenderHalfHeight = 50;
54constexpr int kRenderLeft = (kTestWidth - kRenderWidth) / 2;
55constexpr int kRenderTop = (kTestHeight - kRenderHeight) / 2;
58constexpr int kRenderCenterX = (kRenderLeft + kRenderRight) / 2;
59constexpr int kRenderCenterY = (kRenderTop + kRenderBottom) / 2;
62
67
68// The tests try 3 miter limit values, 0.0, 4.0 (the default), and 10.0
69// These values will allow us to construct a diamond that spans the
70// width or height of the render box and still show the miter for 4.0
71// and 10.0.
72// These values were discovered by drawing a diamond path in Skia fiddle
73// and then playing with the cross-axis size until the miter was about
74// as large as it could get before it got cut off.
75
76// The X offsets which will be used for tall vertical diamonds are
77// expressed in terms of the rendering height to obtain the proper angle
81
82// The Y offsets which will be used for long horizontal diamonds are
83// expressed in terms of the rendering width to obtain the proper angle
87
88// Render 3 vertical and horizontal diamonds each
89// designed to break at the tested miter limits
90// 0.0, 4.0 and 10.0
91// Center is biased by 0.5 to include more pixel centers in the
92// thin miters
101 // Vertical diamonds:
102 // M10 M4 Mextreme
103 // /\ /|\ /\ top of RenderBounds
104 // / \ / | \ / \ to
105 // <----X--+--X----> RenderCenter
106 // \ / \ | / \ / to
107 // \/ \|/ \/ bottom of RenderBounds
108 // clang-format off
122 // clang-format on
123};
127
136 // Horizontal diamonds
137 // Same configuration as Vertical diamonds above but
138 // rotated 90 degrees
139 // clang-format off
153 // clang-format on
154};
158
160 public:
163 static constexpr SkSamplingOptions kLinear =
167 static constexpr SkSamplingOptions kCubic =
168 SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
169};
170
171static void DrawCheckerboard(DlCanvas* canvas) {
172 DlPaint p0, p1;
174 p0.setColor(DlColor(0xff00fe00)); // off-green
177 // Some pixels need some transparency for DstIn testing
178 p1.setAlpha(128);
179 int cbdim = 5;
180 int width = canvas->GetBaseLayerSize().width();
181 int height = canvas->GetBaseLayerSize().height();
182 for (int y = 0; y < width; y += cbdim) {
183 for (int x = 0; x < height; x += cbdim) {
184 DlPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1;
185 canvas->DrawRect(SkRect::MakeXYWH(x, y, cbdim, cbdim), cellp);
186 }
187 }
188}
189
190static void DrawCheckerboard(SkCanvas* canvas) {
191 DlSkCanvasAdapter dl_canvas(canvas);
192 DrawCheckerboard(&dl_canvas);
193}
194
195static std::shared_ptr<DlImageColorSource> MakeColorSource(
196 const sk_sp<DlImage>& image) {
197 return std::make_shared<DlImageColorSource>(image, //
201}
202
207}
208
209// Used to show "INFO" warnings about tests that are omitted on certain
210// backends, but only once for the entire test run to avoid warning spam
212 public:
213 explicit OncePerBackendWarning(const std::string& warning)
214 : warning_(warning) {}
215
216 void warn(const std::string& name) {
217 if (warnings_sent_.find(name) == warnings_sent_.end()) {
218 warnings_sent_.insert(name);
219 FML_LOG(INFO) << warning_ << " on " << name;
220 }
221 }
222
223 private:
224 std::string warning_;
225 std::set<std::string> warnings_sent_;
226};
227
228// A class to specify how much tolerance to allow in bounds estimates.
229// For some attributes, the machinery must make some conservative
230// assumptions as to the extent of the bounds, but some of our test
231// parameters do not produce bounds that expand by the full conservative
232// estimates. This class provides a number of tweaks to apply to the
233// pixel bounds to account for the conservative factors.
234//
235// An instance is passed along through the methods and if any test adds
236// a paint attribute or other modifier that will cause a more conservative
237// estimate for bounds, it can modify the factors here to account for it.
238// Ideally, all tests will be executed with geometry that will trigger
239// the conservative cases anyway and all attributes will be combined with
240// other attributes that make their output more predictable, but in those
241// cases where a given test sequence cannot really provide attributes to
242// demonstrate the worst case scenario, they can modify these factors to
243// avoid false bounds overflow notifications.
245 public:
246 BoundsTolerance() = default;
248
250 SkScalar bounds_pad_y) const {
252 copy.bounds_pad_.offset(bounds_pad_x, bounds_pad_y);
253 return copy;
254 }
255
256 BoundsTolerance mulScale(SkScalar scale_x, SkScalar scale_y) const {
258 copy.scale_.fX *= scale_x;
259 copy.scale_.fY *= scale_y;
260 return copy;
261 }
262
264 SkScalar absolute_pad_y) const {
266 copy.absolute_pad_.offset(absolute_pad_x, absolute_pad_y);
267 return copy;
268 }
269
271 SkScalar absolute_pad_y) const {
273 copy.clip_pad_.offset(absolute_pad_x, absolute_pad_y);
274 return copy;
275 }
276
279 copy.discrete_offset_ += discrete_offset;
280 return copy;
281 }
282
285 if (!copy.clip_.intersect(clip)) {
286 copy.clip_.setEmpty();
287 }
288 return copy;
289 }
290
291 static SkRect Scale(const SkRect& rect, const SkPoint& scales) {
292 SkScalar outset_x = rect.width() * (scales.fX - 1);
293 SkScalar outset_y = rect.height() * (scales.fY - 1);
294 return rect.makeOutset(outset_x, outset_y);
295 }
296
297 bool overflows(SkIRect pix_bounds,
298 int worst_bounds_pad_x,
299 int worst_bounds_pad_y) const {
300 SkRect allowed = SkRect::Make(pix_bounds);
301 allowed.outset(bounds_pad_.fX, bounds_pad_.fY);
302 allowed = Scale(allowed, scale_);
303 allowed.outset(absolute_pad_.fX, absolute_pad_.fY);
304 if (!allowed.intersect(clip_)) {
305 allowed.setEmpty();
306 }
307 allowed.outset(clip_pad_.fX, clip_pad_.fY);
308 SkIRect rounded = allowed.roundOut();
309 int pad_left = std::max(0, pix_bounds.fLeft - rounded.fLeft);
310 int pad_top = std::max(0, pix_bounds.fTop - rounded.fTop);
311 int pad_right = std::max(0, pix_bounds.fRight - rounded.fRight);
312 int pad_bottom = std::max(0, pix_bounds.fBottom - rounded.fBottom);
313 int allowed_pad_x = std::max(pad_left, pad_right);
314 int allowed_pad_y = std::max(pad_top, pad_bottom);
315 if (worst_bounds_pad_x > allowed_pad_x ||
316 worst_bounds_pad_y > allowed_pad_y) {
317 FML_LOG(ERROR) << "acceptable bounds padding: " //
318 << allowed_pad_x << ", " << allowed_pad_y;
319 }
320 return (worst_bounds_pad_x > allowed_pad_x ||
321 worst_bounds_pad_y > allowed_pad_y);
322 }
323
324 SkScalar discrete_offset() const { return discrete_offset_; }
325
326 bool operator==(BoundsTolerance const& other) const {
327 return bounds_pad_ == other.bounds_pad_ && scale_ == other.scale_ &&
328 absolute_pad_ == other.absolute_pad_ && clip_ == other.clip_ &&
329 clip_pad_ == other.clip_pad_ &&
330 discrete_offset_ == other.discrete_offset_;
331 }
332
333 private:
334 SkPoint bounds_pad_ = {0, 0};
335 SkPoint scale_ = {1, 1};
336 SkPoint absolute_pad_ = {0, 0};
337 SkRect clip_ = {-1E9, -1E9, 1E9, 1E9};
338 SkPoint clip_pad_ = {0, 0};
339
340 SkScalar discrete_offset_ = 0;
341};
342
343template <typename C, typename P, typename I>
348};
355
356using SkSetup = const std::function<void(const SkSetupContext&)>;
357using SkRenderer = const std::function<void(const SkRenderContext&)>;
358using DlSetup = const std::function<void(const DlSetupContext&)>;
359using DlRenderer = const std::function<void(const DlRenderContext&)>;
360static const SkSetup kEmptySkSetup = [](const SkSetupContext&) {};
361static const SkRenderer kEmptySkRenderer = [](const SkRenderContext&) {};
362static const DlSetup kEmptyDlSetup = [](const DlSetupContext&) {};
363static const DlRenderer kEmptyDlRenderer = [](const DlRenderContext&) {};
364
367
369 public:
370 virtual ~RenderResult() = default;
371
372 virtual sk_sp<SkImage> image() const = 0;
373 virtual int width() const = 0;
374 virtual int height() const = 0;
375 virtual const uint32_t* addr32(int x, int y) const = 0;
376 virtual void write(const std::string& path) const = 0;
377};
378
379class SkRenderResult final : public RenderResult {
380 public:
382 bool take_snapshot = false) {
383 SkImageInfo info = surface->imageInfo();
384 info = SkImageInfo::MakeN32Premul(info.dimensions());
385 addr_ = malloc(info.computeMinByteSize() * info.height());
386 pixmap_.reset(info, addr_, info.minRowBytes());
387 surface->readPixels(pixmap_, 0, 0);
388 if (take_snapshot) {
389 image_ = surface->makeImageSnapshot();
390 }
391 }
392 ~SkRenderResult() override { free(addr_); }
393
394 sk_sp<SkImage> image() const override { return image_; }
395 int width() const override { return pixmap_.width(); }
396 int height() const override { return pixmap_.height(); }
397 const uint32_t* addr32(int x, int y) const override {
398 return pixmap_.addr32(x, y);
399 }
400 void write(const std::string& path) const {
401 auto stream = SkFILEWStream(path.c_str());
404 stream.flush();
405 }
406
407 private:
408 sk_sp<SkImage> image_;
409 SkPixmap pixmap_;
410 void* addr_ = nullptr;
411};
412
413class ImpellerRenderResult final : public RenderResult {
414 public:
417 : screenshot_(std::move(screenshot)), render_bounds_(render_bounds) {}
418 ~ImpellerRenderResult() override = default;
419
420 sk_sp<SkImage> image() const override { return nullptr; };
421 int width() const override { return screenshot_->width(); };
422 int height() const override { return screenshot_->height(); }
423 const uint32_t* addr32(int x, int y) const override {
424 return screenshot_->addr32(x, y);
425 }
426 void write(const std::string& path) const override {
427 screenshot_->write(path);
428 }
429 const SkRect& render_bounds() const { return render_bounds_; }
430
431 private:
432 const sk_sp<DlPixelData> screenshot_;
433 SkRect render_bounds_;
434};
435
442};
443
445 virtual void Render(SkCanvas* canvas, const RenderJobInfo& info) = 0;
446 virtual bool targets_impeller() const { return false; }
447};
448
450 public:
451 const SkMatrix& setup_matrix() const {
453 return setup_matrix_;
454 }
455
456 const SkIRect& setup_clip_bounds() const {
458 return setup_clip_bounds_;
459 }
460
461 protected:
462 bool is_setup_ = false;
465};
466
468 explicit SkJobRenderer(const SkSetup& sk_setup,
469 const SkRenderer& sk_render,
470 const SkRenderer& sk_restore,
471 const sk_sp<SkImage>& sk_image)
472 : sk_setup_(sk_setup),
473 sk_render_(sk_render),
474 sk_restore_(sk_restore),
475 sk_image_(sk_image) {}
476
477 void Render(SkCanvas* canvas, const RenderJobInfo& info) override {
478 FML_DCHECK(info.opacity == SK_Scalar1);
480 sk_setup_({canvas, paint, sk_image_});
481 setup_paint_ = paint;
482 setup_matrix_ = canvas->getTotalMatrix();
484 is_setup_ = true;
485 sk_render_({canvas, paint, sk_image_});
486 sk_restore_({canvas, paint, sk_image_});
487 }
488
490 SkPictureRecorder recorder;
491 SkRTreeFactory rtree_factory;
492 SkCanvas* cv = recorder.beginRecording(kTestBounds2, &rtree_factory);
493 Render(cv, info);
494 return recorder.finishRecordingAsPicture();
495 }
496
497 const SkPaint& setup_paint() const {
499 return setup_paint_;
500 }
501
502 private:
503 const SkSetup sk_setup_;
504 const SkRenderer sk_render_;
505 const SkRenderer sk_restore_;
506 sk_sp<SkImage> sk_image_;
507 SkPaint setup_paint_;
508};
509
511 explicit DlJobRenderer(const DlSetup& dl_setup,
512 const DlRenderer& dl_render,
513 const DlRenderer& dl_restore,
514 const sk_sp<DlImage>& dl_image)
515 : dl_setup_(dl_setup),
516 dl_render_(dl_render),
517 dl_restore_(dl_restore),
518 dl_image_(dl_image) {}
519
520 void Render(SkCanvas* sk_canvas, const RenderJobInfo& info) override {
521 DlSkCanvasAdapter canvas(sk_canvas);
522 Render(&canvas, info);
523 }
524
525 void Render(DlCanvas* canvas, const RenderJobInfo& info) {
526 FML_DCHECK(info.opacity == SK_Scalar1);
528 dl_setup_({canvas, paint, dl_image_});
529 setup_paint_ = paint;
530 setup_matrix_ = canvas->GetTransform();
532 is_setup_ = true;
533 dl_render_({canvas, paint, dl_image_});
534 dl_restore_({canvas, paint, dl_image_});
535 }
536
540 return builder.Build();
541 }
542
543 const DlPaint& setup_paint() const {
545 return setup_paint_;
546 }
547
548 bool targets_impeller() const override {
549 return dl_image_->impeller_texture() != nullptr;
550 }
551
552 private:
553 const DlSetup dl_setup_;
554 const DlRenderer dl_render_;
555 const DlRenderer dl_restore_;
556 const sk_sp<DlImage> dl_image_;
557 DlPaint setup_paint_;
558};
559
562 : picture_(std::move(picture)) {}
563
564 void Render(SkCanvas* canvas, const RenderJobInfo& info) {
565 FML_DCHECK(info.opacity == SK_Scalar1);
566 picture_->playback(canvas);
567 }
568
569 private:
570 sk_sp<SkPicture> picture_;
571};
572
575 : display_list_(std::move(display_list)) {}
576
577 void Render(SkCanvas* canvas, const RenderJobInfo& info) {
578 DlSkCanvasAdapter(canvas).DrawDisplayList(display_list_, info.opacity);
579 }
580
581 private:
582 sk_sp<DisplayList> display_list_;
583};
584
586 public:
587 static bool EnableImpeller;
588
590 : provider_(provider), format_(format) {
591 if (provider->supports(format)) {
592 surface_1x_ =
594 surface_2x_ = provider->MakeOffscreenSurface(kTestWidth * 2,
595 kTestHeight * 2, format);
596 }
597 }
598
600 return RenderEnvironment(provider, PixelFormat::k565PixelFormat);
601 }
602
604 return RenderEnvironment(provider, PixelFormat::kN32PremulPixelFormat);
605 }
606
607 void init_ref(SkSetup& sk_setup,
608 SkRenderer& sk_renderer,
609 DlSetup& dl_setup,
610 DlRenderer& dl_renderer,
611 DlRenderer& imp_renderer,
613 SkJobRenderer sk_job(sk_setup, sk_renderer, kEmptySkRenderer, kTestSkImage);
615 .bg = bg,
616 };
617 ref_sk_result_ = getResult(info, sk_job);
618 DlJobRenderer dl_job(dl_setup, dl_renderer, kEmptyDlRenderer, kTestDlImage);
619 ref_dl_result_ = getResult(info, dl_job);
620 ref_dl_paint_ = dl_job.setup_paint();
621 ref_matrix_ = dl_job.setup_matrix();
622 ref_clip_bounds_ = dl_job.setup_clip_bounds();
623 ASSERT_EQ(sk_job.setup_matrix(), ref_matrix_);
624 ASSERT_EQ(sk_job.setup_clip_bounds(), ref_clip_bounds_);
625 if (supports_impeller()) {
626 test_impeller_image_ = makeTestImpellerImage(provider_);
627 DlJobRenderer imp_job(dl_setup, imp_renderer, kEmptyDlRenderer,
628 test_impeller_image_);
629 ref_impeller_result_ = getImpellerResult(info, imp_job);
630 }
631 }
632
633 std::unique_ptr<RenderResult> getResult(const RenderJobInfo& info,
634 JobRenderer& renderer) const {
635 auto surface = getSurface(info.width, info.height);
636 FML_DCHECK(surface != nullptr);
637 auto canvas = surface->getCanvas();
638 canvas->clear(ToSk(info.bg));
639
640 int restore_count = canvas->save();
641 canvas->scale(info.scale, info.scale);
642 renderer.Render(canvas, info);
643 canvas->restoreToCount(restore_count);
644
645 if (GrDirectContext* dContext =
646 GrAsDirectContext(surface->recordingContext())) {
647 dContext->flushAndSubmit(surface.get(), GrSyncCpu::kYes);
648 }
649 return std::make_unique<SkRenderResult>(surface);
650 }
651
652 std::unique_ptr<RenderResult> getResult(sk_sp<DisplayList> dl) const {
653 DisplayListJobRenderer job(std::move(dl));
654 RenderJobInfo info = {};
655 return getResult(info, job);
656 }
657
658 std::unique_ptr<ImpellerRenderResult> getImpellerResult(
659 const RenderJobInfo& info,
660 DlJobRenderer& renderer) const {
661 FML_DCHECK(info.scale == SK_Scalar1);
662
664 builder.Clear(info.bg);
665 auto render_dl = renderer.MakeDisplayList(info);
666 builder.DrawDisplayList(render_dl);
667 auto dl = builder.Build();
668 auto snap = provider_->ImpellerSnapshot(dl, kTestWidth, kTestHeight);
669 return std::make_unique<ImpellerRenderResult>(std::move(snap),
670 render_dl->bounds());
671 }
672
673 const DlSurfaceProvider* provider() const { return provider_; }
674 bool valid() const { return provider_->supports(format_); }
675 const std::string backend_name() const { return provider_->backend_name(); }
676 bool supports_impeller() const {
677 return EnableImpeller && provider_->supports_impeller();
678 }
679
680 PixelFormat format() const { return format_; }
681 const DlPaint& ref_dl_paint() const { return ref_dl_paint_; }
682 const SkMatrix& ref_matrix() const { return ref_matrix_; }
683 const SkIRect& ref_clip_bounds() const { return ref_clip_bounds_; }
684 const RenderResult* ref_sk_result() const { return ref_sk_result_.get(); }
685 const RenderResult* ref_dl_result() const { return ref_dl_result_.get(); }
687 return ref_impeller_result_.get();
688 }
689
690 const sk_sp<SkImage> sk_image() const { return kTestSkImage; }
691 const sk_sp<DlImage> dl_image() const { return kTestDlImage; }
692 const sk_sp<DlImage> impeller_image() const { return test_impeller_image_; }
693
694 private:
695 sk_sp<SkSurface> getSurface(int width, int height) const {
696 FML_DCHECK(valid());
697 FML_DCHECK(surface_1x_ != nullptr);
698 FML_DCHECK(surface_2x_ != nullptr);
699 if (width == kTestWidth && height == kTestHeight) {
700 return surface_1x_->sk_surface();
701 }
702 if (width == kTestWidth * 2 && height == kTestHeight * 2) {
703 return surface_2x_->sk_surface();
704 }
705 FML_LOG(ERROR) << "Test surface size (" << width << " x " << height
706 << ") not supported.";
707 FML_DCHECK(false);
708 return nullptr;
709 }
710
711 const DlSurfaceProvider* provider_;
712 const PixelFormat format_;
713 std::shared_ptr<DlSurfaceInstance> surface_1x_;
714 std::shared_ptr<DlSurfaceInstance> surface_2x_;
715
716 DlPaint ref_dl_paint_;
717 SkMatrix ref_matrix_;
718 SkIRect ref_clip_bounds_;
719 std::unique_ptr<RenderResult> ref_sk_result_;
720 std::unique_ptr<RenderResult> ref_dl_result_;
721 std::unique_ptr<ImpellerRenderResult> ref_impeller_result_;
722 sk_sp<DlImage> test_impeller_image_;
723
724 static const sk_sp<SkImage> kTestSkImage;
725 static const sk_sp<DlImage> kTestDlImage;
726 static const sk_sp<SkImage> makeTestSkImage() {
729 DrawCheckerboard(surface->getCanvas());
730 return surface->makeImageSnapshot();
731 }
732 static const sk_sp<DlImage> makeTestImpellerImage(
733 const DlSurfaceProvider* provider) {
735 DisplayListBuilder builder(SkRect::MakeWH(kRenderWidth, kRenderHeight));
737 return provider->MakeImpellerImage(builder.Build(), //
739 }
740};
741
742const sk_sp<SkImage> RenderEnvironment::kTestSkImage = makeTestSkImage();
743const sk_sp<DlImage> RenderEnvironment::kTestDlImage =
744 DlImage::Make(kTestSkImage);
745
747 public:
748 explicit CaseParameters(std::string info)
750
752 : CaseParameters(std::move(info),
753 sk_setup,
754 dl_setup,
758 false,
759 false,
760 false) {}
761
762 CaseParameters(std::string info,
767 DlColor bg,
768 bool has_diff_clip,
771 : info_(std::move(info)),
772 bg_(bg),
773 sk_setup_(sk_setup),
774 dl_setup_(dl_setup),
775 sk_restore_(sk_restore),
776 dl_restore_(dl_restore),
777 has_diff_clip_(has_diff_clip),
778 has_mutating_save_layer_(has_mutating_save_layer),
779 fuzzy_compare_components_(fuzzy_compare_components) {}
780
783 bool mutating_layer,
784 bool fuzzy_compare_components = false) {
785 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore, dl_restore,
786 bg_, has_diff_clip_, mutating_layer,
788 }
789
791 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore_, dl_restore_,
792 bg, has_diff_clip_, has_mutating_save_layer_,
793 fuzzy_compare_components_);
794 }
795
797 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore_, dl_restore_,
798 bg_, true, has_mutating_save_layer_,
799 fuzzy_compare_components_);
800 }
801
802 std::string info() const { return info_; }
803 DlColor bg() const { return bg_; }
804 bool has_diff_clip() const { return has_diff_clip_; }
805 bool has_mutating_save_layer() const { return has_mutating_save_layer_; }
806 bool fuzzy_compare_components() const { return fuzzy_compare_components_; }
807
808 SkSetup sk_setup() const { return sk_setup_; }
809 DlSetup dl_setup() const { return dl_setup_; }
810 SkRenderer sk_restore() const { return sk_restore_; }
811 DlRenderer dl_restore() const { return dl_restore_; }
812
813 private:
814 const std::string info_;
815 const DlColor bg_;
816 const SkSetup sk_setup_;
817 const DlSetup dl_setup_;
818 const SkRenderer sk_restore_;
819 const DlRenderer dl_restore_;
820 const bool has_diff_clip_;
821 const bool has_mutating_save_layer_;
822 const bool fuzzy_compare_components_;
823};
824
826 public:
828 const DlRenderer& dl_renderer,
831
833 const DlRenderer& dl_renderer,
836 : sk_renderer_(sk_renderer),
837 dl_renderer_(dl_renderer),
838 imp_renderer_(imp_renderer),
839 flags_(flags) {}
840
841 bool uses_paint() const { return !flags_.ignores_paint(); }
842 bool uses_gradient() const { return flags_.applies_shader(); }
843
844 bool impeller_compatible(const DlPaint& paint) const {
845 if (is_draw_text_blob()) {
846 // Non-color text is rendered as paths
847 if (paint.getColorSourcePtr() && !paint.getColorSourcePtr()->asColor()) {
848 return false;
849 }
850 // Non-filled text (stroke or stroke and fill) is rendered as paths
851 if (paint.getDrawStyle() != DlDrawStyle::kFill) {
852 return false;
853 }
854 }
855 return true;
856 }
857
859 const CaseParameters& caseP,
860 const DlPaint& attr,
861 const MatrixClipJobRenderer& renderer) const {
862 if (caseP.has_mutating_save_layer()) {
863 return false;
864 }
865 if (env.ref_clip_bounds() != renderer.setup_clip_bounds() ||
866 caseP.has_diff_clip()) {
867 return false;
868 }
869 if (env.ref_matrix() != renderer.setup_matrix() && !flags_.is_flood()) {
870 return false;
871 }
872 if (flags_.ignores_paint()) {
873 return true;
874 }
875 const DlPaint& ref_attr = env.ref_dl_paint();
876 if (flags_.applies_anti_alias() && //
877 ref_attr.isAntiAlias() != attr.isAntiAlias()) {
878 if (renderer.targets_impeller()) {
879 // Impeller only does MSAA, ignoring the AA attribute
880 // https://github.com/flutter/flutter/issues/104721
881 } else {
882 return false;
883 }
884 }
885 if (flags_.applies_color() && //
886 ref_attr.getColor() != attr.getColor()) {
887 return false;
888 }
889 if (flags_.applies_blend() && //
890 ref_attr.getBlendMode() != attr.getBlendMode()) {
891 return false;
892 }
893 if (flags_.applies_color_filter() && //
894 (ref_attr.isInvertColors() != attr.isInvertColors() ||
895 NotEquals(ref_attr.getColorFilter(), attr.getColorFilter()))) {
896 return false;
897 }
898 if (flags_.applies_mask_filter() && //
899 NotEquals(ref_attr.getMaskFilter(), attr.getMaskFilter())) {
900 return false;
901 }
902 if (flags_.applies_image_filter() && //
903 ref_attr.getImageFilter() != attr.getImageFilter()) {
904 return false;
905 }
906 if (flags_.applies_shader() && //
907 NotEquals(ref_attr.getColorSource(), attr.getColorSource())) {
908 return false;
909 }
910
911 bool is_stroked = flags_.is_stroked(attr.getDrawStyle());
912 if (flags_.is_stroked(ref_attr.getDrawStyle()) != is_stroked) {
913 return false;
914 }
915 if (!is_stroked) {
916 return true;
917 }
918 if (ref_attr.getStrokeWidth() != attr.getStrokeWidth()) {
919 return false;
920 }
922 flags_.GeometryFlags(is_stroked);
923 if (geo_flags.may_have_end_caps() && //
924 getCap(ref_attr, geo_flags) != getCap(attr, geo_flags)) {
925 return false;
926 }
927 if (geo_flags.may_have_joins()) {
928 if (ref_attr.getStrokeJoin() != attr.getStrokeJoin()) {
929 return false;
930 }
931 if (ref_attr.getStrokeJoin() == DlStrokeJoin::kMiter) {
932 SkScalar ref_miter = ref_attr.getStrokeMiter();
933 SkScalar test_miter = attr.getStrokeMiter();
934 // miter limit < 1.4 affects right angles
935 if (geo_flags.may_have_acute_joins() || //
936 ref_miter < 1.4 || test_miter < 1.4) {
937 if (ref_miter != test_miter) {
938 return false;
939 }
940 }
941 }
942 }
943 return true;
944 }
945
947 DisplayListSpecialGeometryFlags geo_flags) const {
948 DlStrokeCap cap = attr.getStrokeCap();
949 if (geo_flags.butt_cap_becomes_square() && cap == DlStrokeCap::kButt) {
951 }
952 return cap;
953 }
954
955 const BoundsTolerance adjust(const BoundsTolerance& tolerance,
956 const DlPaint& paint,
957 const SkMatrix& matrix) const {
958 if (is_draw_text_blob() && tolerance.discrete_offset() > 0) {
959 // drawTextBlob needs just a little more leeway when using a
960 // discrete path effect.
961 return tolerance.addBoundsPadding(2, 2);
962 }
963 if (is_draw_line()) {
964 return lineAdjust(tolerance, paint, matrix);
965 }
966 if (is_draw_arc_center()) {
967 if (paint.getDrawStyle() != DlDrawStyle::kFill &&
968 paint.getStrokeJoin() == DlStrokeJoin::kMiter) {
969 // the miter join at the center of an arc does not really affect
970 // its bounds in any of our test cases, but the bounds code needs
971 // to take it into account for the cases where it might, so we
972 // relax our tolerance to reflect the miter bounds padding.
973 SkScalar miter_pad =
974 paint.getStrokeMiter() * paint.getStrokeWidth() * 0.5f;
975 return tolerance.addBoundsPadding(miter_pad, miter_pad);
976 }
977 }
978 return tolerance;
979 }
980
982 const DlPaint& paint,
983 const SkMatrix& matrix) const {
984 SkScalar adjust = 0.0;
985 SkScalar half_width = paint.getStrokeWidth() * 0.5f;
986 if (tolerance.discrete_offset() > 0) {
987 // When a discrete path effect is added, the bounds calculations must
988 // allow for miters in any direction, but a horizontal line will not
989 // have miters in the horizontal direction, similarly for vertical
990 // lines, and diagonal lines will have miters off at a "45 degree"
991 // angle that don't expand the bounds much at all.
992 // Also, the discrete offset will not move any points parallel with
993 // the line, so provide tolerance for both miters and offset.
994 adjust =
995 half_width * paint.getStrokeMiter() + tolerance.discrete_offset();
996 }
997
998 DisplayListSpecialGeometryFlags geo_flags = flags_.GeometryFlags(true);
999 if (paint.getStrokeCap() == DlStrokeCap::kButt &&
1000 !geo_flags.butt_cap_becomes_square()) {
1001 adjust = std::max(adjust, half_width);
1002 }
1003 if (adjust == 0) {
1004 return tolerance;
1005 }
1006 SkScalar h_tolerance;
1007 SkScalar v_tolerance;
1008 if (is_horizontal_line()) {
1010 h_tolerance = adjust;
1011 v_tolerance = 0;
1012 } else if (is_vertical_line()) {
1013 h_tolerance = 0;
1014 v_tolerance = adjust;
1015 } else {
1016 // The perpendicular miters just do not impact the bounds of
1017 // diagonal lines at all as they are aimed in the wrong direction
1018 // to matter. So allow tolerance in both axes.
1019 h_tolerance = v_tolerance = adjust;
1020 }
1021 BoundsTolerance new_tolerance =
1022 tolerance.addBoundsPadding(h_tolerance, v_tolerance);
1023 return new_tolerance;
1024 }
1025
1026 const SkRenderer& sk_renderer() const { return sk_renderer_; }
1027 const DlRenderer& dl_renderer() const { return dl_renderer_; }
1028 const DlRenderer& imp_renderer() const { return imp_renderer_; }
1029
1030 // Tests that call drawTextBlob with an sk_ref paint attribute will cause
1031 // those attributes to be stored in an internal Skia cache so we need
1032 // to expect that the |sk_ref.unique()| call will fail in those cases.
1033 // See: (TBD(flar) - file Skia bug)
1034 bool is_draw_text_blob() const { return is_draw_text_blob_; }
1035 bool is_draw_display_list() const { return is_draw_display_list_; }
1036 bool is_draw_line() const { return is_draw_line_; }
1037 bool is_draw_arc_center() const { return is_draw_arc_center_; }
1038 bool is_draw_path() const { return is_draw_path_; }
1039 bool is_horizontal_line() const { return is_horizontal_line_; }
1040 bool is_vertical_line() const { return is_vertical_line_; }
1041 bool ignores_dashes() const { return ignores_dashes_; }
1042
1044 is_draw_text_blob_ = true;
1045 return *this;
1046 }
1048 is_draw_display_list_ = true;
1049 return *this;
1050 }
1052 is_draw_line_ = true;
1053 return *this;
1054 }
1056 is_draw_arc_center_ = true;
1057 return *this;
1058 }
1060 is_draw_path_ = true;
1061 return *this;
1062 }
1064 ignores_dashes_ = true;
1065 return *this;
1066 }
1068 is_horizontal_line_ = true;
1069 return *this;
1070 }
1072 is_vertical_line_ = true;
1073 return *this;
1074 }
1075
1076 private:
1077 const SkRenderer sk_renderer_;
1078 const DlRenderer dl_renderer_;
1079 const DlRenderer imp_renderer_;
1080 const DisplayListAttributeFlags flags_;
1081
1082 bool is_draw_text_blob_ = false;
1083 bool is_draw_display_list_ = false;
1084 bool is_draw_line_ = false;
1085 bool is_draw_arc_center_ = false;
1086 bool is_draw_path_ = false;
1087 bool ignores_dashes_ = false;
1088 bool is_horizontal_line_ = false;
1089 bool is_vertical_line_ = false;
1090};
1091
1093 public:
1094 static std::vector<BackendType> TestBackends;
1097 static std::vector<std::string> ImpellerFailureImages;
1099
1100 static std::unique_ptr<DlSurfaceProvider> GetProvider(BackendType type) {
1101 auto provider = DlSurfaceProvider::Create(type);
1102 if (provider == nullptr) {
1104 << " not supported (ignoring)";
1105 return nullptr;
1106 }
1107 provider->InitializeSurface(kTestWidth, kTestHeight,
1108 PixelFormat::kN32PremulPixelFormat);
1109 return provider;
1110 }
1111
1112 static void ClearProviders() { TestBackends.clear(); }
1113
1115 auto provider = GetProvider(type);
1116 if (!provider) {
1117 return false;
1118 }
1119 if (provider->supports_impeller()) {
1120 ImpellerSupported = true;
1121 }
1122 TestBackends.push_back(type);
1123 return true;
1124 }
1125
1127
1128 static void RenderAll(const TestParameters& params,
1129 const BoundsTolerance& tolerance = DefaultTolerance) {
1130 for (auto& back_end : TestBackends) {
1131 auto provider = GetProvider(back_end);
1133 env.init_ref(kEmptySkSetup, params.sk_renderer(), //
1134 kEmptyDlSetup, params.dl_renderer(), params.imp_renderer());
1135 quickCompareToReference(env, "default");
1136 if (env.supports_impeller()) {
1137 auto impeller_result = env.ref_impeller_result();
1138 if (!checkPixels(impeller_result, impeller_result->render_bounds(),
1139 "Impeller reference")) {
1140 std::string test_name =
1141 ::testing::UnitTest::GetInstance()->current_test_info()->name();
1142 save_to_png(impeller_result, test_name + " (Impeller reference)",
1143 "base rendering was blank or out of bounds");
1144 }
1145 } else {
1146 static OncePerBackendWarning warnings("No Impeller output tests");
1147 warnings.warn(env.backend_name());
1148 }
1149
1150 RenderWithTransforms(params, env, tolerance);
1151 RenderWithClips(params, env, tolerance);
1152 RenderWithSaveRestore(params, env, tolerance);
1153 // Only test attributes if the canvas version uses the paint object
1154 if (params.uses_paint()) {
1155 RenderWithAttributes(params, env, tolerance);
1156 }
1157 }
1158 }
1159
1160 static void RenderWithSaveRestore(const TestParameters& testP,
1161 const RenderEnvironment& env,
1162 const BoundsTolerance& tolerance) {
1163 SkRect clip =
1166 DlColor alpha_layer_color = DlColor::kCyan().withAlpha(0x7f);
1167 SkRenderer sk_safe_restore = [=](const SkRenderContext& ctx) {
1168 // Draw another primitive to disable peephole optimizations
1169 ctx.canvas->drawRect(kRenderBounds.makeOffset(500, 500), SkPaint());
1170 ctx.canvas->restore();
1171 };
1172 DlRenderer dl_safe_restore = [=](const DlRenderContext& ctx) {
1173 // Draw another primitive to disable peephole optimizations
1174 // As the rendering op rejection in the DisplayList Builder
1175 // gets smarter and smarter, this operation has had to get
1176 // sneakier and sneakier about specifying an operation that
1177 // won't practically show up in the output, but technically
1178 // can't be culled.
1179 ctx.canvas->DrawRect(
1181 DlPaint());
1182 ctx.canvas->Restore();
1183 };
1184 SkRenderer sk_opt_restore = [=](const SkRenderContext& ctx) {
1185 // Just a simple restore to allow peephole optimizations to occur
1186 ctx.canvas->restore();
1187 };
1188 DlRenderer dl_opt_restore = [=](const DlRenderContext& ctx) {
1189 // Just a simple restore to allow peephole optimizations to occur
1190 ctx.canvas->Restore();
1191 };
1192 SkRect layer_bounds = kRenderBounds.makeInset(15, 15);
1193 RenderWith(testP, env, tolerance,
1195 "With prior save/clip/restore",
1196 [=](const SkSetupContext& ctx) {
1197 ctx.canvas->save();
1198 ctx.canvas->clipRect(clip, SkClipOp::kIntersect, false);
1199 SkPaint p2;
1200 ctx.canvas->drawRect(rect, p2);
1202 ctx.canvas->drawRect(rect, p2);
1203 ctx.canvas->restore();
1204 },
1205 [=](const DlSetupContext& ctx) {
1206 ctx.canvas->Save();
1207 ctx.canvas->ClipRect(clip, ClipOp::kIntersect, false);
1208 DlPaint p2;
1209 ctx.canvas->DrawRect(rect, p2);
1211 ctx.canvas->DrawRect(rect, p2);
1212 ctx.canvas->Restore();
1213 }));
1214 RenderWith(testP, env, tolerance,
1216 "saveLayer no paint, no bounds",
1217 [=](const SkSetupContext& ctx) {
1218 ctx.canvas->saveLayer(nullptr, nullptr);
1219 },
1220 [=](const DlSetupContext& ctx) {
1221 ctx.canvas->SaveLayer(nullptr, nullptr);
1222 })
1223 .with_restore(sk_safe_restore, dl_safe_restore, false));
1224 RenderWith(testP, env, tolerance,
1226 "saveLayer no paint, with bounds",
1227 [=](const SkSetupContext& ctx) {
1228 ctx.canvas->saveLayer(layer_bounds, nullptr);
1229 },
1230 [=](const DlSetupContext& ctx) {
1231 ctx.canvas->SaveLayer(&layer_bounds, nullptr);
1232 })
1233 .with_restore(sk_safe_restore, dl_safe_restore, true));
1234 RenderWith(testP, env, tolerance,
1236 "saveLayer with alpha, no bounds",
1237 [=](const SkSetupContext& ctx) {
1238 SkPaint save_p;
1239 save_p.setColor(ToSk(alpha_layer_color));
1240 ctx.canvas->saveLayer(nullptr, &save_p);
1241 },
1242 [=](const DlSetupContext& ctx) {
1243 DlPaint save_p;
1244 save_p.setColor(alpha_layer_color);
1245 ctx.canvas->SaveLayer(nullptr, &save_p);
1246 })
1247 .with_restore(sk_safe_restore, dl_safe_restore, true));
1248 RenderWith(testP, env, tolerance,
1250 "saveLayer with peephole alpha, no bounds",
1251 [=](const SkSetupContext& ctx) {
1252 SkPaint save_p;
1253 save_p.setColor(ToSk(alpha_layer_color));
1254 ctx.canvas->saveLayer(nullptr, &save_p);
1255 },
1256 [=](const DlSetupContext& ctx) {
1257 DlPaint save_p;
1258 save_p.setColor(alpha_layer_color);
1259 ctx.canvas->SaveLayer(nullptr, &save_p);
1260 })
1261 .with_restore(sk_opt_restore, dl_opt_restore, true, true));
1262 RenderWith(testP, env, tolerance,
1264 "saveLayer with alpha and bounds",
1265 [=](const SkSetupContext& ctx) {
1266 SkPaint save_p;
1267 save_p.setColor(ToSk(alpha_layer_color));
1268 ctx.canvas->saveLayer(layer_bounds, &save_p);
1269 },
1270 [=](const DlSetupContext& ctx) {
1271 DlPaint save_p;
1272 save_p.setColor(alpha_layer_color);
1273 ctx.canvas->SaveLayer(&layer_bounds, &save_p);
1274 })
1275 .with_restore(sk_safe_restore, dl_safe_restore, true));
1276 {
1277 // Being able to see a backdrop blur requires a non-default background
1278 // so we create a new environment for these tests that has a checkerboard
1279 // background that can be blurred by the backdrop filter. We also want
1280 // to avoid the rendered primitive from obscuring the blurred background
1281 // so we set an alpha value which works for all primitives except for
1282 // drawColor which can override the alpha with its color, but it now uses
1283 // a non-opaque color to avoid that problem.
1284 RenderEnvironment backdrop_env =
1285 RenderEnvironment::MakeN32(env.provider());
1286 SkSetup sk_backdrop_setup = [=](const SkSetupContext& ctx) {
1287 SkPaint setup_p;
1288 setup_p.setShader(MakeColorSource(ctx.image));
1289 ctx.canvas->drawPaint(setup_p);
1290 };
1291 DlSetup dl_backdrop_setup = [=](const DlSetupContext& ctx) {
1292 DlPaint setup_p;
1293 setup_p.setColorSource(MakeColorSource(ctx.image));
1294 ctx.canvas->DrawPaint(setup_p);
1295 };
1296 SkSetup sk_content_setup = [=](const SkSetupContext& ctx) {
1297 ctx.paint.setAlpha(ctx.paint.getAlpha() / 2);
1298 };
1299 DlSetup dl_content_setup = [=](const DlSetupContext& ctx) {
1300 ctx.paint.setAlpha(ctx.paint.getAlpha() / 2);
1301 };
1302 backdrop_env.init_ref(sk_backdrop_setup, testP.sk_renderer(),
1303 dl_backdrop_setup, testP.dl_renderer(),
1304 testP.imp_renderer());
1305 quickCompareToReference(backdrop_env, "backdrop");
1306
1307 DlBlurImageFilter dl_backdrop(5, 5, DlTileMode::kDecal);
1308 auto sk_backdrop =
1310 RenderWith(testP, backdrop_env, tolerance,
1312 "saveLayer with backdrop",
1313 [=](const SkSetupContext& ctx) {
1314 sk_backdrop_setup(ctx);
1315 ctx.canvas->saveLayer(SkCanvas::SaveLayerRec(
1316 nullptr, nullptr, sk_backdrop.get(), 0));
1317 sk_content_setup(ctx);
1318 },
1319 [=](const DlSetupContext& ctx) {
1320 dl_backdrop_setup(ctx);
1321 ctx.canvas->SaveLayer(nullptr, nullptr, &dl_backdrop);
1322 dl_content_setup(ctx);
1323 })
1324 .with_restore(sk_safe_restore, dl_safe_restore, true));
1325 RenderWith(testP, backdrop_env, tolerance,
1327 "saveLayer with bounds and backdrop",
1328 [=](const SkSetupContext& ctx) {
1329 sk_backdrop_setup(ctx);
1330 ctx.canvas->saveLayer(SkCanvas::SaveLayerRec(
1331 &layer_bounds, nullptr, sk_backdrop.get(), 0));
1332 sk_content_setup(ctx);
1333 },
1334 [=](const DlSetupContext& ctx) {
1335 dl_backdrop_setup(ctx);
1336 ctx.canvas->SaveLayer(&layer_bounds, nullptr,
1337 &dl_backdrop);
1338 dl_content_setup(ctx);
1339 })
1340 .with_restore(sk_safe_restore, dl_safe_restore, true));
1341 RenderWith(testP, backdrop_env, tolerance,
1343 "clipped saveLayer with backdrop",
1344 [=](const SkSetupContext& ctx) {
1345 sk_backdrop_setup(ctx);
1346 ctx.canvas->clipRect(layer_bounds);
1347 ctx.canvas->saveLayer(SkCanvas::SaveLayerRec(
1348 nullptr, nullptr, sk_backdrop.get(), 0));
1349 sk_content_setup(ctx);
1350 },
1351 [=](const DlSetupContext& ctx) {
1352 dl_backdrop_setup(ctx);
1353 ctx.canvas->ClipRect(layer_bounds);
1354 ctx.canvas->SaveLayer(nullptr, nullptr, &dl_backdrop);
1355 dl_content_setup(ctx);
1356 })
1357 .with_restore(sk_safe_restore, dl_safe_restore, true));
1358 }
1359
1360 {
1361 // clang-format off
1362 constexpr float rotate_alpha_color_matrix[20] = {
1363 0, 1, 0, 0 , 0,
1364 0, 0, 1, 0 , 0,
1365 1, 0, 0, 0 , 0,
1366 0, 0, 0, 0.5, 0,
1367 };
1368 // clang-format on
1369 DlMatrixColorFilter dl_alpha_rotate_filter(rotate_alpha_color_matrix);
1370 auto sk_alpha_rotate_filter =
1371 SkColorFilters::Matrix(rotate_alpha_color_matrix);
1372 {
1373 RenderWith(testP, env, tolerance,
1375 "saveLayer ColorFilter, no bounds",
1376 [=](const SkSetupContext& ctx) {
1377 SkPaint save_p;
1378 save_p.setColorFilter(sk_alpha_rotate_filter);
1379 ctx.canvas->saveLayer(nullptr, &save_p);
1380 ctx.paint.setStrokeWidth(5.0);
1381 },
1382 [=](const DlSetupContext& ctx) {
1383 DlPaint save_p;
1384 save_p.setColorFilter(&dl_alpha_rotate_filter);
1385 ctx.canvas->SaveLayer(nullptr, &save_p);
1386 ctx.paint.setStrokeWidth(5.0);
1387 })
1388 .with_restore(sk_safe_restore, dl_safe_restore, true));
1389 }
1390 {
1391 RenderWith(testP, env, tolerance,
1393 "saveLayer ColorFilter and bounds",
1394 [=](const SkSetupContext& ctx) {
1395 SkPaint save_p;
1396 save_p.setColorFilter(sk_alpha_rotate_filter);
1397 ctx.canvas->saveLayer(kRenderBounds, &save_p);
1398 ctx.paint.setStrokeWidth(5.0);
1399 },
1400 [=](const DlSetupContext& ctx) {
1401 DlPaint save_p;
1402 save_p.setColorFilter(&dl_alpha_rotate_filter);
1403 ctx.canvas->SaveLayer(&kRenderBounds, &save_p);
1404 ctx.paint.setStrokeWidth(5.0);
1405 })
1406 .with_restore(sk_safe_restore, dl_safe_restore, true));
1407 }
1408 }
1409
1410 {
1411 // clang-format off
1412 constexpr float color_matrix[20] = {
1413 0.5, 0, 0, 0, 0.5,
1414 0, 0.5, 0, 0, 0.5,
1415 0, 0, 0.5, 0, 0.5,
1416 0, 0, 0, 1, 0,
1417 };
1418 // clang-format on
1419 DlMatrixColorFilter dl_color_filter(color_matrix);
1420 DlColorFilterImageFilter dl_cf_image_filter(dl_color_filter);
1421 auto sk_cf_image_filter = SkImageFilters::ColorFilter(
1422 SkColorFilters::Matrix(color_matrix), nullptr);
1423 {
1424 RenderWith(testP, env, tolerance,
1426 "saveLayer ImageFilter, no bounds",
1427 [=](const SkSetupContext& ctx) {
1428 SkPaint save_p;
1429 save_p.setImageFilter(sk_cf_image_filter);
1430 ctx.canvas->saveLayer(nullptr, &save_p);
1431 ctx.paint.setStrokeWidth(5.0);
1432 },
1433 [=](const DlSetupContext& ctx) {
1434 DlPaint save_p;
1435 save_p.setImageFilter(&dl_cf_image_filter);
1436 ctx.canvas->SaveLayer(nullptr, &save_p);
1437 ctx.paint.setStrokeWidth(5.0);
1438 })
1439 .with_restore(sk_safe_restore, dl_safe_restore, true));
1440 }
1441 {
1442 RenderWith(testP, env, tolerance,
1444 "saveLayer ImageFilter and bounds",
1445 [=](const SkSetupContext& ctx) {
1446 SkPaint save_p;
1447 save_p.setImageFilter(sk_cf_image_filter);
1448 ctx.canvas->saveLayer(kRenderBounds, &save_p);
1449 ctx.paint.setStrokeWidth(5.0);
1450 },
1451 [=](const DlSetupContext& ctx) {
1452 DlPaint save_p;
1453 save_p.setImageFilter(&dl_cf_image_filter);
1454 ctx.canvas->SaveLayer(&kRenderBounds, &save_p);
1455 ctx.paint.setStrokeWidth(5.0);
1456 })
1457 .with_restore(sk_safe_restore, dl_safe_restore, true));
1458 }
1459 }
1460 }
1461
1462 static void RenderWithAttributes(const TestParameters& testP,
1463 const RenderEnvironment& env,
1464 const BoundsTolerance& tolerance) {
1465 RenderWith(testP, env, tolerance, CaseParameters("Defaults Test"));
1466
1467 {
1468 // CPU renderer with default line width of 0 does not show antialiasing
1469 // for stroked primitives, so we make a new reference with a non-trivial
1470 // stroke width to demonstrate the differences
1472 // Tweak the bounds tolerance for the displacement of 1/10 of a pixel
1473 const BoundsTolerance aa_tolerance = tolerance.addBoundsPadding(1, 1);
1474 auto sk_aa_setup = [=](SkSetupContext ctx, bool is_aa) {
1475 ctx.canvas->translate(0.1, 0.1);
1476 ctx.paint.setAntiAlias(is_aa);
1477 ctx.paint.setStrokeWidth(5.0);
1478 };
1479 auto dl_aa_setup = [=](DlSetupContext ctx, bool is_aa) {
1480 ctx.canvas->Translate(0.1, 0.1);
1481 ctx.paint.setAntiAlias(is_aa);
1482 ctx.paint.setStrokeWidth(5.0);
1483 };
1484 aa_env.init_ref(
1485 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, false); },
1486 testP.sk_renderer(),
1487 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); },
1488 testP.dl_renderer(), testP.imp_renderer());
1489 quickCompareToReference(aa_env, "AntiAlias");
1490 RenderWith(
1491 testP, aa_env, aa_tolerance,
1493 "AntiAlias == True",
1494 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, true); },
1495 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, true); }));
1496 RenderWith(
1497 testP, aa_env, aa_tolerance,
1499 "AntiAlias == False",
1500 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, false); },
1501 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); }));
1502 }
1503
1504 RenderWith( //
1505 testP, env, tolerance,
1507 "Color == Blue",
1508 [=](const SkSetupContext& ctx) {
1509 ctx.paint.setColor(SK_ColorBLUE);
1510 },
1511 [=](const DlSetupContext& ctx) {
1512 ctx.paint.setColor(DlColor::kBlue());
1513 }));
1514 RenderWith( //
1515 testP, env, tolerance,
1517 "Color == Green",
1518 [=](const SkSetupContext& ctx) {
1519 ctx.paint.setColor(SK_ColorGREEN);
1520 },
1521 [=](const DlSetupContext& ctx) {
1522 ctx.paint.setColor(DlColor::kGreen());
1523 }));
1524
1525 RenderWithStrokes(testP, env, tolerance);
1526
1527 {
1528 // half opaque cyan
1529 DlColor blendable_color = DlColor::kCyan().withAlpha(0x7f);
1530 DlColor bg = DlColor::kWhite();
1531
1532 RenderWith(testP, env, tolerance,
1534 "Blend == SrcIn",
1535 [=](const SkSetupContext& ctx) {
1536 ctx.paint.setBlendMode(SkBlendMode::kSrcIn);
1537 ctx.paint.setColor(blendable_color.argb());
1538 },
1539 [=](const DlSetupContext& ctx) {
1540 ctx.paint.setBlendMode(DlBlendMode::kSrcIn);
1541 ctx.paint.setColor(blendable_color);
1542 })
1543 .with_bg(bg));
1544 RenderWith(testP, env, tolerance,
1546 "Blend == DstIn",
1547 [=](const SkSetupContext& ctx) {
1548 ctx.paint.setBlendMode(SkBlendMode::kDstIn);
1549 ctx.paint.setColor(blendable_color.argb());
1550 },
1551 [=](const DlSetupContext& ctx) {
1552 ctx.paint.setBlendMode(DlBlendMode::kDstIn);
1553 ctx.paint.setColor(blendable_color);
1554 })
1555 .with_bg(bg));
1556 }
1557
1558 {
1559 // Being able to see a blur requires some non-default attributes,
1560 // like a non-trivial stroke width and a shader rather than a color
1561 // (for drawPaint) so we create a new environment for these tests.
1562 RenderEnvironment blur_env = RenderEnvironment::MakeN32(env.provider());
1563 SkSetup sk_blur_setup = [=](const SkSetupContext& ctx) {
1564 ctx.paint.setShader(MakeColorSource(ctx.image));
1565 ctx.paint.setStrokeWidth(5.0);
1566 };
1567 DlSetup dl_blur_setup = [=](const DlSetupContext& ctx) {
1568 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1569 ctx.paint.setStrokeWidth(5.0);
1570 };
1571 blur_env.init_ref(sk_blur_setup, testP.sk_renderer(), //
1572 dl_blur_setup, testP.dl_renderer(),
1573 testP.imp_renderer());
1574 quickCompareToReference(blur_env, "blur");
1575 DlBlurImageFilter dl_filter_decal_5(5.0, 5.0, DlTileMode::kDecal);
1576 auto sk_filter_decal_5 =
1577 SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr);
1578 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1579 {
1580 RenderWith(testP, blur_env, blur_5_tolerance,
1582 "ImageFilter == Decal Blur 5",
1583 [=](const SkSetupContext& ctx) {
1584 sk_blur_setup(ctx);
1585 ctx.paint.setImageFilter(sk_filter_decal_5);
1586 },
1587 [=](const DlSetupContext& ctx) {
1588 dl_blur_setup(ctx);
1589 ctx.paint.setImageFilter(&dl_filter_decal_5);
1590 }));
1591 }
1592 DlBlurImageFilter dl_filter_clamp_5(5.0, 5.0, DlTileMode::kClamp);
1593 auto sk_filter_clamp_5 =
1594 SkImageFilters::Blur(5.0, 5.0, SkTileMode::kClamp, nullptr);
1595 {
1596 RenderWith(testP, blur_env, blur_5_tolerance,
1598 "ImageFilter == Clamp Blur 5",
1599 [=](const SkSetupContext& ctx) {
1600 sk_blur_setup(ctx);
1601 ctx.paint.setImageFilter(sk_filter_clamp_5);
1602 },
1603 [=](const DlSetupContext& ctx) {
1604 dl_blur_setup(ctx);
1605 ctx.paint.setImageFilter(&dl_filter_clamp_5);
1606 }));
1607 }
1608 }
1609
1610 {
1611 // Being able to see a dilate requires some non-default attributes,
1612 // like a non-trivial stroke width and a shader rather than a color
1613 // (for drawPaint) so we create a new environment for these tests.
1614 RenderEnvironment dilate_env = RenderEnvironment::MakeN32(env.provider());
1615 SkSetup sk_dilate_setup = [=](const SkSetupContext& ctx) {
1616 ctx.paint.setShader(MakeColorSource(ctx.image));
1617 ctx.paint.setStrokeWidth(5.0);
1618 };
1619 DlSetup dl_dilate_setup = [=](const DlSetupContext& ctx) {
1620 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1621 ctx.paint.setStrokeWidth(5.0);
1622 };
1623 dilate_env.init_ref(sk_dilate_setup, testP.sk_renderer(), //
1624 dl_dilate_setup, testP.dl_renderer(),
1625 testP.imp_renderer());
1626 quickCompareToReference(dilate_env, "dilate");
1627 DlDilateImageFilter dl_dilate_filter_5(5.0, 5.0);
1628 auto sk_dilate_filter_5 = SkImageFilters::Dilate(5.0, 5.0, nullptr);
1629 RenderWith(testP, dilate_env, tolerance,
1631 "ImageFilter == Dilate 5",
1632 [=](const SkSetupContext& ctx) {
1633 sk_dilate_setup(ctx);
1634 ctx.paint.setImageFilter(sk_dilate_filter_5);
1635 },
1636 [=](const DlSetupContext& ctx) {
1637 dl_dilate_setup(ctx);
1638 ctx.paint.setImageFilter(&dl_dilate_filter_5);
1639 }));
1640 }
1641
1642 {
1643 // Being able to see an erode requires some non-default attributes,
1644 // like a non-trivial stroke width and a shader rather than a color
1645 // (for drawPaint) so we create a new environment for these tests.
1646 RenderEnvironment erode_env = RenderEnvironment::MakeN32(env.provider());
1647 SkSetup sk_erode_setup = [=](const SkSetupContext& ctx) {
1648 ctx.paint.setShader(MakeColorSource(ctx.image));
1649 ctx.paint.setStrokeWidth(6.0);
1650 };
1651 DlSetup dl_erode_setup = [=](const DlSetupContext& ctx) {
1652 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1653 ctx.paint.setStrokeWidth(6.0);
1654 };
1655 erode_env.init_ref(sk_erode_setup, testP.sk_renderer(), //
1656 dl_erode_setup, testP.dl_renderer(),
1657 testP.imp_renderer());
1658 quickCompareToReference(erode_env, "erode");
1659 // do not erode too much, because some tests assert there are enough
1660 // pixels that are changed.
1661 DlErodeImageFilter dl_erode_filter_1(1.0, 1.0);
1662 auto sk_erode_filter_1 = SkImageFilters::Erode(1.0, 1.0, nullptr);
1663 RenderWith(testP, erode_env, tolerance,
1665 "ImageFilter == Erode 1",
1666 [=](const SkSetupContext& ctx) {
1667 sk_erode_setup(ctx);
1668 ctx.paint.setImageFilter(sk_erode_filter_1);
1669 },
1670 [=](const DlSetupContext& ctx) {
1671 dl_erode_setup(ctx);
1672 ctx.paint.setImageFilter(&dl_erode_filter_1);
1673 }));
1674 }
1675
1676 {
1677 // clang-format off
1678 constexpr float rotate_color_matrix[20] = {
1679 0, 1, 0, 0, 0,
1680 0, 0, 1, 0, 0,
1681 1, 0, 0, 0, 0,
1682 0, 0, 0, 1, 0,
1683 };
1684 constexpr float invert_color_matrix[20] = {
1685 -1.0, 0, 0, 1.0, 0,
1686 0, -1.0, 0, 1.0, 0,
1687 0, 0, -1.0, 1.0, 0,
1688 1.0, 1.0, 1.0, 1.0, 0,
1689 };
1690 // clang-format on
1691 DlMatrixColorFilter dl_color_filter(rotate_color_matrix);
1692 auto sk_color_filter = SkColorFilters::Matrix(rotate_color_matrix);
1693 {
1694 DlColor bg = DlColor::kWhite();
1695 RenderWith(testP, env, tolerance,
1697 "ColorFilter == RotateRGB",
1698 [=](const SkSetupContext& ctx) {
1699 ctx.paint.setColor(SK_ColorYELLOW);
1700 ctx.paint.setColorFilter(sk_color_filter);
1701 },
1702 [=](const DlSetupContext& ctx) {
1703 ctx.paint.setColor(DlColor::kYellow());
1704 ctx.paint.setColorFilter(&dl_color_filter);
1705 })
1706 .with_bg(bg));
1707 }
1708 {
1709 DlColor bg = DlColor::kWhite();
1710 RenderWith(testP, env, tolerance,
1712 "ColorFilter == Invert",
1713 [=](const SkSetupContext& ctx) {
1714 ctx.paint.setColor(SK_ColorYELLOW);
1715 ctx.paint.setColorFilter(
1716 SkColorFilters::Matrix(invert_color_matrix));
1717 },
1718 [=](const DlSetupContext& ctx) {
1719 ctx.paint.setColor(DlColor::kYellow());
1720 ctx.paint.setInvertColors(true);
1721 })
1722 .with_bg(bg));
1723 }
1724 }
1725
1726 {
1727 const DlBlurMaskFilter dl_mask_filter(DlBlurStyle::kNormal, 5.0);
1728 auto sk_mask_filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0);
1729 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1730 {
1731 // Stroked primitives need some non-trivial stroke size to be blurred
1732 RenderWith(testP, env, blur_5_tolerance,
1734 "MaskFilter == Blur 5",
1735 [=](const SkSetupContext& ctx) {
1736 ctx.paint.setStrokeWidth(5.0);
1737 ctx.paint.setMaskFilter(sk_mask_filter);
1738 },
1739 [=](const DlSetupContext& ctx) {
1740 ctx.paint.setStrokeWidth(5.0);
1741 ctx.paint.setMaskFilter(&dl_mask_filter);
1742 }));
1743 }
1744 }
1745
1746 {
1747 SkPoint end_points[] = {
1750 };
1751 DlColor dl_colors[] = {
1755 };
1756 SkColor sk_colors[] = {
1760 };
1761 float stops[] = {
1762 0.0,
1763 0.5,
1764 1.0,
1765 };
1766 auto dl_gradient =
1767 DlColorSource::MakeLinear(end_points[0], end_points[1], 3, dl_colors,
1768 stops, DlTileMode::kMirror);
1769 auto sk_gradient = SkGradientShader::MakeLinear(
1770 end_points, sk_colors, stops, 3, SkTileMode::kMirror, 0, nullptr);
1771 {
1772 RenderWith(testP, env, tolerance,
1774 "LinearGradient GYB",
1775 [=](const SkSetupContext& ctx) {
1776 ctx.paint.setShader(sk_gradient);
1777 ctx.paint.setDither(testP.uses_gradient());
1778 },
1779 [=](const DlSetupContext& ctx) {
1780 ctx.paint.setColorSource(dl_gradient);
1781 }));
1782 }
1783 }
1784 }
1785
1786 static void RenderWithStrokes(const TestParameters& testP,
1787 const RenderEnvironment& env,
1788 const BoundsTolerance& tolerance_in) {
1789 // The test cases were generated with geometry that will try to fill
1790 // out the various miter limits used for testing, but they can be off
1791 // by a couple of pixels so we will relax bounds testing for strokes by
1792 // a couple of pixels.
1793 BoundsTolerance tolerance = tolerance_in.addBoundsPadding(2, 2);
1794 RenderWith(testP, env, tolerance,
1796 "Fill",
1797 [=](const SkSetupContext& ctx) {
1798 ctx.paint.setStyle(SkPaint::kFill_Style);
1799 },
1800 [=](const DlSetupContext& ctx) {
1801 ctx.paint.setDrawStyle(DlDrawStyle::kFill);
1802 }));
1803 // Skia on HW produces a strong miter consistent with width=1.0
1804 // for any width less than a pixel, but the bounds computations of
1805 // both DL and SkPicture do not account for this. We will get
1806 // OOB pixel errors for the highly mitered drawPath geometry if
1807 // we don't set stroke width to 1.0 for that test on HW.
1808 // See https://bugs.chromium.org/p/skia/issues/detail?id=14046
1809 bool no_hairlines =
1810 testP.is_draw_path() &&
1811 env.provider()->backend_type() != BackendType::kSoftwareBackend;
1812 RenderWith(testP, env, tolerance,
1814 "Stroke + defaults",
1815 [=](const SkSetupContext& ctx) {
1816 if (no_hairlines) {
1817 ctx.paint.setStrokeWidth(1.0);
1818 }
1819 ctx.paint.setStyle(SkPaint::kStroke_Style);
1820 },
1821 [=](const DlSetupContext& ctx) {
1822 if (no_hairlines) {
1823 ctx.paint.setStrokeWidth(1.0);
1824 }
1825 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1826 }));
1827
1828 RenderWith(testP, env, tolerance,
1830 "Fill + unnecessary StrokeWidth 10",
1831 [=](const SkSetupContext& ctx) {
1832 ctx.paint.setStyle(SkPaint::kFill_Style);
1833 ctx.paint.setStrokeWidth(10.0);
1834 },
1835 [=](const DlSetupContext& ctx) {
1836 ctx.paint.setDrawStyle(DlDrawStyle::kFill);
1837 ctx.paint.setStrokeWidth(10.0);
1838 }));
1839
1840 RenderEnvironment stroke_base_env =
1841 RenderEnvironment::MakeN32(env.provider());
1842 SkSetup sk_stroke_setup = [=](const SkSetupContext& ctx) {
1843 ctx.paint.setStyle(SkPaint::kStroke_Style);
1844 ctx.paint.setStrokeWidth(5.0);
1845 };
1846 DlSetup dl_stroke_setup = [=](const DlSetupContext& ctx) {
1847 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1848 ctx.paint.setStrokeWidth(5.0);
1849 };
1850 stroke_base_env.init_ref(sk_stroke_setup, testP.sk_renderer(),
1851 dl_stroke_setup, testP.dl_renderer(),
1852 testP.imp_renderer());
1853 quickCompareToReference(stroke_base_env, "stroke");
1854
1855 RenderWith(testP, stroke_base_env, tolerance,
1857 "Stroke Width 10",
1858 [=](const SkSetupContext& ctx) {
1859 ctx.paint.setStyle(SkPaint::kStroke_Style);
1860 ctx.paint.setStrokeWidth(10.0);
1861 },
1862 [=](const DlSetupContext& ctx) {
1863 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1864 ctx.paint.setStrokeWidth(10.0);
1865 }));
1866 RenderWith(testP, stroke_base_env, tolerance,
1868 "Stroke Width 5",
1869 [=](const SkSetupContext& ctx) {
1870 ctx.paint.setStyle(SkPaint::kStroke_Style);
1871 ctx.paint.setStrokeWidth(5.0);
1872 },
1873 [=](const DlSetupContext& ctx) {
1874 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1875 ctx.paint.setStrokeWidth(5.0);
1876 }));
1877
1878 RenderWith(testP, stroke_base_env, tolerance,
1880 "Stroke Width 5, Square Cap",
1881 [=](const SkSetupContext& ctx) {
1882 ctx.paint.setStyle(SkPaint::kStroke_Style);
1883 ctx.paint.setStrokeWidth(5.0);
1884 ctx.paint.setStrokeCap(SkPaint::kSquare_Cap);
1885 },
1886 [=](const DlSetupContext& ctx) {
1887 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1888 ctx.paint.setStrokeWidth(5.0);
1889 ctx.paint.setStrokeCap(DlStrokeCap::kSquare);
1890 }));
1891 RenderWith(testP, stroke_base_env, tolerance,
1893 "Stroke Width 5, Round Cap",
1894 [=](const SkSetupContext& ctx) {
1895 ctx.paint.setStyle(SkPaint::kStroke_Style);
1896 ctx.paint.setStrokeWidth(5.0);
1897 ctx.paint.setStrokeCap(SkPaint::kRound_Cap);
1898 },
1899 [=](const DlSetupContext& ctx) {
1900 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1901 ctx.paint.setStrokeWidth(5.0);
1902 ctx.paint.setStrokeCap(DlStrokeCap::kRound);
1903 }));
1904
1905 RenderWith(testP, stroke_base_env, tolerance,
1907 "Stroke Width 5, Bevel Join",
1908 [=](const SkSetupContext& ctx) {
1909 ctx.paint.setStyle(SkPaint::kStroke_Style);
1910 ctx.paint.setStrokeWidth(5.0);
1911 ctx.paint.setStrokeJoin(SkPaint::kBevel_Join);
1912 },
1913 [=](const DlSetupContext& ctx) {
1914 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1915 ctx.paint.setStrokeWidth(5.0);
1916 ctx.paint.setStrokeJoin(DlStrokeJoin::kBevel);
1917 }));
1918 RenderWith(testP, stroke_base_env, tolerance,
1920 "Stroke Width 5, Round Join",
1921 [=](const SkSetupContext& ctx) {
1922 ctx.paint.setStyle(SkPaint::kStroke_Style);
1923 ctx.paint.setStrokeWidth(5.0);
1924 ctx.paint.setStrokeJoin(SkPaint::kRound_Join);
1925 },
1926 [=](const DlSetupContext& ctx) {
1927 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1928 ctx.paint.setStrokeWidth(5.0);
1929 ctx.paint.setStrokeJoin(DlStrokeJoin::kRound);
1930 }));
1931
1932 RenderWith(testP, stroke_base_env, tolerance,
1934 "Stroke Width 5, Miter 10",
1935 [=](const SkSetupContext& ctx) {
1936 ctx.paint.setStyle(SkPaint::kStroke_Style);
1937 ctx.paint.setStrokeWidth(5.0);
1938 ctx.paint.setStrokeMiter(10.0);
1939 ctx.paint.setStrokeJoin(SkPaint::kMiter_Join);
1940 },
1941 [=](const DlSetupContext& ctx) {
1942 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1943 ctx.paint.setStrokeWidth(5.0);
1944 ctx.paint.setStrokeMiter(10.0);
1945 ctx.paint.setStrokeJoin(DlStrokeJoin::kMiter);
1946 }));
1947
1948 RenderWith(testP, stroke_base_env, tolerance,
1950 "Stroke Width 5, Miter 0",
1951 [=](const SkSetupContext& ctx) {
1952 ctx.paint.setStyle(SkPaint::kStroke_Style);
1953 ctx.paint.setStrokeWidth(5.0);
1954 ctx.paint.setStrokeMiter(0.0);
1955 ctx.paint.setStrokeJoin(SkPaint::kMiter_Join);
1956 },
1957 [=](const DlSetupContext& ctx) {
1958 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1959 ctx.paint.setStrokeWidth(5.0);
1960 ctx.paint.setStrokeMiter(0.0);
1961 ctx.paint.setStrokeJoin(DlStrokeJoin::kMiter);
1962 }));
1963 }
1964
1965 static void RenderWithTransforms(const TestParameters& testP,
1966 const RenderEnvironment& env,
1967 const BoundsTolerance& tolerance) {
1968 // If the rendering method does not fill the corners of the original
1969 // bounds, then the estimate under rotation or skewing will be off
1970 // so we scale the padding by about 5% to compensate.
1971 BoundsTolerance skewed_tolerance = tolerance.mulScale(1.05, 1.05);
1972 RenderWith( //
1973 testP, env, tolerance,
1975 "Translate 5, 10", //
1976 [=](const SkSetupContext& ctx) { ctx.canvas->translate(5, 10); },
1977 [=](const DlSetupContext& ctx) { ctx.canvas->Translate(5, 10); }));
1978 RenderWith( //
1979 testP, env, tolerance,
1981 "Scale +5%", //
1982 [=](const SkSetupContext& ctx) { ctx.canvas->scale(1.05, 1.05); },
1983 [=](const DlSetupContext& ctx) { ctx.canvas->Scale(1.05, 1.05); }));
1984 RenderWith( //
1985 testP, env, skewed_tolerance,
1987 "Rotate 5 degrees", //
1988 [=](const SkSetupContext& ctx) { ctx.canvas->rotate(5); },
1989 [=](const DlSetupContext& ctx) { ctx.canvas->Rotate(5); }));
1990 RenderWith( //
1991 testP, env, skewed_tolerance,
1993 "Skew 5%", //
1994 [=](const SkSetupContext& ctx) { ctx.canvas->skew(0.05, 0.05); },
1995 [=](const DlSetupContext& ctx) { ctx.canvas->Skew(0.05, 0.05); }));
1996 {
1997 // This rather odd transform can cause slight differences in
1998 // computing in-bounds samples depending on which base rendering
1999 // routine Skia uses. Making sure our matrix values are powers
2000 // of 2 reduces, but does not eliminate, these slight differences
2001 // in calculation when we are comparing rendering with an alpha
2002 // to rendering opaque colors in the group opacity tests, for
2003 // example.
2004 SkScalar tweak = 1.0 / 16.0;
2005 SkMatrix tx = SkMatrix::MakeAll(1.0 + tweak, tweak, 5, //
2006 tweak, 1.0 + tweak, 10, //
2007 0, 0, 1);
2008 RenderWith( //
2009 testP, env, skewed_tolerance,
2011 "Transform 2D Affine",
2012 [=](const SkSetupContext& ctx) { ctx.canvas->concat(tx); },
2013 [=](const DlSetupContext& ctx) { ctx.canvas->Transform(tx); }));
2014 }
2015 {
2016 SkM44 m44 = SkM44(1, 0, 0, kRenderCenterX, //
2017 0, 1, 0, kRenderCenterY, //
2018 0, 0, 1, 0, //
2019 0, 0, .001, 1);
2020 m44.preConcat(
2021 SkM44::Rotate({1, 0, 0}, math::kPi / 60)); // 3 degrees around X
2022 m44.preConcat(
2023 SkM44::Rotate({0, 1, 0}, math::kPi / 45)); // 4 degrees around Y
2025 RenderWith( //
2026 testP, env, skewed_tolerance,
2028 "Transform Full Perspective",
2029 [=](const SkSetupContext& ctx) { ctx.canvas->concat(m44); },
2030 [=](const DlSetupContext& ctx) { ctx.canvas->Transform(m44); }));
2031 }
2032 }
2033
2034 static void RenderWithClips(const TestParameters& testP,
2035 const RenderEnvironment& env,
2036 const BoundsTolerance& diff_tolerance) {
2037 // We used to use an inset of 15.5 pixels here, but since Skia's rounding
2038 // behavior at the center of pixels does not match between HW and SW, we
2039 // ended up with some clips including different pixels between the two
2040 // destinations and this interacted poorly with the carefully chosen
2041 // geometry in some of the tests which was designed to have just the
2042 // right features fully filling the clips based on the SW rounding. By
2043 // moving to a 15.4 inset, the edge of the clip is never on the "rounding
2044 // edge" of a pixel.
2045 SkRect r_clip = kRenderBounds.makeInset(15.4, 15.4);
2046 BoundsTolerance intersect_tolerance = diff_tolerance.clip(r_clip);
2047 intersect_tolerance = intersect_tolerance.addPostClipPadding(1, 1);
2048 RenderWith(testP, env, intersect_tolerance,
2050 "Hard ClipRect inset by 15.4",
2051 [=](const SkSetupContext& ctx) {
2052 ctx.canvas->clipRect(r_clip, SkClipOp::kIntersect, false);
2053 },
2054 [=](const DlSetupContext& ctx) {
2055 ctx.canvas->ClipRect(r_clip, ClipOp::kIntersect, false);
2056 }));
2057 RenderWith(testP, env, intersect_tolerance,
2059 "AntiAlias ClipRect inset by 15.4",
2060 [=](const SkSetupContext& ctx) {
2061 ctx.canvas->clipRect(r_clip, SkClipOp::kIntersect, true);
2062 },
2063 [=](const DlSetupContext& ctx) {
2064 ctx.canvas->ClipRect(r_clip, ClipOp::kIntersect, true);
2065 }));
2066 RenderWith(testP, env, diff_tolerance,
2068 "Hard ClipRect Diff, inset by 15.4",
2069 [=](const SkSetupContext& ctx) {
2070 ctx.canvas->clipRect(r_clip, SkClipOp::kDifference, false);
2071 },
2072 [=](const DlSetupContext& ctx) {
2073 ctx.canvas->ClipRect(r_clip, ClipOp::kDifference, false);
2074 })
2075 .with_diff_clip());
2076 // This test RR clip used to use very small radii, but due to
2077 // optimizations in the HW rrect rasterization, this caused small
2078 // bulges in the corners of the RRect which were interpreted as
2079 // "clip overruns" by the clip OOB pixel testing code. Using less
2080 // abusively small radii fixes the problem.
2081 SkRRect rr_clip = SkRRect::MakeRectXY(r_clip, 9, 9);
2082 RenderWith(testP, env, intersect_tolerance,
2084 "Hard ClipRRect with radius of 15.4",
2085 [=](const SkSetupContext& ctx) {
2086 ctx.canvas->clipRRect(rr_clip, SkClipOp::kIntersect,
2087 false);
2088 },
2089 [=](const DlSetupContext& ctx) {
2090 ctx.canvas->ClipRRect(rr_clip, ClipOp::kIntersect, false);
2091 }));
2092 RenderWith(testP, env, intersect_tolerance,
2094 "AntiAlias ClipRRect with radius of 15.4",
2095 [=](const SkSetupContext& ctx) {
2096 ctx.canvas->clipRRect(rr_clip, SkClipOp::kIntersect, true);
2097 },
2098 [=](const DlSetupContext& ctx) {
2099 ctx.canvas->ClipRRect(rr_clip, ClipOp::kIntersect, true);
2100 }));
2101 RenderWith(testP, env, diff_tolerance,
2103 "Hard ClipRRect Diff, with radius of 15.4",
2104 [=](const SkSetupContext& ctx) {
2105 ctx.canvas->clipRRect(rr_clip, SkClipOp::kDifference,
2106 false);
2107 },
2108 [=](const DlSetupContext& ctx) {
2109 ctx.canvas->ClipRRect(rr_clip, ClipOp::kDifference, false);
2110 })
2111 .with_diff_clip());
2112 SkPath path_clip = SkPath();
2113 path_clip.setFillType(SkPathFillType::kEvenOdd);
2114 path_clip.addRect(r_clip);
2115 path_clip.addCircle(kRenderCenterX, kRenderCenterY, 1.0);
2116 RenderWith(testP, env, intersect_tolerance,
2118 "Hard ClipPath inset by 15.4",
2119 [=](const SkSetupContext& ctx) {
2120 ctx.canvas->clipPath(path_clip, SkClipOp::kIntersect,
2121 false);
2122 },
2123 [=](const DlSetupContext& ctx) {
2124 ctx.canvas->ClipPath(path_clip, ClipOp::kIntersect, false);
2125 }));
2126 RenderWith(testP, env, intersect_tolerance,
2128 "AntiAlias ClipPath inset by 15.4",
2129 [=](const SkSetupContext& ctx) {
2130 ctx.canvas->clipPath(path_clip, SkClipOp::kIntersect,
2131 true);
2132 },
2133 [=](const DlSetupContext& ctx) {
2134 ctx.canvas->ClipPath(path_clip, ClipOp::kIntersect, true);
2135 }));
2136 RenderWith(
2137 testP, env, diff_tolerance,
2139 "Hard ClipPath Diff, inset by 15.4",
2140 [=](const SkSetupContext& ctx) {
2141 ctx.canvas->clipPath(path_clip, SkClipOp::kDifference, false);
2142 },
2143 [=](const DlSetupContext& ctx) {
2144 ctx.canvas->ClipPath(path_clip, ClipOp::kDifference, false);
2145 })
2146 .with_diff_clip());
2147 }
2148
2149 enum class DirectoryStatus {
2150 kExisted,
2151 kCreated,
2152 kFailed,
2153 };
2154
2155 static DirectoryStatus CheckDir(const std::string& dir) {
2156 auto ret =
2158 if (ret.is_valid()) {
2160 }
2161 ret =
2163 if (ret.is_valid()) {
2165 }
2166 FML_LOG(ERROR) << "Could not create directory (" << dir
2167 << ") for impeller failure images" << ", ret = " << ret.get()
2168 << ", errno = " << errno;
2170 }
2171
2173 std::string base_dir = "./impeller_failure_images";
2174 if (CheckDir(base_dir) == DirectoryStatus::kFailed) {
2175 return;
2176 }
2177 for (int i = 0; i < 10000; i++) {
2178 std::string sub_dir = std::to_string(i);
2179 while (sub_dir.length() < 4) {
2180 sub_dir = "0" + sub_dir;
2181 }
2182 std::string try_dir = base_dir + "/" + sub_dir;
2183 switch (CheckDir(try_dir)) {
2185 break;
2188 return;
2190 return;
2191 }
2192 }
2193 FML_LOG(ERROR) << "Too many output directories for Impeller failure images";
2194 }
2195
2196 static void save_to_png(const RenderResult* result,
2197 const std::string& op_desc,
2198 const std::string& reason) {
2200 return;
2201 }
2202 if (ImpellerFailureImageDirectory.length() == 0) {
2204 if (ImpellerFailureImageDirectory.length() == 0) {
2206 return;
2207 }
2208 }
2209
2210 std::string filename = ImpellerFailureImageDirectory + "/";
2211 for (const char& ch : op_desc) {
2212 filename += (ch == ':' || ch == ' ') ? '_' : ch;
2213 }
2214 filename = filename + ".png";
2215 result->write(filename);
2216 ImpellerFailureImages.push_back(filename);
2217 FML_LOG(ERROR) << reason << ": " << filename;
2218 }
2219
2220 static void RenderWith(const TestParameters& testP,
2221 const RenderEnvironment& env,
2222 const BoundsTolerance& tolerance_in,
2223 const CaseParameters& caseP) {
2224 std::string test_name =
2225 ::testing::UnitTest::GetInstance()->current_test_info()->name();
2226 const std::string info =
2227 env.backend_name() + ": " + test_name + " (" + caseP.info() + ")";
2228 const DlColor bg = caseP.bg();
2229 RenderJobInfo base_info = {
2230 .bg = bg,
2231 };
2232
2233 // sk_result is a direct rendering via SkCanvas to SkSurface
2234 // DisplayList mechanisms are not involved in this operation
2235 SkJobRenderer sk_job(caseP.sk_setup(), //
2236 testP.sk_renderer(), //
2237 caseP.sk_restore(), //
2238 env.sk_image());
2239 auto sk_result = env.getResult(base_info, sk_job);
2240
2241 DlJobRenderer dl_job(caseP.dl_setup(), //
2242 testP.dl_renderer(), //
2243 caseP.dl_restore(), //
2244 env.dl_image());
2245 auto dl_result = env.getResult(base_info, dl_job);
2246
2247 EXPECT_EQ(sk_job.setup_matrix(), dl_job.setup_matrix());
2248 EXPECT_EQ(sk_job.setup_clip_bounds(), dl_job.setup_clip_bounds());
2249 ASSERT_EQ(sk_result->width(), kTestWidth) << info;
2250 ASSERT_EQ(sk_result->height(), kTestHeight) << info;
2251 ASSERT_EQ(dl_result->width(), kTestWidth) << info;
2252 ASSERT_EQ(dl_result->height(), kTestHeight) << info;
2253
2254 const BoundsTolerance tolerance =
2255 testP.adjust(tolerance_in, dl_job.setup_paint(), dl_job.setup_matrix());
2256 const sk_sp<SkPicture> sk_picture = sk_job.MakePicture(base_info);
2257 const sk_sp<DisplayList> display_list = dl_job.MakeDisplayList(base_info);
2258
2259 SkRect sk_bounds = sk_picture->cullRect();
2260 checkPixels(sk_result.get(), sk_bounds, info + " (Skia reference)", bg);
2261
2262 if (testP.should_match(env, caseP, dl_job.setup_paint(), dl_job)) {
2263 quickCompareToReference(env.ref_sk_result(), sk_result.get(), true,
2264 info + " (attribute should not have effect)");
2265 } else {
2266 quickCompareToReference(env.ref_sk_result(), sk_result.get(), false,
2267 info + " (attribute should affect rendering)");
2268 }
2269
2270 // If either the reference setup or the test setup contain attributes
2271 // that Impeller doesn't support, we skip the Impeller testing. This
2272 // is mostly stroked or patterned text which is vectored through drawPath
2273 // for Impeller.
2274 if (env.supports_impeller() &&
2275 testP.impeller_compatible(dl_job.setup_paint()) &&
2276 testP.impeller_compatible(env.ref_dl_paint())) {
2277 DlJobRenderer imp_job(caseP.dl_setup(), //
2278 testP.imp_renderer(), //
2279 caseP.dl_restore(), //
2280 env.impeller_image());
2281 auto imp_result = env.getImpellerResult(base_info, imp_job);
2282 std::string imp_info = info + " (Impeller)";
2283 bool success = checkPixels(imp_result.get(), imp_result->render_bounds(),
2284 imp_info, bg);
2285 if (testP.should_match(env, caseP, imp_job.setup_paint(), imp_job)) {
2286 success = success && //
2288 env.ref_impeller_result(), imp_result.get(), true,
2289 imp_info + " (attribute should not have effect)");
2290 } else {
2291 success = success && //
2293 env.ref_impeller_result(), imp_result.get(), false,
2294 imp_info + " (attribute should affect rendering)");
2295 }
2296 if (SaveImpellerFailureImages && !success) {
2297 FML_LOG(ERROR) << "Impeller issue encountered for: "
2298 << *imp_job.MakeDisplayList(base_info);
2299 save_to_png(imp_result.get(), info + " (Impeller Result)",
2300 "output saved in");
2301 save_to_png(env.ref_impeller_result(), info + " (Impeller Reference)",
2302 "compare to reference without attributes");
2303 save_to_png(sk_result.get(), info + " (Skia Result)",
2304 "and to Skia reference with attributes");
2305 save_to_png(env.ref_sk_result(), info + " (Skia Reference)",
2306 "and to Skia reference without attributes");
2307 }
2308 }
2309
2310 quickCompareToReference(sk_result.get(), dl_result.get(), true,
2311 info + " (DlCanvas output matches SkCanvas)");
2312
2313 {
2314 SkRect dl_bounds = display_list->bounds();
2315 if (!sk_bounds.roundOut().contains(dl_bounds)) {
2316 FML_LOG(ERROR) << "For " << info;
2317 FML_LOG(ERROR) << "sk ref: " //
2318 << sk_bounds.fLeft << ", " << sk_bounds.fTop << " => "
2319 << sk_bounds.fRight << ", " << sk_bounds.fBottom;
2320 FML_LOG(ERROR) << "dl: " //
2321 << dl_bounds.fLeft << ", " << dl_bounds.fTop << " => "
2322 << dl_bounds.fRight << ", " << dl_bounds.fBottom;
2323 if (!dl_bounds.contains(sk_bounds)) {
2324 FML_LOG(ERROR) << "DisplayList bounds are too small!";
2325 }
2326 if (!dl_bounds.isEmpty() &&
2327 !sk_bounds.roundOut().contains(dl_bounds.roundOut())) {
2328 FML_LOG(ERROR) << "###### DisplayList bounds larger than reference!";
2329 }
2330 }
2331
2332 // This EXPECT sometimes triggers, but when it triggers and I examine
2333 // the ref_bounds, they are always unnecessarily large and since the
2334 // pixel OOB tests in the compare method do not trigger, we will trust
2335 // the DL bounds.
2336 // EXPECT_TRUE(dl_bounds.contains(ref_bounds)) << info;
2337
2338 // When we are drawing a DisplayList, the display_list built above
2339 // will contain just a single drawDisplayList call plus the case
2340 // attribute. The sk_picture will, however, contain a list of all
2341 // of the embedded calls in the display list and so the op counts
2342 // will not be equal between the two.
2343 if (!testP.is_draw_display_list()) {
2344 EXPECT_EQ(static_cast<int>(display_list->op_count()),
2345 sk_picture->approximateOpCount())
2346 << info;
2347 EXPECT_EQ(static_cast<int>(display_list->op_count()),
2348 sk_picture->approximateOpCount())
2349 << info;
2350 }
2351
2352 DisplayListJobRenderer dl_builder_job(display_list);
2353 auto dl_builder_result = env.getResult(base_info, dl_builder_job);
2354 if (caseP.fuzzy_compare_components()) {
2356 dl_builder_result.get(), dl_result.get(),
2357 info + " (DlCanvas DL output close to Builder Dl output)",
2358 &dl_bounds, &tolerance, bg, true);
2359 } else {
2361 dl_builder_result.get(), dl_result.get(), true,
2362 info + " (DlCanvas DL output matches Builder Dl output)");
2363 }
2364
2365 compareToReference(dl_result.get(), sk_result.get(),
2366 info + " (DisplayList built directly -> surface)",
2367 &dl_bounds, &tolerance, bg,
2368 caseP.fuzzy_compare_components());
2369
2370 if (display_list->can_apply_group_opacity()) {
2371 checkGroupOpacity(env, display_list, dl_result.get(),
2372 info + " with Group Opacity", bg);
2373 }
2374 }
2375
2376 {
2377 // This sequence uses an SkPicture generated previously from the SkCanvas
2378 // calls and a DisplayList generated previously from the DlCanvas calls
2379 // and renders both back under a transform (scale(2x)) to see if their
2380 // rendering is affected differently by a change of matrix between
2381 // recording time and rendering time.
2382 const int test_width_2 = kTestWidth * 2;
2383 const int test_height_2 = kTestHeight * 2;
2384 const SkScalar test_scale = 2.0;
2385
2386 SkPictureJobRenderer sk_job_x2(sk_picture);
2387 RenderJobInfo info_2x = {
2388 .width = test_width_2,
2389 .height = test_height_2,
2390 .bg = bg,
2391 .scale = test_scale,
2392 };
2393 auto ref_x2_result = env.getResult(info_2x, sk_job_x2);
2394 ASSERT_EQ(ref_x2_result->width(), test_width_2) << info;
2395 ASSERT_EQ(ref_x2_result->height(), test_height_2) << info;
2396
2397 DisplayListJobRenderer dl_job_x2(display_list);
2398 auto test_x2_result = env.getResult(info_2x, dl_job_x2);
2399 compareToReference(test_x2_result.get(), ref_x2_result.get(),
2400 info + " (Both rendered scaled 2x)", nullptr, nullptr,
2401 bg, caseP.fuzzy_compare_components(), //
2402 test_width_2, test_height_2, false);
2403 }
2404 }
2405
2406 static bool fuzzyCompare(uint32_t pixel_a, uint32_t pixel_b, int fudge) {
2407 for (int i = 0; i < 32; i += 8) {
2408 int comp_a = (pixel_a >> i) & 0xff;
2409 int comp_b = (pixel_b >> i) & 0xff;
2410 if (std::abs(comp_a - comp_b) > fudge) {
2411 return false;
2412 }
2413 }
2414 return true;
2415 }
2416
2418 if (env.format() == PixelFormat::k565PixelFormat) {
2419 return 9;
2420 }
2421 if (env.provider()->backend_type() == BackendType::kOpenGlBackend) {
2422 // OpenGL gets a little fuzzy at times. Still, "within 5" (aka +/-4)
2423 // for byte samples is not bad, though the other backends give +/-1
2424 return 5;
2425 }
2426 return 2;
2427 }
2429 const sk_sp<DisplayList>& display_list,
2430 const RenderResult* ref_result,
2431 const std::string& info,
2432 DlColor bg) {
2433 SkScalar opacity = 128.0 / 255.0;
2434
2435 DisplayListJobRenderer opacity_job(display_list);
2436 RenderJobInfo opacity_info = {
2437 .bg = bg,
2438 .opacity = opacity,
2439 };
2440 auto group_opacity_result = env.getResult(opacity_info, opacity_job);
2441
2442 ASSERT_EQ(group_opacity_result->width(), kTestWidth) << info;
2443 ASSERT_EQ(group_opacity_result->height(), kTestHeight) << info;
2444
2445 ASSERT_EQ(ref_result->width(), kTestWidth) << info;
2446 ASSERT_EQ(ref_result->height(), kTestHeight) << info;
2447
2448 int pixels_touched = 0;
2449 int pixels_different = 0;
2450 int max_diff = 0;
2451 // We need to allow some slight differences per component due to the
2452 // fact that rearranging discrete calculations can compound round off
2453 // errors. Off-by-2 is enough for 8 bit components, but for the 565
2454 // tests we allow at least 9 which is the maximum distance between
2455 // samples when converted to 8 bits. (You might think it would be a
2456 // max step of 8 converting 5 bits to 8 bits, but it is really
2457 // converting 31 steps to 255 steps with an average step size of
2458 // 8.23 - 24 of the steps are by 8, but 7 of them are by 9.)
2459 int fudge = groupOpacityFudgeFactor(env);
2460 for (int y = 0; y < kTestHeight; y++) {
2461 const uint32_t* ref_row = ref_result->addr32(0, y);
2462 const uint32_t* test_row = group_opacity_result->addr32(0, y);
2463 for (int x = 0; x < kTestWidth; x++) {
2464 uint32_t ref_pixel = ref_row[x];
2465 uint32_t test_pixel = test_row[x];
2466 if (ref_pixel != bg.argb() || test_pixel != bg.argb()) {
2467 pixels_touched++;
2468 for (int i = 0; i < 32; i += 8) {
2469 int ref_comp = (ref_pixel >> i) & 0xff;
2470 int bg_comp = (bg.argb() >> i) & 0xff;
2471 SkScalar faded_comp = bg_comp + (ref_comp - bg_comp) * opacity;
2472 int test_comp = (test_pixel >> i) & 0xff;
2473 if (std::abs(faded_comp - test_comp) > fudge) {
2474 int diff = std::abs(faded_comp - test_comp);
2475 if (max_diff < diff) {
2476 max_diff = diff;
2477 }
2478 pixels_different++;
2479 break;
2480 }
2481 }
2482 }
2483 }
2484 }
2485 ASSERT_GT(pixels_touched, 20) << info;
2486 if (pixels_different > 1) {
2487 FML_LOG(ERROR) << "max diff == " << max_diff << " for " << info;
2488 }
2489 ASSERT_LE(pixels_different, 1) << info;
2490 }
2491
2492 static bool checkPixels(const RenderResult* ref_result,
2493 const SkRect ref_bounds,
2494 const std::string& info,
2495 const DlColor bg = DlColor::kTransparent()) {
2496 uint32_t untouched = bg.premultipliedArgb();
2497 int pixels_touched = 0;
2498 int pixels_oob = 0;
2499 SkIRect i_bounds = ref_bounds.roundOut();
2500 EXPECT_EQ(ref_result->width(), kTestWidth) << info;
2501 EXPECT_EQ(ref_result->height(), kTestWidth) << info;
2502 for (int y = 0; y < kTestHeight; y++) {
2503 const uint32_t* ref_row = ref_result->addr32(0, y);
2504 for (int x = 0; x < kTestWidth; x++) {
2505 if (ref_row[x] != untouched) {
2506 pixels_touched++;
2507 if (!i_bounds.contains(x, y)) {
2508 pixels_oob++;
2509 }
2510 }
2511 }
2512 }
2513 EXPECT_EQ(pixels_oob, 0) << info;
2514 EXPECT_GT(pixels_touched, 0) << info;
2515 return pixels_oob == 0 && pixels_touched > 0;
2516 }
2517
2518 static int countModifiedTransparentPixels(const RenderResult* ref_result,
2519 const RenderResult* test_result) {
2520 int count = 0;
2521 for (int y = 0; y < kTestHeight; y++) {
2522 const uint32_t* ref_row = ref_result->addr32(0, y);
2523 const uint32_t* test_row = test_result->addr32(0, y);
2524 for (int x = 0; x < kTestWidth; x++) {
2525 if (ref_row[x] != test_row[x]) {
2526 if (ref_row[x] == 0) {
2527 count++;
2528 }
2529 }
2530 }
2531 }
2532 return count;
2533 }
2534
2536 const std::string& info) {
2537 quickCompareToReference(env.ref_sk_result(), env.ref_dl_result(), true,
2538 info + " reference rendering");
2539 }
2540
2541 static bool quickCompareToReference(const RenderResult* ref_result,
2542 const RenderResult* test_result,
2543 bool should_match,
2544 const std::string& info) {
2545 int w = test_result->width();
2546 int h = test_result->height();
2547 EXPECT_EQ(w, ref_result->width()) << info;
2548 EXPECT_EQ(h, ref_result->height()) << info;
2549 int pixels_different = 0;
2550 for (int y = 0; y < h; y++) {
2551 const uint32_t* ref_row = ref_result->addr32(0, y);
2552 const uint32_t* test_row = test_result->addr32(0, y);
2553 for (int x = 0; x < w; x++) {
2554 if (ref_row[x] != test_row[x]) {
2555 if (should_match && pixels_different < 5) {
2556 FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
2557 }
2558 pixels_different++;
2559 }
2560 }
2561 }
2562 if (should_match) {
2563 EXPECT_EQ(pixels_different, 0) << info;
2564 return pixels_different == 0;
2565 } else {
2566 EXPECT_NE(pixels_different, 0) << info;
2567 return pixels_different != 0;
2568 }
2569 }
2570
2571 static void compareToReference(const RenderResult* test_result,
2572 const RenderResult* ref_result,
2573 const std::string& info,
2574 const SkRect* bounds,
2575 const BoundsTolerance* tolerance,
2576 const DlColor bg,
2577 bool fuzzyCompares = false,
2578 int width = kTestWidth,
2579 int height = kTestHeight,
2580 bool printMismatches = false) {
2581 uint32_t untouched = bg.premultipliedArgb();
2582 ASSERT_EQ(test_result->width(), width) << info;
2583 ASSERT_EQ(test_result->height(), height) << info;
2584 SkIRect i_bounds =
2585 bounds ? bounds->roundOut() : SkIRect::MakeWH(width, height);
2586
2587 int pixels_different = 0;
2588 int pixels_oob = 0;
2589 int min_x = width;
2590 int min_y = height;
2591 int max_x = 0;
2592 int max_y = 0;
2593 for (int y = 0; y < height; y++) {
2594 const uint32_t* ref_row = ref_result->addr32(0, y);
2595 const uint32_t* test_row = test_result->addr32(0, y);
2596 for (int x = 0; x < width; x++) {
2597 if (bounds && test_row[x] != untouched) {
2598 if (min_x > x) {
2599 min_x = x;
2600 }
2601 if (min_y > y) {
2602 min_y = y;
2603 }
2604 if (max_x <= x) {
2605 max_x = x + 1;
2606 }
2607 if (max_y <= y) {
2608 max_y = y + 1;
2609 }
2610 if (!i_bounds.contains(x, y)) {
2611 pixels_oob++;
2612 }
2613 }
2614 bool match = fuzzyCompares ? fuzzyCompare(test_row[x], ref_row[x], 1)
2615 : test_row[x] == ref_row[x];
2616 if (!match) {
2617 if (printMismatches && pixels_different < 5) {
2618 FML_LOG(ERROR) << "pix[" << x << ", " << y
2619 << "] mismatch: " << std::hex << test_row[x]
2620 << "(test) != (ref)" << ref_row[x] << std::dec;
2621 }
2622 pixels_different++;
2623 }
2624 }
2625 }
2626 if (pixels_oob > 0) {
2627 FML_LOG(ERROR) << "pix bounds[" //
2628 << min_x << ", " << min_y << " => " << max_x << ", "
2629 << max_y << "]";
2630 FML_LOG(ERROR) << "dl_bounds[" //
2631 << bounds->fLeft << ", " << bounds->fTop //
2632 << " => " //
2633 << bounds->fRight << ", " << bounds->fBottom //
2634 << "]";
2635 } else if (bounds) {
2636 showBoundsOverflow(info, i_bounds, tolerance, min_x, min_y, max_x, max_y);
2637 }
2638 ASSERT_EQ(pixels_oob, 0) << info;
2639 ASSERT_EQ(pixels_different, 0) << info;
2640 }
2641
2642 static void showBoundsOverflow(const std::string& info,
2643 SkIRect& bounds,
2644 const BoundsTolerance* tolerance,
2645 int pixLeft,
2646 int pixTop,
2647 int pixRight,
2648 int pixBottom) {
2649 int pad_left = std::max(0, pixLeft - bounds.fLeft);
2650 int pad_top = std::max(0, pixTop - bounds.fTop);
2651 int pad_right = std::max(0, bounds.fRight - pixRight);
2652 int pad_bottom = std::max(0, bounds.fBottom - pixBottom);
2653 SkIRect pix_bounds =
2654 SkIRect::MakeLTRB(pixLeft, pixTop, pixRight, pixBottom);
2655 SkISize pix_size = pix_bounds.size();
2656 int pix_width = pix_size.width();
2657 int pix_height = pix_size.height();
2658 int worst_pad_x = std::max(pad_left, pad_right);
2659 int worst_pad_y = std::max(pad_top, pad_bottom);
2660 if (tolerance->overflows(pix_bounds, worst_pad_x, worst_pad_y)) {
2661 FML_LOG(ERROR) << "Computed bounds for " << info;
2662 FML_LOG(ERROR) << "pix bounds[" //
2663 << pixLeft << ", " << pixTop << " => " //
2664 << pixRight << ", " << pixBottom //
2665 << "]";
2666 FML_LOG(ERROR) << "dl_bounds[" //
2667 << bounds.fLeft << ", " << bounds.fTop //
2668 << " => " //
2669 << bounds.fRight << ", " << bounds.fBottom //
2670 << "]";
2671 FML_LOG(ERROR) << "Bounds overly conservative by up to " //
2672 << worst_pad_x << ", " << worst_pad_y //
2673 << " (" << (worst_pad_x * 100.0 / pix_width) //
2674 << "%, " << (worst_pad_y * 100.0 / pix_height) << "%)";
2675 int pix_area = pix_size.area();
2676 int dl_area = bounds.width() * bounds.height();
2677 FML_LOG(ERROR) << "Total overflow area: " << (dl_area - pix_area) //
2678 << " (+" << (dl_area * 100.0 / pix_area - 100.0) //
2679 << "% larger)";
2680 FML_LOG(ERROR);
2681 }
2682 }
2683
2684 static sk_sp<SkTextBlob> MakeTextBlob(const std::string& string,
2685 SkScalar font_height) {
2686 SkFont font = CreateTestFontOfSize(font_height);
2687 sk_sp<SkTypeface> face = font.refTypeface();
2688 FML_CHECK(face);
2689 FML_CHECK(face->countGlyphs() > 0) << "No glyphs in font";
2690 return SkTextBlob::MakeFromText(string.c_str(), string.size(), font,
2692 }
2693};
2694
2695std::vector<BackendType> CanvasCompareTester::TestBackends;
2698std::vector<std::string> CanvasCompareTester::ImpellerFailureImages;
2701
2703 BoundsTolerance().addAbsolutePadding(1, 1);
2704
2705// Eventually this bare bones testing::Test fixture will subsume the
2706// CanvasCompareTester and the TestParameters could then become just
2707// configuration calls made upon the fixture.
2708template <typename BaseT>
2709class DisplayListRenderingTestBase : public BaseT,
2710 protected DisplayListOpFlags {
2711 public:
2713
2714 static bool StartsWith(std::string str, std::string prefix) {
2715 if (prefix.length() > str.length()) {
2716 return false;
2717 }
2718 for (size_t i = 0; i < prefix.length(); i++) {
2719 if (str[i] != prefix[i]) {
2720 return false;
2721 }
2722 }
2723 return true;
2724 }
2725
2726 static void SetUpTestSuite() {
2727 bool do_software = true;
2728 bool do_opengl = false;
2729 bool do_metal = false;
2730 std::vector<std::string> args = ::testing::internal::GetArgvs();
2731 for (auto p_arg = std::next(args.begin()); p_arg != args.end(); p_arg++) {
2732 std::string arg = *p_arg;
2733 bool enable = true;
2734 if (arg == "--save-impeller-failures") {
2736 continue;
2737 }
2738 if (StartsWith(arg, "--no")) {
2739 enable = false;
2740 arg = "-" + arg.substr(4);
2741 } else if (StartsWith(arg, "--disable")) {
2742 enable = false;
2743 arg = "--en" + arg.substr(5);
2744 }
2745 if (arg == "--enable-software") {
2746 do_software = enable;
2747 } else if (arg == "--enable-opengl" || arg == "--enable-gl") {
2748 do_opengl = enable;
2749 } else if (arg == "--enable-metal") {
2750 do_metal = enable;
2751 } else if (arg == "--enable-impeller") {
2753 }
2754 }
2755 // Multiple test suites use this test base. Make sure that they don't
2756 // double-register the supported providers.
2758 if (do_software) {
2759 CanvasCompareTester::AddProvider(BackendType::kSoftwareBackend);
2760 }
2761 if (do_opengl) {
2762 CanvasCompareTester::AddProvider(BackendType::kOpenGlBackend);
2763 }
2764 if (do_metal) {
2765 CanvasCompareTester::AddProvider(BackendType::kMetalBackend);
2766 }
2767 std::string providers = "";
2768 for (auto& back_end : CanvasCompareTester::TestBackends) {
2769 providers += " " + DlSurfaceProvider::BackendName(back_end);
2770 }
2771 std::string libraries = " Skia";
2774 libraries += " Impeller";
2775 }
2776 FML_LOG(INFO) << "Running tests on [" << providers //
2777 << " ], and [" << libraries << " ]";
2778 }
2779
2780 static void TearDownTestSuite() {
2782 FML_LOG(INFO);
2784 << " images saved in "
2786 for (const auto& filename : CanvasCompareTester::ImpellerFailureImages) {
2787 FML_LOG(INFO) << " " << filename;
2788 }
2789 FML_LOG(INFO);
2790 }
2791 }
2792
2793 private:
2794 FML_DISALLOW_COPY_AND_ASSIGN(DisplayListRenderingTestBase);
2795};
2797
2801 [=](const SkRenderContext& ctx) { //
2802 ctx.canvas->drawPaint(ctx.paint);
2803 },
2804 [=](const DlRenderContext& ctx) { //
2805 ctx.canvas->DrawPaint(ctx.paint);
2806 },
2807 kDrawPaintFlags));
2808}
2809
2810TEST_F(DisplayListRendering, DrawOpaqueColor) {
2811 // We use a non-opaque color to avoid obliterating any backdrop filter output
2814 [=](const SkRenderContext& ctx) {
2815 // DrawColor is not tested against attributes because it is supposed
2816 // to ignore them. So, if the paint has an alpha, it is because we
2817 // are doing a saveLayer+backdrop test and we need to not flood over
2818 // the backdrop output with a solid color. So, we perform an alpha
2819 // drawColor for that case only.
2820 SkColor color = SkColorSetA(SK_ColorMAGENTA, ctx.paint.getAlpha());
2821 ctx.canvas->drawColor(color);
2822 },
2823 [=](const DlRenderContext& ctx) {
2824 // DrawColor is not tested against attributes because it is supposed
2825 // to ignore them. So, if the paint has an alpha, it is because we
2826 // are doing a saveLayer+backdrop test and we need to not flood over
2827 // the backdrop output with a solid color. So, we transfer the alpha
2828 // from the paint for that case only.
2829 ctx.canvas->DrawColor(
2830 DlColor::kMagenta().withAlpha(ctx.paint.getAlpha()));
2831 },
2832 kDrawColorFlags));
2833}
2834
2836 // We use a non-opaque color to avoid obliterating any backdrop filter output
2839 [=](const SkRenderContext& ctx) {
2840 ctx.canvas->drawColor(0x7FFF00FF);
2841 },
2842 [=](const DlRenderContext& ctx) {
2843 ctx.canvas->DrawColor(DlColor(0x7FFF00FF));
2844 },
2845 kDrawColorFlags));
2846}
2847
2848TEST_F(DisplayListRendering, DrawDiagonalLines) {
2853 // Adding some edge center to edge center diagonals to better fill
2854 // out the RRect Clip so bounds checking sees less empty bounds space.
2859
2862 [=](const SkRenderContext& ctx) { //
2863 // Skia requires kStroke style on horizontal and vertical
2864 // lines to get the bounds correct.
2865 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
2866 SkPaint p = ctx.paint;
2867 p.setStyle(SkPaint::kStroke_Style);
2868 ctx.canvas->drawLine(p1, p2, p);
2869 ctx.canvas->drawLine(p3, p4, p);
2870 ctx.canvas->drawLine(p5, p6, p);
2871 ctx.canvas->drawLine(p7, p8, p);
2872 },
2873 [=](const DlRenderContext& ctx) { //
2874 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2875 ctx.canvas->DrawLine(p3, p4, ctx.paint);
2876 ctx.canvas->DrawLine(p5, p6, ctx.paint);
2877 ctx.canvas->DrawLine(p7, p8, ctx.paint);
2878 },
2879 kDrawLineFlags)
2880 .set_draw_line());
2881}
2882
2883TEST_F(DisplayListRendering, DrawHorizontalLine) {
2886
2889 [=](const SkRenderContext& ctx) { //
2890 // Skia requires kStroke style on horizontal and vertical
2891 // lines to get the bounds correct.
2892 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
2893 SkPaint p = ctx.paint;
2894 p.setStyle(SkPaint::kStroke_Style);
2895 ctx.canvas->drawLine(p1, p2, p);
2896 },
2897 [=](const DlRenderContext& ctx) { //
2898 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2899 },
2900 kDrawHVLineFlags)
2901 .set_draw_line()
2903}
2904
2905TEST_F(DisplayListRendering, DrawVerticalLine) {
2908
2911 [=](const SkRenderContext& ctx) { //
2912 // Skia requires kStroke style on horizontal and vertical
2913 // lines to get the bounds correct.
2914 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
2915 SkPaint p = ctx.paint;
2916 p.setStyle(SkPaint::kStroke_Style);
2917 ctx.canvas->drawLine(p1, p2, p);
2918 },
2919 [=](const DlRenderContext& ctx) { //
2920 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2921 },
2922 kDrawHVLineFlags)
2923 .set_draw_line()
2925}
2926
2927TEST_F(DisplayListRendering, DrawDiagonalDashedLines) {
2932 // Adding some edge center to edge center diagonals to better fill
2933 // out the RRect Clip so bounds checking sees less empty bounds space.
2938
2939 // Full diagonals are 100x100 which are 140 in length
2940 // Dashing them with 25 on, 5 off means that the last
2941 // dash goes from 120 to 145 which means both ends of the
2942 // diagonals will be in an "on" dash for maximum bounds
2943
2944 // Edge to edge diagonals are 50x50 which are 70 in length
2945 // Dashing them with 25 on, 5 off means that the last
2946 // dash goes from 60 to 85 which means both ends of the
2947 // edge diagonals will be in a dash segment
2948
2951 [=](const SkRenderContext& ctx) { //
2952 // Skia requires kStroke style on horizontal and vertical
2953 // lines to get the bounds correct.
2954 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
2955 SkPaint p = ctx.paint;
2956 p.setStyle(SkPaint::kStroke_Style);
2957 SkScalar intervals[2] = {25.0f, 5.0f};
2958 p.setPathEffect(SkDashPathEffect::Make(intervals, 2.0f, 0.0f));
2959 ctx.canvas->drawLine(p1, p2, p);
2960 ctx.canvas->drawLine(p3, p4, p);
2961 ctx.canvas->drawLine(p5, p6, p);
2962 ctx.canvas->drawLine(p7, p8, p);
2963 },
2964 [=](const DlRenderContext& ctx) { //
2965 ctx.canvas->DrawDashedLine(ToDlPoint(p1), ToDlPoint(p2), //
2966 25.0f, 5.0f, ctx.paint);
2967 ctx.canvas->DrawDashedLine(ToDlPoint(p3), ToDlPoint(p4), //
2968 25.0f, 5.0f, ctx.paint);
2969 ctx.canvas->DrawDashedLine(ToDlPoint(p5), ToDlPoint(p6), //
2970 25.0f, 5.0f, ctx.paint);
2971 ctx.canvas->DrawDashedLine(ToDlPoint(p7), ToDlPoint(p8), //
2972 25.0f, 5.0f, ctx.paint);
2973 },
2974 kDrawLineFlags)
2975 .set_draw_line());
2976}
2977
2979 // Bounds are offset by 0.5 pixels to induce AA
2982 [=](const SkRenderContext& ctx) { //
2983 ctx.canvas->drawRect(kRenderBounds.makeOffset(0.5, 0.5), ctx.paint);
2984 },
2985 [=](const DlRenderContext& ctx) { //
2986 ctx.canvas->DrawRect(kRenderBounds.makeOffset(0.5, 0.5), ctx.paint);
2987 },
2988 kDrawRectFlags));
2989}
2990
2993
2996 [=](const SkRenderContext& ctx) { //
2997 ctx.canvas->drawOval(rect, ctx.paint);
2998 },
2999 [=](const DlRenderContext& ctx) { //
3000 ctx.canvas->DrawOval(rect, ctx.paint);
3001 },
3002 kDrawOvalFlags));
3003}
3004
3008 [=](const SkRenderContext& ctx) { //
3009 ctx.canvas->drawCircle(kTestCenter, kRenderRadius, ctx.paint);
3010 },
3011 [=](const DlRenderContext& ctx) { //
3012 ctx.canvas->DrawCircle(kTestCenter, kRenderRadius, ctx.paint);
3013 },
3014 kDrawCircleFlags));
3015}
3016
3022 [=](const SkRenderContext& ctx) { //
3023 ctx.canvas->drawRRect(rrect, ctx.paint);
3024 },
3025 [=](const DlRenderContext& ctx) { //
3026 ctx.canvas->DrawRRect(rrect, ctx.paint);
3027 },
3028 kDrawRRectFlags));
3029}
3030
3034 SkRect inner_bounds = kRenderBounds.makeInset(30.0, 30.0);
3035 SkRRect inner = SkRRect::MakeRectXY(inner_bounds, kRenderCornerRadius,
3039 [=](const SkRenderContext& ctx) { //
3040 ctx.canvas->drawDRRect(outer, inner, ctx.paint);
3041 },
3042 [=](const DlRenderContext& ctx) { //
3043 ctx.canvas->DrawDRRect(outer, inner, ctx.paint);
3044 },
3045 kDrawDRRectFlags));
3046}
3047
3049 SkPath path;
3050
3051 // unclosed lines to show some caps
3052 path.moveTo(kRenderLeft + 15, kRenderTop + 15);
3053 path.lineTo(kRenderRight - 15, kRenderBottom - 15);
3054 path.moveTo(kRenderLeft + 15, kRenderBottom - 15);
3055 path.lineTo(kRenderRight - 15, kRenderTop + 15);
3056
3057 path.addRect(kRenderBounds);
3058
3059 // miter diamonds horizontally and vertically to show miters
3061 for (int i = 1; i < kVerticalMiterDiamondPointCount; i++) {
3063 }
3064 path.close();
3066 for (int i = 1; i < kHorizontalMiterDiamondPointCount; i++) {
3068 }
3069 path.close();
3070
3073 [=](const SkRenderContext& ctx) { //
3074 ctx.canvas->drawPath(path, ctx.paint);
3075 },
3076 [=](const DlRenderContext& ctx) { //
3077 ctx.canvas->DrawPath(path, ctx.paint);
3078 },
3079 kDrawPathFlags)
3080 .set_draw_path());
3081}
3082
3086 [=](const SkRenderContext& ctx) { //
3087 ctx.canvas->drawArc(kRenderBounds, 60, 330, false, ctx.paint);
3088 },
3089 [=](const DlRenderContext& ctx) { //
3090 ctx.canvas->DrawArc(kRenderBounds, 60, 330, false, ctx.paint);
3091 },
3092 kDrawArcNoCenterFlags));
3093}
3094
3096 // Center arcs that inscribe nearly a whole circle except for a small
3097 // arc extent gap have 2 angles that may appear or disappear at the
3098 // various miter limits tested (0, 4, and 10).
3099 // The center angle here is 12 degrees which shows a miter
3100 // at limit=10, but not 0 or 4.
3101 // The arcs at the corners where it turns in towards the
3102 // center show miters at 4 and 10, but not 0.
3103 // Limit == 0, neither corner does a miter
3104 // Limit == 4, only the edge "turn-in" corners miter
3105 // Limit == 10, edge and center corners all miter
3108 [=](const SkRenderContext& ctx) { //
3109 ctx.canvas->drawArc(kRenderBounds, 60, 360 - 12, true, ctx.paint);
3110 },
3111 [=](const DlRenderContext& ctx) { //
3112 ctx.canvas->DrawArc(kRenderBounds, 60, 360 - 12, true, ctx.paint);
3113 },
3114 kDrawArcWithCenterFlags)
3115 .set_draw_arc_center());
3116}
3117
3118TEST_F(DisplayListRendering, DrawPointsAsPoints) {
3119 // The +/- 16 points are designed to fall just inside the clips
3120 // that are tested against so we avoid lots of undrawn pixels
3121 // in the accumulated bounds.
3122 const SkScalar x0 = kRenderLeft;
3123 const SkScalar x1 = kRenderLeft + 16;
3124 const SkScalar x2 = (kRenderLeft + kRenderCenterX) * 0.5;
3125 const SkScalar x3 = kRenderCenterX + 0.1;
3126 const SkScalar x4 = (kRenderRight + kRenderCenterX) * 0.5;
3127 const SkScalar x5 = kRenderRight - 16;
3128 const SkScalar x6 = kRenderRight;
3129
3130 const SkScalar y0 = kRenderTop;
3131 const SkScalar y1 = kRenderTop + 16;
3132 const SkScalar y2 = (kRenderTop + kRenderCenterY) * 0.5;
3133 const SkScalar y3 = kRenderCenterY + 0.1;
3134 const SkScalar y4 = (kRenderBottom + kRenderCenterY) * 0.5;
3135 const SkScalar y5 = kRenderBottom - 16;
3136 const SkScalar y6 = kRenderBottom;
3137
3138 // clang-format off
3139 const SkPoint points[] = {
3140 {x0, y0}, {x1, y0}, {x2, y0}, {x3, y0}, {x4, y0}, {x5, y0}, {x6, y0},
3141 {x0, y1}, {x1, y1}, {x2, y1}, {x3, y1}, {x4, y1}, {x5, y1}, {x6, y1},
3142 {x0, y2}, {x1, y2}, {x2, y2}, {x3, y2}, {x4, y2}, {x5, y2}, {x6, y2},
3143 {x0, y3}, {x1, y3}, {x2, y3}, {x3, y3}, {x4, y3}, {x5, y3}, {x6, y3},
3144 {x0, y4}, {x1, y4}, {x2, y4}, {x3, y4}, {x4, y4}, {x5, y4}, {x6, y4},
3145 {x0, y5}, {x1, y5}, {x2, y5}, {x3, y5}, {x4, y5}, {x5, y5}, {x6, y5},
3146 {x0, y6}, {x1, y6}, {x2, y6}, {x3, y6}, {x4, y6}, {x5, y6}, {x6, y6},
3147 };
3148 // clang-format on
3149 const int count = sizeof(points) / sizeof(points[0]);
3150
3153 [=](const SkRenderContext& ctx) {
3154 // Skia requires kStroke style on horizontal and vertical
3155 // lines to get the bounds correct.
3156 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3157 SkPaint p = ctx.paint;
3158 p.setStyle(SkPaint::kStroke_Style);
3160 ctx.canvas->drawPoints(mode, count, points, p);
3161 },
3162 [=](const DlRenderContext& ctx) {
3163 auto mode = PointMode::kPoints;
3164 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
3165 },
3166 kDrawPointsAsPointsFlags)
3167 .set_draw_line()
3169}
3170
3171TEST_F(DisplayListRendering, DrawPointsAsLines) {
3172 const SkScalar x0 = kRenderLeft + 1;
3173 const SkScalar x1 = kRenderLeft + 16;
3174 const SkScalar x2 = kRenderRight - 16;
3175 const SkScalar x3 = kRenderRight - 1;
3176
3177 const SkScalar y0 = kRenderTop;
3178 const SkScalar y1 = kRenderTop + 16;
3179 const SkScalar y2 = kRenderBottom - 16;
3180 const SkScalar y3 = kRenderBottom;
3181
3182 // clang-format off
3183 const SkPoint points[] = {
3184 // Outer box
3185 {x0, y0}, {x3, y0},
3186 {x3, y0}, {x3, y3},
3187 {x3, y3}, {x0, y3},
3188 {x0, y3}, {x0, y0},
3189
3190 // Diagonals
3191 {x0, y0}, {x3, y3}, {x3, y0}, {x0, y3},
3192
3193 // Inner box
3194 {x1, y1}, {x2, y1},
3195 {x2, y1}, {x2, y2},
3196 {x2, y2}, {x1, y2},
3197 {x1, y2}, {x1, y1},
3198 };
3199 // clang-format on
3200
3201 const int count = sizeof(points) / sizeof(points[0]);
3202 ASSERT_TRUE((count & 1) == 0);
3205 [=](const SkRenderContext& ctx) {
3206 // Skia requires kStroke style on horizontal and vertical
3207 // lines to get the bounds correct.
3208 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3209 SkPaint p = ctx.paint;
3210 p.setStyle(SkPaint::kStroke_Style);
3212 ctx.canvas->drawPoints(mode, count, points, p);
3213 },
3214 [=](const DlRenderContext& ctx) {
3215 auto mode = PointMode::kLines;
3216 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
3217 },
3218 kDrawPointsAsLinesFlags));
3219}
3220
3221TEST_F(DisplayListRendering, DrawPointsAsPolygon) {
3222 const SkPoint points1[] = {
3223 // RenderBounds box with a diamond
3233 };
3234 const int count1 = sizeof(points1) / sizeof(points1[0]);
3235
3238 [=](const SkRenderContext& ctx) {
3239 // Skia requires kStroke style on horizontal and vertical
3240 // lines to get the bounds correct.
3241 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3242 SkPaint p = ctx.paint;
3243 p.setStyle(SkPaint::kStroke_Style);
3245 ctx.canvas->drawPoints(mode, count1, points1, p);
3246 },
3247 [=](const DlRenderContext& ctx) {
3249 ctx.canvas->DrawPoints(mode, count1, points1, ctx.paint);
3250 },
3251 kDrawPointsAsPolygonFlags));
3252}
3253
3254TEST_F(DisplayListRendering, DrawVerticesWithColors) {
3255 // Cover as many sides of the box with only 6 vertices:
3256 // +----------+
3257 // |xxxxxxxxxx|
3258 // | xxxxxx|
3259 // | xxx|
3260 // |xxx |
3261 // |xxxxxx |
3262 // |xxxxxxxxxx|
3263 // +----------|
3264 const SkPoint pts[6] = {
3265 // Upper-Right corner, full top, half right coverage
3269 // Lower-Left corner, full bottom, half left coverage
3273 };
3274 const DlColor dl_colors[6] = {
3277 };
3278 const SkColor sk_colors[6] = {
3281 };
3282 const std::shared_ptr<DlVertices> dl_vertices =
3283 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, nullptr, dl_colors);
3284 const auto sk_vertices =
3285 SkVertices::MakeCopy(SkVertices::VertexMode::kTriangles_VertexMode, 6,
3286 pts, nullptr, sk_colors);
3287
3290 [=](const SkRenderContext& ctx) {
3291 ctx.canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver,
3292 ctx.paint);
3293 },
3294 [=](const DlRenderContext& ctx) {
3295 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
3296 ctx.paint);
3297 },
3298 kDrawVerticesFlags));
3299}
3300
3301TEST_F(DisplayListRendering, DrawVerticesWithImage) {
3302 // Cover as many sides of the box with only 6 vertices:
3303 // +----------+
3304 // |xxxxxxxxxx|
3305 // | xxxxxx|
3306 // | xxx|
3307 // |xxx |
3308 // |xxxxxx |
3309 // |xxxxxxxxxx|
3310 // +----------|
3311 const SkPoint pts[6] = {
3312 // Upper-Right corner, full top, half right coverage
3316 // Lower-Left corner, full bottom, half left coverage
3320 };
3321 const SkPoint tex[6] = {
3322 SkPoint::Make(kRenderWidth / 2.0, 0),
3326 SkPoint::Make(0, 0),
3328 };
3329 const std::shared_ptr<DlVertices> dl_vertices =
3330 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, tex, nullptr);
3331 const auto sk_vertices = SkVertices::MakeCopy(
3332 SkVertices::VertexMode::kTriangles_VertexMode, 6, pts, tex, nullptr);
3333
3336 [=](const SkRenderContext& ctx) { //
3337 SkPaint v_paint = ctx.paint;
3338 if (v_paint.getShader() == nullptr) {
3339 v_paint.setShader(MakeColorSource(ctx.image));
3340 }
3341 ctx.canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver,
3342 v_paint);
3343 },
3344 [=](const DlRenderContext& ctx) { //
3345 DlPaint v_paint = ctx.paint;
3346 if (v_paint.getColorSource() == nullptr) {
3347 v_paint.setColorSource(MakeColorSource(ctx.image));
3348 }
3349 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
3350 v_paint);
3351 },
3352 kDrawVerticesFlags));
3353}
3354
3355TEST_F(DisplayListRendering, DrawImageNearest) {
3358 [=](const SkRenderContext& ctx) {
3359 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3361 &ctx.paint);
3362 },
3363 [=](const DlRenderContext& ctx) {
3364 ctx.canvas->DrawImage(ctx.image, //
3367 &ctx.paint);
3368 },
3369 kDrawImageWithPaintFlags));
3370}
3371
3372TEST_F(DisplayListRendering, DrawImageNearestNoPaint) {
3375 [=](const SkRenderContext& ctx) {
3376 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3378 },
3379 [=](const DlRenderContext& ctx) {
3380 ctx.canvas->DrawImage(ctx.image,
3383 },
3384 kDrawImageFlags));
3385}
3386
3387TEST_F(DisplayListRendering, DrawImageLinear) {
3390 [=](const SkRenderContext& ctx) {
3391 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3393 },
3394 [=](const DlRenderContext& ctx) {
3395 ctx.canvas->DrawImage(ctx.image,
3398 },
3399 kDrawImageWithPaintFlags));
3400}
3401
3402TEST_F(DisplayListRendering, DrawImageRectNearest) {
3404 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3407 [=](const SkRenderContext& ctx) {
3408 ctx.canvas->drawImageRect(
3411 },
3412 [=](const DlRenderContext& ctx) {
3413 ctx.canvas->DrawImageRect(
3416 },
3417 kDrawImageRectWithPaintFlags));
3418}
3419
3420TEST_F(DisplayListRendering, DrawImageRectNearestNoPaint) {
3422 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3425 [=](const SkRenderContext& ctx) {
3426 ctx.canvas->drawImageRect(
3429 },
3430 [=](const DlRenderContext& ctx) {
3431 ctx.canvas->DrawImageRect(
3434 },
3435 kDrawImageRectFlags));
3436}
3437
3438TEST_F(DisplayListRendering, DrawImageRectLinear) {
3440 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3443 [=](const SkRenderContext& ctx) {
3444 ctx.canvas->drawImageRect(
3447 },
3448 [=](const DlRenderContext& ctx) { //
3449 ctx.canvas->DrawImageRect(
3452 },
3453 kDrawImageRectWithPaintFlags));
3454}
3455
3456TEST_F(DisplayListRendering, DrawImageNineNearest) {
3458 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3461 [=](const SkRenderContext& ctx) {
3462 ctx.canvas->drawImageNine(ctx.image.get(), src, dst,
3464 },
3465 [=](const DlRenderContext& ctx) {
3466 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3468 },
3469 kDrawImageNineWithPaintFlags));
3470}
3471
3472TEST_F(DisplayListRendering, DrawImageNineNearestNoPaint) {
3474 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3477 [=](const SkRenderContext& ctx) {
3478 ctx.canvas->drawImageNine(ctx.image.get(), src, dst,
3479 SkFilterMode::kNearest, nullptr);
3480 },
3481 [=](const DlRenderContext& ctx) {
3482 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3483 DlFilterMode::kNearest, nullptr);
3484 },
3485 kDrawImageNineFlags));
3486}
3487
3488TEST_F(DisplayListRendering, DrawImageNineLinear) {
3490 SkRect dst = kRenderBounds.makeInset(10.5, 10.5);
3493 [=](const SkRenderContext& ctx) {
3494 ctx.canvas->drawImageNine(ctx.image.get(), src, dst,
3496 },
3497 [=](const DlRenderContext& ctx) {
3498 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3500 },
3501 kDrawImageNineWithPaintFlags));
3502}
3503
3504TEST_F(DisplayListRendering, DrawAtlasNearest) {
3505 const SkRSXform xform[] = {
3506 // clang-format off
3507 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3508 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3509 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3510 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3511 // clang-format on
3512 };
3513 const SkRect tex[] = {
3514 // clang-format off
3519 // clang-format on
3520 };
3521 const SkColor sk_colors[] = {
3526 };
3527 const DlColor dl_colors[] = {
3532 };
3537 [=](const SkRenderContext& ctx) {
3538 ctx.canvas->drawAtlas(ctx.image.get(), xform, tex, sk_colors, 4,
3539 SkBlendMode::kSrcOver, sk_sampling, nullptr,
3540 &ctx.paint);
3541 },
3542 [=](const DlRenderContext& ctx) {
3543 ctx.canvas->DrawAtlas(ctx.image, xform, tex, dl_colors, 4,
3544 DlBlendMode::kSrcOver, dl_sampling, nullptr,
3545 &ctx.paint);
3546 },
3547 kDrawAtlasWithPaintFlags));
3548}
3549
3550TEST_F(DisplayListRendering, DrawAtlasNearestNoPaint) {
3551 const SkRSXform xform[] = {
3552 // clang-format off
3553 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3554 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3555 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3556 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3557 // clang-format on
3558 };
3559 const SkRect tex[] = {
3560 // clang-format off
3565 // clang-format on
3566 };
3567 const SkColor sk_colors[] = {
3572 };
3573 const DlColor dl_colors[] = {
3578 };
3583 [=](const SkRenderContext& ctx) {
3584 ctx.canvas->drawAtlas(ctx.image.get(), xform, tex, sk_colors, 4,
3585 SkBlendMode::kSrcOver, sk_sampling, //
3586 nullptr, nullptr);
3587 },
3588 [=](const DlRenderContext& ctx) {
3589 ctx.canvas->DrawAtlas(ctx.image, xform, tex, dl_colors, 4,
3590 DlBlendMode::kSrcOver, dl_sampling, //
3591 nullptr, nullptr);
3592 },
3593 kDrawAtlasFlags));
3594}
3595
3596TEST_F(DisplayListRendering, DrawAtlasLinear) {
3597 const SkRSXform xform[] = {
3598 // clang-format off
3599 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3600 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3601 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3602 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3603 // clang-format on
3604 };
3605 const SkRect tex[] = {
3606 // clang-format off
3611 // clang-format on
3612 };
3613 const SkColor sk_colors[] = {
3618 };
3619 const DlColor dl_colors[] = {
3624 };
3625 const DlImageSampling dl_sampling = DlImageSampling::kLinear;
3626 const SkSamplingOptions sk_sampling = SkImageSampling::kLinear;
3629 [=](const SkRenderContext& ctx) {
3630 ctx.canvas->drawAtlas(ctx.image.get(), xform, tex, sk_colors, 2,
3631 SkBlendMode::kSrcOver, sk_sampling, //
3632 nullptr, &ctx.paint);
3633 },
3634 [=](const DlRenderContext& ctx) {
3635 ctx.canvas->DrawAtlas(ctx.image, xform, tex, dl_colors, 2,
3636 DlBlendMode::kSrcOver, dl_sampling, //
3637 nullptr, &ctx.paint);
3638 },
3639 kDrawAtlasWithPaintFlags));
3640}
3641
3644 DlPaint paint;
3645 paint.setDrawStyle(DlDrawStyle::kFill);
3646 paint.setColor(DlColor(SK_ColorRED));
3648 paint);
3649 paint.setColor(DlColor(SK_ColorBLUE));
3651 paint);
3652 paint.setColor(DlColor(SK_ColorGREEN));
3654 paint);
3655 paint.setColor(DlColor(SK_ColorYELLOW));
3656 builder.DrawRect(
3658 return builder.Build();
3659}
3660
3661TEST_F(DisplayListRendering, DrawDisplayList) {
3662 sk_sp<DisplayList> display_list = makeTestDisplayList();
3665 [=](const SkRenderContext& ctx) { //
3666 DlSkCanvasAdapter(ctx.canvas).DrawDisplayList(display_list);
3667 },
3668 [=](const DlRenderContext& ctx) { //
3669 ctx.canvas->DrawDisplayList(display_list);
3670 },
3671 kDrawDisplayListFlags)
3672 .set_draw_display_list());
3673}
3674
3676 // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the
3677 // performance overlay can use Fuchsia's font manager instead of the empty
3678 // default.
3679#if defined(OS_FUCHSIA)
3680 GTEST_SKIP() << "Rendering comparisons require a valid default font manager";
3681#else
3682 sk_sp<SkTextBlob> blob =
3684#ifdef IMPELLER_SUPPORTS_RENDERING
3686#endif // IMPELLER_SUPPORTS_RENDERING
3687 SkScalar render_y_1_3 = kRenderTop + kRenderHeight * 0.3;
3688 SkScalar render_y_2_3 = kRenderTop + kRenderHeight * 0.6;
3691 [=](const SkRenderContext& ctx) {
3692 auto paint = ctx.paint;
3693 ctx.canvas->drawTextBlob(blob, kRenderLeft, render_y_1_3, paint);
3694 ctx.canvas->drawTextBlob(blob, kRenderLeft, render_y_2_3, paint);
3695 ctx.canvas->drawTextBlob(blob, kRenderLeft, kRenderBottom, paint);
3696 },
3697 [=](const DlRenderContext& ctx) {
3698 auto paint = ctx.paint;
3699 ctx.canvas->DrawTextBlob(blob, kRenderLeft, render_y_1_3, paint);
3700 ctx.canvas->DrawTextBlob(blob, kRenderLeft, render_y_2_3, paint);
3701 ctx.canvas->DrawTextBlob(blob, kRenderLeft, kRenderBottom, paint);
3702 },
3703#ifdef IMPELLER_SUPPORTS_RENDERING
3704 [=](const DlRenderContext& ctx) {
3705 auto paint = ctx.paint;
3706 ctx.canvas->DrawTextFrame(frame, kRenderLeft, render_y_1_3, paint);
3707 ctx.canvas->DrawTextFrame(frame, kRenderLeft, render_y_2_3, paint);
3708 ctx.canvas->DrawTextFrame(frame, kRenderLeft, kRenderBottom, paint);
3709 },
3710#endif // IMPELLER_SUPPORTS_RENDERING
3711 kDrawTextBlobFlags)
3712 .set_draw_text_blob(),
3713 // From examining the bounds differential for the "Default" case, the
3714 // SkTextBlob adds a padding of ~32 on the left, ~30 on the right,
3715 // ~12 on top and ~8 on the bottom, so we add 33h & 13v allowed
3716 // padding to the tolerance
3718 EXPECT_TRUE(blob->unique());
3719#endif // OS_FUCHSIA
3720}
3721
3723 SkPath path;
3724 path.addRoundRect(
3725 {
3726 kRenderLeft + 10,
3727 kRenderTop,
3728 kRenderRight - 10,
3729 kRenderBottom - 20,
3730 },
3733 const SkScalar elevation = 5;
3734
3737 [=](const SkRenderContext& ctx) { //
3739 false, 1.0);
3740 },
3741 [=](const DlRenderContext& ctx) { //
3742 ctx.canvas->DrawShadow(path, color, elevation, false, 1.0);
3743 },
3744 kDrawShadowFlags),
3745 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3746}
3747
3748TEST_F(DisplayListRendering, DrawShadowTransparentOccluder) {
3749 SkPath path;
3750 path.addRoundRect(
3751 {
3752 kRenderLeft + 10,
3753 kRenderTop,
3754 kRenderRight - 10,
3755 kRenderBottom - 20,
3756 },
3759 const SkScalar elevation = 5;
3760
3763 [=](const SkRenderContext& ctx) { //
3765 true, 1.0);
3766 },
3767 [=](const DlRenderContext& ctx) { //
3768 ctx.canvas->DrawShadow(path, color, elevation, true, 1.0);
3769 },
3770 kDrawShadowFlags),
3771 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3772}
3773
3775 SkPath path;
3776 path.addRoundRect(
3777 {
3778 kRenderLeft + 10,
3779 kRenderTop,
3780 kRenderRight - 10,
3781 kRenderBottom - 20,
3782 },
3785 const SkScalar elevation = 5;
3786
3789 [=](const SkRenderContext& ctx) { //
3791 false, 1.5);
3792 },
3793 [=](const DlRenderContext& ctx) { //
3794 ctx.canvas->DrawShadow(path, color, elevation, false, 1.5);
3795 },
3796 kDrawShadowFlags),
3797 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3798}
3799
3800TEST_F(DisplayListRendering, SaveLayerClippedContentStillFilters) {
3801 // draw rect is just outside of render bounds on the right
3803 kRenderRight + 1, //
3804 kRenderTop, //
3806 kRenderBottom //
3807 );
3808 TestParameters test_params(
3809 [=](const SkRenderContext& ctx) {
3810 auto layer_filter =
3811 SkImageFilters::Blur(10.0f, 10.0f, SkTileMode::kDecal, nullptr);
3812 SkPaint layer_paint;
3813 layer_paint.setImageFilter(layer_filter);
3814 ctx.canvas->save();
3815 ctx.canvas->clipRect(kRenderBounds, SkClipOp::kIntersect, false);
3816 ctx.canvas->saveLayer(&kTestBounds2, &layer_paint);
3817 ctx.canvas->drawRect(draw_rect, ctx.paint);
3818 ctx.canvas->restore();
3819 ctx.canvas->restore();
3820 },
3821 [=](const DlRenderContext& ctx) {
3822 auto layer_filter =
3824 DlPaint layer_paint;
3825 layer_paint.setImageFilter(layer_filter);
3826 ctx.canvas->Save();
3827 ctx.canvas->ClipRect(kRenderBounds, ClipOp::kIntersect, false);
3828 ctx.canvas->SaveLayer(&kTestBounds2, &layer_paint);
3829 ctx.canvas->DrawRect(draw_rect, ctx.paint);
3830 ctx.canvas->Restore();
3831 ctx.canvas->Restore();
3832 },
3833 kSaveLayerWithPaintFlags);
3834 CaseParameters case_params("Filtered SaveLayer with clipped content");
3835 BoundsTolerance tolerance = BoundsTolerance().addAbsolutePadding(6.0f, 6.0f);
3836
3837 for (auto& back_end : CanvasCompareTester::TestBackends) {
3838 auto provider = CanvasCompareTester::GetProvider(back_end);
3840 env.init_ref(kEmptySkSetup, test_params.sk_renderer(), //
3841 kEmptyDlSetup, test_params.dl_renderer(),
3842 test_params.imp_renderer());
3844 CanvasCompareTester::RenderWith(test_params, env, tolerance, case_params);
3845 }
3846}
3847
3848TEST_F(DisplayListRendering, SaveLayerConsolidation) {
3849 float commutable_color_matrix[]{
3850 // clang-format off
3851 0, 1, 0, 0, 0,
3852 0, 0, 1, 0, 0,
3853 1, 0, 0, 0, 0,
3854 0, 0, 0, 1, 0,
3855 // clang-format on
3856 };
3857 float non_commutable_color_matrix[]{
3858 // clang-format off
3859 0, 1, 0, .1, 0,
3860 0, 0, 1, .1, 0,
3861 1, 0, 0, .1, 0,
3862 0, 0, 0, .7, 0,
3863 // clang-format on
3864 };
3865 SkMatrix contract_matrix;
3866 contract_matrix.setScale(0.9f, 0.9f, kRenderCenterX, kRenderCenterY);
3867
3868 std::vector<SkScalar> opacities = {
3869 0,
3870 0.5f,
3871 SK_Scalar1,
3872 };
3873 std::vector<std::shared_ptr<DlColorFilter>> color_filters = {
3874 std::make_shared<DlBlendColorFilter>(DlColor::kCyan(),
3876 std::make_shared<DlMatrixColorFilter>(commutable_color_matrix),
3877 std::make_shared<DlMatrixColorFilter>(non_commutable_color_matrix),
3880 };
3881 std::vector<std::shared_ptr<DlImageFilter>> image_filters = {
3882 std::make_shared<DlBlurImageFilter>(5.0f, 5.0f, DlTileMode::kDecal),
3883 std::make_shared<DlDilateImageFilter>(5.0f, 5.0f),
3884 std::make_shared<DlErodeImageFilter>(5.0f, 5.0f),
3885 std::make_shared<DlMatrixImageFilter>(contract_matrix,
3887 };
3888
3889 auto render_content = [](DisplayListBuilder& builder) {
3890 builder.DrawRect(
3893 builder.DrawRect(
3896 builder.DrawRect(
3899 builder.DrawRect(
3901 DlPaint(DlColor::kRed().modulateOpacity(0.5f)));
3902 };
3903
3904 auto test_attributes_env =
3905 [render_content](DlPaint& paint1, DlPaint& paint2,
3906 const DlPaint& paint_both, bool same, bool rev_same,
3907 const std::string& desc1, const std::string& desc2,
3908 const RenderEnvironment* env) {
3909 DisplayListBuilder nested_builder;
3910 nested_builder.SaveLayer(&kTestBounds2, &paint1);
3911 nested_builder.SaveLayer(&kTestBounds2, &paint2);
3912 render_content(nested_builder);
3913 auto nested_results = env->getResult(nested_builder.Build());
3914
3915 DisplayListBuilder reverse_builder;
3916 reverse_builder.SaveLayer(&kTestBounds2, &paint2);
3917 reverse_builder.SaveLayer(&kTestBounds2, &paint1);
3918 render_content(reverse_builder);
3919 auto reverse_results = env->getResult(reverse_builder.Build());
3920
3921 DisplayListBuilder combined_builder;
3922 combined_builder.SaveLayer(&kTestBounds2, &paint_both);
3923 render_content(combined_builder);
3924 auto combined_results = env->getResult(combined_builder.Build());
3925
3926 // Set this boolean to true to test if combinations that are marked
3927 // as incompatible actually are compatible despite our predictions.
3928 // Some of the combinations that we treat as incompatible actually
3929 // are compatible with swapping the order of the operations, but
3930 // it would take a bit of new infrastructure to really identify
3931 // those combinations. The only hard constraint to test here is
3932 // when we claim that they are compatible and they aren't.
3933 const bool always = false;
3934
3935 // In some circumstances, Skia can combine image filter evaluations
3936 // and elide a renderpass. In this case rounding and precision of inputs
3937 // to color filters may cause the output to differ by 1.
3938 if (always || same) {
3940 nested_results.get(), combined_results.get(),
3941 "nested " + desc1 + " then " + desc2, /*bounds=*/nullptr,
3942 /*tolerance=*/nullptr, DlColor::kTransparent(),
3943 /*fuzzyCompares=*/true, combined_results->width(),
3944 combined_results->height(), /*printMismatches=*/true);
3945 }
3946 if (always || rev_same) {
3948 reverse_results.get(), combined_results.get(),
3949 "nested " + desc2 + " then " + desc1, /*bounds=*/nullptr,
3950 /*tolerance=*/nullptr, DlColor::kTransparent(),
3951 /*fuzzyCompares=*/true, combined_results->width(),
3952 combined_results->height(), /*printMismatches=*/true);
3953 }
3954 };
3955
3956 auto test_attributes = [test_attributes_env](DlPaint& paint1, DlPaint& paint2,
3957 const DlPaint& paint_both,
3958 bool same, bool rev_same,
3959 const std::string& desc1,
3960 const std::string& desc2) {
3961 for (auto& back_end : CanvasCompareTester::TestBackends) {
3962 auto provider = CanvasCompareTester::GetProvider(back_end);
3963 auto env = std::make_unique<RenderEnvironment>(
3964 provider.get(), PixelFormat::kN32PremulPixelFormat);
3965 test_attributes_env(paint1, paint2, paint_both, //
3966 same, rev_same, desc1, desc2, env.get());
3967 }
3968 };
3969
3970 // CF then Opacity should always work.
3971 // The reverse sometimes works.
3972 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
3973 auto color_filter = color_filters[cfi];
3974 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
3975 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
3976
3977 for (size_t oi = 0; oi < opacities.size(); oi++) {
3978 SkScalar opacity = opacities[oi];
3979 std::string op_desc = "opacity " + std::to_string(opacity);
3980 DlPaint nested_paint2 = DlPaint().setOpacity(opacity);
3981
3982 DlPaint combined_paint = nested_paint1;
3983 combined_paint.setOpacity(opacity);
3984
3985 bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 ||
3986 color_filter->can_commute_with_opacity();
3987
3988 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
3989 op_then_cf_works, cf_desc, op_desc);
3990 }
3991 }
3992
3993 // Opacity then IF should always work.
3994 // The reverse can also work for some values of opacity.
3995 // The reverse should also theoretically work for some IFs, but we
3996 // get some rounding errors that are more than just trivial.
3997 for (size_t oi = 0; oi < opacities.size(); oi++) {
3998 SkScalar opacity = opacities[oi];
3999 std::string op_desc = "opacity " + std::to_string(opacity);
4000 DlPaint nested_paint1 = DlPaint().setOpacity(opacity);
4001
4002 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
4003 auto image_filter = image_filters[ifi];
4004 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
4005 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
4006
4007 DlPaint combined_paint = nested_paint1;
4008 combined_paint.setImageFilter(image_filter);
4009
4010 bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0;
4011 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
4012 if_then_op_works, op_desc, if_desc);
4013 }
4014 }
4015
4016 // CF then IF should always work.
4017 // The reverse might work, but we lack the infrastructure to check it.
4018 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
4019 auto color_filter = color_filters[cfi];
4020 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
4021 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
4022
4023 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
4024 auto image_filter = image_filters[ifi];
4025 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
4026 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
4027
4028 DlPaint combined_paint = nested_paint1;
4029 combined_paint.setImageFilter(image_filter);
4030
4031 test_attributes(nested_paint1, nested_paint2, combined_paint, true, false,
4032 cf_desc, if_desc);
4033 }
4034 }
4035}
4036
4037TEST_F(DisplayListRendering, MatrixColorFilterModifyTransparencyCheck) {
4038 auto test_matrix = [](int element, SkScalar value) {
4039 // clang-format off
4040 float matrix[] = {
4041 1, 0, 0, 0, 0,
4042 0, 1, 0, 0, 0,
4043 0, 0, 1, 0, 0,
4044 0, 0, 0, 1, 0,
4045 };
4046 // clang-format on
4047 std::string desc =
4048 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
4049 float original_value = matrix[element];
4050 matrix[element] = value;
4052 auto dl_filter = DlMatrixColorFilter::Make(matrix);
4053 bool is_identity = (dl_filter == nullptr || original_value == value);
4054
4055 DlPaint paint(DlColor(0x7f7f7f7f));
4056 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4057
4058 DisplayListBuilder builder1;
4060 builder1.Rotate(45);
4061 builder1.Translate(-kTestCenter.fX, -kTestCenter.fY);
4062 builder1.DrawRect(kRenderBounds, paint);
4063 auto display_list1 = builder1.Build();
4064
4065 DisplayListBuilder builder2;
4067 builder2.Rotate(45);
4068 builder2.Translate(-kTestCenter.fX, -kTestCenter.fY);
4069 builder2.SaveLayer(&kTestBounds2, &filter_save_paint);
4070 builder2.DrawRect(kRenderBounds, paint);
4071 builder2.Restore();
4072 auto display_list2 = builder2.Build();
4073
4074 for (auto& back_end : CanvasCompareTester::TestBackends) {
4075 auto provider = CanvasCompareTester::GetProvider(back_end);
4076 auto env = std::make_unique<RenderEnvironment>(
4077 provider.get(), PixelFormat::kN32PremulPixelFormat);
4078 auto results1 = env->getResult(display_list1);
4079 auto results2 = env->getResult(display_list2);
4081 results1.get(), results2.get(), is_identity,
4082 desc + " filter affects rendering");
4083 int modified_transparent_pixels =
4085 results2.get());
4086 EXPECT_EQ(filter.modifies_transparent_black(),
4087 modified_transparent_pixels != 0)
4088 << desc;
4089 }
4090 };
4091
4092 // Tests identity (matrix[0] already == 1 in an identity filter)
4093 test_matrix(0, 1);
4094 // test_matrix(19, 1);
4095 for (int i = 0; i < 20; i++) {
4096 test_matrix(i, -0.25);
4097 test_matrix(i, 0);
4098 test_matrix(i, 0.25);
4099 test_matrix(i, 1);
4100 test_matrix(i, 1.25);
4104 }
4105}
4106
4107TEST_F(DisplayListRendering, MatrixColorFilterOpacityCommuteCheck) {
4108 auto test_matrix = [](int element, SkScalar value) {
4109 // clang-format off
4110 float matrix[] = {
4111 1, 0, 0, 0, 0,
4112 0, 1, 0, 0, 0,
4113 0, 0, 1, 0, 0,
4114 0, 0, 0, 1, 0,
4115 };
4116 // clang-format on
4117 std::string desc =
4118 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
4119 matrix[element] = value;
4120 auto filter = DlMatrixColorFilter::Make(matrix);
4121 EXPECT_EQ(std::isfinite(value), filter != nullptr);
4122
4123 DlPaint paint(DlColor(0x80808080));
4124 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
4125 DlPaint filter_save_paint = DlPaint().setColorFilter(filter);
4126
4127 DisplayListBuilder builder1;
4128 builder1.SaveLayer(&kTestBounds2, &opacity_save_paint);
4129 builder1.SaveLayer(&kTestBounds2, &filter_save_paint);
4130 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4131 builder1.DrawRect(kRenderBounds, paint);
4132 builder1.Restore();
4133 builder1.Restore();
4134 auto display_list1 = builder1.Build();
4135
4136 DisplayListBuilder builder2;
4137 builder2.SaveLayer(&kTestBounds2, &filter_save_paint);
4138 builder2.SaveLayer(&kTestBounds2, &opacity_save_paint);
4139 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4140 builder2.DrawRect(kRenderBounds, paint);
4141 builder2.Restore();
4142 builder2.Restore();
4143 auto display_list2 = builder2.Build();
4144
4145 for (auto& back_end : CanvasCompareTester::TestBackends) {
4146 auto provider = CanvasCompareTester::GetProvider(back_end);
4147 auto env = std::make_unique<RenderEnvironment>(
4148 provider.get(), PixelFormat::kN32PremulPixelFormat);
4149 auto results1 = env->getResult(display_list1);
4150 auto results2 = env->getResult(display_list2);
4151 if (!filter || filter->can_commute_with_opacity()) {
4153 results2.get(), results1.get(), desc, nullptr, nullptr,
4155 } else {
4157 results1.get(), results2.get(), false, desc);
4158 }
4159 }
4160 };
4161
4162 // Tests identity (matrix[0] already == 1 in an identity filter)
4163 test_matrix(0, 1);
4164 // test_matrix(19, 1);
4165 for (int i = 0; i < 20; i++) {
4166 test_matrix(i, -0.25);
4167 test_matrix(i, 0);
4168 test_matrix(i, 0.25);
4169 test_matrix(i, 1);
4170 test_matrix(i, 1.1);
4174 }
4175}
4176
4177#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
4178 FUNC(kClear) \
4179 FUNC(kSrc) \
4180 FUNC(kDst) \
4181 FUNC(kSrcOver) \
4182 FUNC(kDstOver) \
4183 FUNC(kSrcIn) \
4184 FUNC(kDstIn) \
4185 FUNC(kSrcOut) \
4186 FUNC(kDstOut) \
4187 FUNC(kSrcATop) \
4188 FUNC(kDstATop) \
4189 FUNC(kXor) \
4190 FUNC(kPlus) \
4191 FUNC(kModulate) \
4192 FUNC(kScreen) \
4193 FUNC(kOverlay) \
4194 FUNC(kDarken) \
4195 FUNC(kLighten) \
4196 FUNC(kColorDodge) \
4197 FUNC(kColorBurn) \
4198 FUNC(kHardLight) \
4199 FUNC(kSoftLight) \
4200 FUNC(kDifference) \
4201 FUNC(kExclusion) \
4202 FUNC(kMultiply) \
4203 FUNC(kHue) \
4204 FUNC(kSaturation) \
4205 FUNC(kColor) \
4206 FUNC(kLuminosity)
4207
4208// This function serves both to enhance error output below and to double
4209// check that the macro supplies all modes (otherwise it won't compile)
4211 switch (mode) {
4212#define MODE_CASE(m) \
4213 case DlBlendMode::m: \
4214 return #m;
4216#undef MODE_CASE
4217 }
4218}
4219
4220TEST_F(DisplayListRendering, BlendColorFilterModifyTransparencyCheck) {
4221 auto test_mode_color = [](DlBlendMode mode, DlColor color) {
4222 std::stringstream desc_str;
4223 std::string mode_string = BlendModeToString(mode);
4224 desc_str << "blend[" << mode_string << ", " << color << "]";
4225 std::string desc = desc_str.str();
4226 DlBlendColorFilter filter(color, mode);
4227 if (filter.modifies_transparent_black()) {
4228 ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc;
4229 }
4230
4231 DlPaint paint(DlColor(0x7f7f7f7f));
4232 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4233
4234 DisplayListBuilder builder1;
4236 builder1.Rotate(45);
4237 builder1.Translate(-kTestCenter.fX, -kTestCenter.fY);
4238 builder1.DrawRect(kRenderBounds, paint);
4239 auto display_list1 = builder1.Build();
4240
4241 DisplayListBuilder builder2;
4243 builder2.Rotate(45);
4244 builder2.Translate(-kTestCenter.fX, -kTestCenter.fY);
4245 builder2.SaveLayer(&kTestBounds2, &filter_save_paint);
4246 builder2.DrawRect(kRenderBounds, paint);
4247 builder2.Restore();
4248 auto display_list2 = builder2.Build();
4249
4250 for (auto& back_end : CanvasCompareTester::TestBackends) {
4251 auto provider = CanvasCompareTester::GetProvider(back_end);
4252 auto env = std::make_unique<RenderEnvironment>(
4253 provider.get(), PixelFormat::kN32PremulPixelFormat);
4254 auto results1 = env->getResult(display_list1);
4255 auto results2 = env->getResult(display_list2);
4256 int modified_transparent_pixels =
4258 results2.get());
4259 EXPECT_EQ(filter.modifies_transparent_black(),
4260 modified_transparent_pixels != 0)
4261 << desc;
4262 }
4263 };
4264
4265 auto test_mode = [&test_mode_color](DlBlendMode mode) {
4266 test_mode_color(mode, DlColor::kTransparent());
4267 test_mode_color(mode, DlColor::kWhite());
4268 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
4269 test_mode_color(mode, DlColor::kBlack());
4270 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
4271 };
4272
4273#define TEST_MODE(V) test_mode(DlBlendMode::V);
4275#undef TEST_MODE
4276}
4277
4278TEST_F(DisplayListRendering, BlendColorFilterOpacityCommuteCheck) {
4279 auto test_mode_color = [](DlBlendMode mode, DlColor color) {
4280 std::stringstream desc_str;
4281 std::string mode_string = BlendModeToString(mode);
4282 desc_str << "blend[" << mode_string << ", " << color << "]";
4283 std::string desc = desc_str.str();
4284 DlBlendColorFilter filter(color, mode);
4285 if (filter.can_commute_with_opacity()) {
4286 // If it can commute with opacity, then it might also be a NOP,
4287 // so we won't necessarily get a non-null return from |::Make()|
4288 } else {
4289 ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc;
4290 }
4291
4292 DlPaint paint(DlColor(0x80808080));
4293 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
4294 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4295
4296 DisplayListBuilder builder1;
4297 builder1.SaveLayer(&kTestBounds2, &opacity_save_paint);
4298 builder1.SaveLayer(&kTestBounds2, &filter_save_paint);
4299 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4300 builder1.DrawRect(kRenderBounds, paint);
4301 builder1.Restore();
4302 builder1.Restore();
4303 auto display_list1 = builder1.Build();
4304
4305 DisplayListBuilder builder2;
4306 builder2.SaveLayer(&kTestBounds2, &filter_save_paint);
4307 builder2.SaveLayer(&kTestBounds2, &opacity_save_paint);
4308 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4309 builder2.DrawRect(kRenderBounds, paint);
4310 builder2.Restore();
4311 builder2.Restore();
4312 auto display_list2 = builder2.Build();
4313
4314 for (auto& back_end : CanvasCompareTester::TestBackends) {
4315 auto provider = CanvasCompareTester::GetProvider(back_end);
4316 auto env = std::make_unique<RenderEnvironment>(
4317 provider.get(), PixelFormat::kN32PremulPixelFormat);
4318 auto results1 = env->getResult(display_list1);
4319 auto results2 = env->getResult(display_list2);
4320 if (filter.can_commute_with_opacity()) {
4322 results2.get(), results1.get(), desc, nullptr, nullptr,
4324 } else {
4326 results1.get(), results2.get(), false, desc);
4327 }
4328 }
4329 };
4330
4331 auto test_mode = [&test_mode_color](DlBlendMode mode) {
4332 test_mode_color(mode, DlColor::kTransparent());
4333 test_mode_color(mode, DlColor::kWhite());
4334 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
4335 test_mode_color(mode, DlColor::kBlack());
4336 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
4337 };
4338
4339#define TEST_MODE(V) test_mode(DlBlendMode::V);
4341#undef TEST_MODE
4342}
4343
4345 // The following code uses the acronym MTB for "modifies_transparent_black"
4346
4347 protected:
4349 test_src_colors = {
4350 DlColor::kBlack().withAlpha(0), // transparent black
4351 DlColor::kBlack().withAlpha(0x7f), // half transparent black
4352 DlColor::kWhite().withAlpha(0x7f), // half transparent white
4353 DlColor::kBlack(), // opaque black
4354 DlColor::kWhite(), // opaque white
4355 DlColor::kRed(), // opaque red
4356 DlColor::kGreen(), // opaque green
4357 DlColor::kBlue(), // opaque blue
4358 DlColor::kDarkGrey(), // dark grey
4359 DlColor::kLightGrey(), // light grey
4360 };
4361
4362 // We test against a color cube of 3x3x3 colors [55,aa,ff]
4363 // plus transparency as the first color/pixel
4365 const int step = 0x55;
4366 static_assert(step * 3 == 255);
4367 for (int a = step; a < 256; a += step) {
4368 for (int r = step; r < 256; r += step) {
4369 for (int g = step; g < 256; g += step) {
4370 for (int b = step; b < 256; b += step) {
4371 test_dst_colors.push_back(DlColor(a << 24 | r << 16 | g << 8 | b));
4372 }
4373 }
4374 }
4375 }
4376
4377 static constexpr float color_filter_matrix_nomtb[] = {
4378 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4379 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4380 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4381 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4382 };
4383 static constexpr float color_filter_matrix_mtb[] = {
4384 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4385 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4386 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4387 0.0001, 0.0001, 0.0001, 0.9997, 0.1, //
4388 };
4389 color_filter_nomtb = DlMatrixColorFilter::Make(color_filter_matrix_nomtb);
4390 color_filter_mtb = DlMatrixColorFilter::Make(color_filter_matrix_mtb);
4391 EXPECT_FALSE(color_filter_nomtb->modifies_transparent_black());
4392 EXPECT_TRUE(color_filter_mtb->modifies_transparent_black());
4393
4394 test_data =
4395 get_output(test_dst_colors.size(), 1, true, [this](SkCanvas* canvas) {
4396 int x = 0;
4397 for (DlColor color : test_dst_colors) {
4398 SkPaint paint;
4399 paint.setColor(ToSk(color));
4400 paint.setBlendMode(SkBlendMode::kSrc);
4401 canvas->drawRect(SkRect::MakeXYWH(x, 0, 1, 1), paint);
4402 x++;
4403 }
4404 });
4405
4406 // For image-on-image tests, the src and dest images will have repeated
4407 // rows/columns that have every color, but laid out at right angles to
4408 // each other so we see an interaction with every test color against
4409 // every other test color.
4410 int data_count = test_data->image()->width();
4412 data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
4413 ASSERT_EQ(test_data->width(), data_count);
4414 ASSERT_EQ(test_data->height(), 1);
4415 for (int y = 0; y < data_count; y++) {
4416 canvas->drawImage(test_data->image().get(), 0, y);
4417 }
4418 });
4420 data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
4421 ASSERT_EQ(test_data->width(), data_count);
4422 ASSERT_EQ(test_data->height(), 1);
4423 canvas->translate(data_count, 0);
4424 canvas->rotate(90);
4425 for (int y = 0; y < data_count; y++) {
4426 canvas->drawImage(test_data->image().get(), 0, y);
4427 }
4428 });
4429 // Double check that the pixel data is laid out in orthogonal stripes
4430 for (int y = 0; y < data_count; y++) {
4431 for (int x = 0; x < data_count; x++) {
4432 EXPECT_EQ(*test_image_dst_data->addr32(x, y), *test_data->addr32(x, 0));
4433 EXPECT_EQ(*test_image_src_data->addr32(x, y), *test_data->addr32(y, 0));
4434 }
4435 }
4436 }
4437
4438 // These flags are 0 by default until they encounter a counter-example
4439 // result and get set.
4440 static constexpr int kWasNotNop = 0x1; // Some tested pixel was modified
4441 static constexpr int kWasMTB = 0x2; // A transparent pixel was modified
4442
4443 std::vector<DlColor> test_src_colors;
4444 std::vector<DlColor> test_dst_colors;
4445
4446 std::shared_ptr<DlColorFilter> color_filter_nomtb;
4447 std::shared_ptr<DlColorFilter> color_filter_mtb;
4448
4449 // A 1-row image containing every color in test_dst_colors
4450 std::unique_ptr<RenderResult> test_data;
4451
4452 // A square image containing test_data duplicated in each row
4453 std::unique_ptr<RenderResult> test_image_dst_data;
4454
4455 // A square image containing test_data duplicated in each column
4456 std::unique_ptr<RenderResult> test_image_src_data;
4457
4458 std::unique_ptr<RenderResult> get_output(
4459 int w,
4460 int h,
4461 bool snapshot,
4462 const std::function<void(SkCanvas*)>& renderer) {
4464 SkCanvas* canvas = surface->getCanvas();
4465 renderer(canvas);
4466 return std::make_unique<SkRenderResult>(surface, snapshot);
4467 }
4468
4470 DlColor result_color,
4471 const sk_sp<DisplayList>& dl,
4472 const std::string& desc) {
4473 int ret = 0;
4474 bool is_error = false;
4475 if (dst_color.isTransparent() && !result_color.isTransparent()) {
4476 ret |= kWasMTB;
4477 is_error = !dl->modifies_transparent_black();
4478 }
4479 if (result_color != dst_color) {
4480 ret |= kWasNotNop;
4481 is_error = (dl->op_count() == 0u);
4482 }
4483 if (is_error) {
4484 FML_LOG(ERROR) << std::hex << dst_color << " filters to " << result_color
4485 << desc;
4486 }
4487 return ret;
4488 }
4489
4490 int check_image_result(const std::unique_ptr<RenderResult>& dst_data,
4491 const std::unique_ptr<RenderResult>& result_data,
4492 const sk_sp<DisplayList>& dl,
4493 const std::string& desc) {
4494 EXPECT_EQ(dst_data->width(), result_data->width());
4495 EXPECT_EQ(dst_data->height(), result_data->height());
4496 int all_flags = 0;
4497 for (int y = 0; y < dst_data->height(); y++) {
4498 const uint32_t* dst_pixels = dst_data->addr32(0, y);
4499 const uint32_t* result_pixels = result_data->addr32(0, y);
4500 for (int x = 0; x < dst_data->width(); x++) {
4501 all_flags |= check_color_result(DlColor(dst_pixels[x]),
4502 DlColor(result_pixels[x]), dl, desc);
4503 }
4504 }
4505 return all_flags;
4506 }
4507
4509 const sk_sp<DisplayList>& dl,
4510 const std::string& desc) {
4511 if (!dl->modifies_transparent_black()) {
4512 EXPECT_TRUE((all_flags & kWasMTB) == 0);
4513 } else if ((all_flags & kWasMTB) == 0) {
4514 FML_LOG(INFO) << "combination does not affect transparency: " << desc;
4515 }
4516 if (dl->op_count() == 0u) {
4517 EXPECT_TRUE((all_flags & kWasNotNop) == 0);
4518 } else if ((all_flags & kWasNotNop) == 0) {
4519 FML_LOG(INFO) << "combination could be classified as a nop: " << desc;
4520 }
4521 };
4522
4524 std::stringstream desc_stream;
4525 desc_stream << " using SkColorFilter::filterColor() with: ";
4526 desc_stream << BlendModeToString(mode);
4527 desc_stream << "/" << color;
4528 std::string desc = desc_stream.str();
4529 DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f});
4531 builder.DrawRect({0.0f, 0.0f, 10.0f, 10.0f}, paint);
4532 auto dl = builder.Build();
4533 if (dl->modifies_transparent_black()) {
4534 ASSERT_TRUE(dl->op_count() != 0u);
4535 }
4536
4537 auto sk_mode = static_cast<SkBlendMode>(mode);
4538 auto sk_color_filter = SkColorFilters::Blend(ToSk(color), sk_mode);
4539 auto srgb = SkColorSpace::MakeSRGB();
4540 int all_flags = 0;
4541 if (sk_color_filter) {
4542 for (DlColor dst_color : test_dst_colors) {
4543 SkColor4f dst_color_f = SkColor4f::FromColor(ToSk(dst_color));
4545 sk_color_filter->filterColor4f(dst_color_f, srgb.get(), srgb.get())
4546 .toSkColor());
4547 all_flags |= check_color_result(dst_color, result, dl, desc);
4548 }
4549 if ((all_flags & kWasMTB) != 0) {
4550 EXPECT_FALSE(sk_color_filter->isAlphaUnchanged());
4551 }
4552 }
4553 report_results(all_flags, dl, desc);
4554 };
4555
4557 std::stringstream desc_stream;
4558 desc_stream << " rendering with: ";
4559 desc_stream << BlendModeToString(mode);
4560 desc_stream << "/" << color;
4561 std::string desc = desc_stream.str();
4562 auto test_image = test_data->image();
4564 SkRect::MakeWH(test_image->width(), test_image->height());
4566 DlPaint dl_paint = DlPaint(color).setBlendMode(mode);
4567 builder.DrawRect(test_bounds, dl_paint);
4568 auto dl = builder.Build();
4569 bool dl_is_elided = dl->op_count() == 0u;
4570 bool dl_affects_transparent_pixels = dl->modifies_transparent_black();
4571 ASSERT_TRUE(!dl_is_elided || !dl_affects_transparent_pixels);
4572
4573 auto sk_mode = static_cast<SkBlendMode>(mode);
4574 SkPaint sk_paint;
4575 sk_paint.setBlendMode(sk_mode);
4576 sk_paint.setColor(ToSk(color));
4577 for (auto& back_end : CanvasCompareTester::TestBackends) {
4578 auto provider = CanvasCompareTester::GetProvider(back_end);
4579 auto result_surface = provider->MakeOffscreenSurface(
4580 test_image->width(), test_image->height(),
4582 SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
4583 result_canvas->clear(SK_ColorTRANSPARENT);
4584 result_canvas->drawImage(test_image.get(), 0, 0);
4585 result_canvas->drawRect(test_bounds, sk_paint);
4586 if (GrDirectContext* direct_context = GrAsDirectContext(
4587 result_surface->sk_surface()->recordingContext())) {
4588 direct_context->flushAndSubmit();
4589 direct_context->flushAndSubmit(result_surface->sk_surface().get(),
4591 }
4592 const std::unique_ptr<RenderResult> result_pixels =
4593 std::make_unique<SkRenderResult>(result_surface->sk_surface());
4594
4595 int all_flags = check_image_result(test_data, result_pixels, dl, desc);
4596 report_results(all_flags, dl, desc);
4597 }
4598 };
4599
4601 DlColor color,
4603 DlImageFilter* image_filter) {
4604 // if (true) { return; }
4605 std::stringstream desc_stream;
4606 desc_stream << " rendering with: ";
4607 desc_stream << BlendModeToString(mode);
4608 desc_stream << "/" << color;
4609 std::string cf_mtb = color_filter
4610 ? color_filter->modifies_transparent_black()
4611 ? "modifies transparency"
4612 : "preserves transparency"
4613 : "no filter";
4614 desc_stream << ", CF: " << cf_mtb;
4615 std::string if_mtb = image_filter
4616 ? image_filter->modifies_transparent_black()
4617 ? "modifies transparency"
4618 : "preserves transparency"
4619 : "no filter";
4620 desc_stream << ", IF: " << if_mtb;
4621 std::string desc = desc_stream.str();
4622
4623 DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f});
4625 .setBlendMode(mode) //
4627 .setImageFilter(image_filter);
4628 builder.DrawImage(DlImage::Make(test_image_src_data->image()), {0, 0},
4630 auto dl = builder.Build();
4631
4632 int w = test_image_src_data->width();
4633 int h = test_image_src_data->height();
4634 auto sk_mode = static_cast<SkBlendMode>(mode);
4635 SkPaint sk_paint;
4636 sk_paint.setBlendMode(sk_mode);
4637 sk_paint.setColor(ToSk(color));
4638 sk_paint.setColorFilter(ToSk(color_filter));
4639 sk_paint.setImageFilter(ToSk(image_filter));
4640 for (auto& back_end : CanvasCompareTester::TestBackends) {
4641 auto provider = CanvasCompareTester::GetProvider(back_end);
4642 auto result_surface = provider->MakeOffscreenSurface(
4644 SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
4645 result_canvas->clear(SK_ColorTRANSPARENT);
4646 result_canvas->drawImage(test_image_dst_data->image(), 0, 0);
4647 result_canvas->drawImage(test_image_src_data->image(), 0, 0,
4648 SkSamplingOptions(), &sk_paint);
4649 if (GrDirectContext* direct_context = GrAsDirectContext(
4650 result_surface->sk_surface()->recordingContext())) {
4651 direct_context->flushAndSubmit();
4652 direct_context->flushAndSubmit(result_surface->sk_surface().get(),
4654 }
4655 std::unique_ptr<RenderResult> result_pixels =
4656 std::make_unique<SkRenderResult>(result_surface->sk_surface());
4657
4658 int all_flags =
4659 check_image_result(test_image_dst_data, result_pixels, dl, desc);
4660 report_results(all_flags, dl, desc);
4661 }
4662 };
4663};
4664
4665TEST_F(DisplayListNopTest, BlendModeAndColorViaColorFilter) {
4666 auto test_mode_filter = [this](DlBlendMode mode) {
4667 for (DlColor color : test_src_colors) {
4668 test_mode_color_via_filter(mode, color);
4669 }
4670 };
4671
4672#define TEST_MODE(V) test_mode_filter(DlBlendMode::V);
4674#undef TEST_MODE
4675}
4676
4677TEST_F(DisplayListNopTest, BlendModeAndColorByRendering) {
4678 auto test_mode_render = [this](DlBlendMode mode) {
4679 // First check rendering a variety of colors onto image
4680 for (DlColor color : test_src_colors) {
4681 test_mode_color_via_rendering(mode, color);
4682 }
4683 };
4684
4685#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4687#undef TEST_MODE
4688}
4689
4690TEST_F(DisplayListNopTest, BlendModeAndColorAndFiltersByRendering) {
4691 auto test_mode_render = [this](DlBlendMode mode) {
4692 auto image_filter_nomtb = DlColorFilterImageFilter(color_filter_nomtb);
4693 auto image_filter_mtb = DlColorFilterImageFilter(color_filter_mtb);
4694 for (DlColor color : test_src_colors) {
4695 test_attributes_image(mode, color, nullptr, nullptr);
4696 test_attributes_image(mode, color, color_filter_nomtb.get(), nullptr);
4697 test_attributes_image(mode, color, color_filter_mtb.get(), nullptr);
4698 test_attributes_image(mode, color, nullptr, &image_filter_nomtb);
4699 test_attributes_image(mode, color, nullptr, &image_filter_mtb);
4700 }
4701 };
4702
4703#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4705#undef TEST_MODE
4706}
4707
4708#undef FOR_EACH_BLEND_MODE_ENUM
4709
4710} // namespace testing
4711} // namespace flutter
static int step(int x, SkScalar min, SkScalar max)
Definition: BlurTest.cpp:215
static void test_bounds(skiatest::Reporter *reporter, SkClipStack::Element::DeviceSpaceType primType)
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
int count
Definition: FontMgrTest.cpp:50
static GrDirectContext * GrAsDirectContext(GrContext_Base *base)
static void test_scale(skiatest::Reporter *reporter, const Geo &geo)
static const int points[]
static bool is_identity(const SkMatrix &m)
Definition: MatrixTest.cpp:89
static float next(float f)
static void test_matrix(skiatest::Reporter *reporter)
SkBlendMode
Definition: SkBlendMode.h:38
@ kDstIn
r = d * sa
@ kSrcOver
r = s + (1-sa)*d
@ kSrcIn
r = s * da
@ kClear
r = 0
@ kNormal_SkBlurStyle
fuzzy inside and outside
Definition: SkBlurTypes.h:12
constexpr SkColor SK_ColorYELLOW
Definition: SkColor.h:139
constexpr SkColor SK_ColorMAGENTA
Definition: SkColor.h:147
uint32_t SkColor
Definition: SkColor.h:37
constexpr SkColor SK_ColorCYAN
Definition: SkColor.h:143
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
constexpr SkColor SK_ColorBLUE
Definition: SkColor.h:135
static constexpr SkColor SkColorSetA(SkColor c, U8CPU a)
Definition: SkColor.h:82
constexpr SkColor SK_ColorRED
Definition: SkColor.h:126
constexpr SkColor SK_ColorGREEN
Definition: SkColor.h:131
@ kUTF8
uses bytes to represent UTF-8 or ASCII
static sk_sp< SkImage > color_filter(const SkImage *image, SkColorFilter *colorFilter)
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
#define SK_Scalar1
Definition: SkScalar.h:18
#define SK_ScalarNaN
Definition: SkScalar.h:28
#define SK_ScalarInfinity
Definition: SkScalar.h:26
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
static void test_image(const sk_sp< SkSpecialImage > &img, skiatest::Reporter *reporter, GrRecordingContext *rContext, bool isGPUBacked)
GLenum type
void drawRect(const SkRect &rect, const SkPaint &paint)
Definition: SkCanvas.cpp:1673
void translate(SkScalar dx, SkScalar dy)
Definition: SkCanvas.cpp:1278
@ kFast_SrcRectConstraint
sample outside bounds; faster
Definition: SkCanvas.h:1543
void clear(SkColor color)
Definition: SkCanvas.h:1199
void rotate(SkScalar degrees)
Definition: SkCanvas.cpp:1300
SkMatrix getTotalMatrix() const
Definition: SkCanvas.cpp:1629
SkIRect getDeviceClipBounds() const
Definition: SkCanvas.cpp:1607
@ kLines_PointMode
draw each pair of points as a line segment
Definition: SkCanvas.h:1242
@ kPolygon_PointMode
draw the array of points as a open polygon
Definition: SkCanvas.h:1243
@ kPoints_PointMode
draw each point separately
Definition: SkCanvas.h:1241
void drawImage(const SkImage *image, SkScalar left, SkScalar top)
Definition: SkCanvas.h:1528
static sk_sp< SkColorFilter > Blend(const SkColor4f &c, sk_sp< SkColorSpace >, SkBlendMode mode)
static sk_sp< SkColorFilter > Matrix(const SkColorMatrix &)
static sk_sp< SkColorSpace > MakeSRGB()
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
Definition: SkFont.h:35
static sk_sp< SkShader > MakeLinear(const SkPoint pts[2], const SkColor colors[], const SkScalar pos[], int count, SkTileMode mode, uint32_t flags=0, const SkMatrix *localMatrix=nullptr)
static sk_sp< SkImageFilter > ColorFilter(sk_sp< SkColorFilter > cf, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Erode(SkScalar radiusX, SkScalar radiusY, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Blur(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Dilate(SkScalar radiusX, SkScalar radiusY, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
sk_sp< SkShader > makeShader(SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions &, const SkMatrix *localMatrix=nullptr) const
Definition: SkImage.cpp:179
Definition: SkM44.h:150
static SkM44 Rotate(SkV3 axis, SkScalar radians)
Definition: SkM44.h:239
SkM44 & preConcat(const SkM44 &m)
Definition: SkM44.h:351
SkM44 & preTranslate(SkScalar x, SkScalar y, SkScalar z=0)
Definition: SkM44.cpp:89
static sk_sp< SkMaskFilter > MakeBlur(SkBlurStyle style, SkScalar sigma, bool respectCTM=true)
static SkMatrix MakeAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar pers0, SkScalar pers1, SkScalar pers2)
Definition: SkMatrix.h:179
SkMatrix & setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py)
Definition: SkMatrix.cpp:296
bool unique() const
Definition: SkRefCnt.h:175
@ kRound_Cap
adds circle
Definition: SkPaint.h:335
@ kSquare_Cap
adds square
Definition: SkPaint.h:336
void setColor(SkColor color)
Definition: SkPaint.cpp:119
void setImageFilter(sk_sp< SkImageFilter > imageFilter)
@ kStroke_Style
set to stroke geometry
Definition: SkPaint.h:194
@ kFill_Style
set to fill geometry
Definition: SkPaint.h:193
void setShader(sk_sp< SkShader > shader)
@ kRound_Join
adds circle
Definition: SkPaint.h:360
@ kMiter_Join
extends to miter limit
Definition: SkPaint.h:359
@ kBevel_Join
connects outside edges
Definition: SkPaint.h:361
void setBlendMode(SkBlendMode mode)
Definition: SkPaint.cpp:151
SkShader * getShader() const
Definition: SkPaint.h:397
void setColorFilter(sk_sp< SkColorFilter > colorFilter)
Definition: SkPath.h:59
SkCanvas * beginRecording(const SkRect &bounds, sk_sp< SkBBoxHierarchy > bbh)
sk_sp< SkPicture > finishRecordingAsPicture()
virtual SkRect cullRect() const =0
virtual void playback(SkCanvas *canvas, AbortCallback *callback=nullptr) const =0
virtual int approximateOpCount(bool nested=false) const =0
const uint32_t * addr32() const
Definition: SkPixmap.h:352
int width() const
Definition: SkPixmap.h:160
int height() const
Definition: SkPixmap.h:166
void reset()
Definition: SkPixmap.cpp:32
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition: SkRRect.h:180
static sk_sp< SkTextBlob > MakeFromText(const void *text, size_t byteLength, const SkFont &font, SkTextEncoding encoding=SkTextEncoding::kUTF8)
Definition: SkTextBlob.cpp:788
int countGlyphs() const
Definition: SkTypeface.cpp:432
static sk_sp< SkVertices > MakeCopy(VertexMode mode, int vertexCount, const SkPoint positions[], const SkPoint texs[], const SkColor colors[], int indexCount, const uint16_t indices[])
Definition: SkVertices.cpp:200
constexpr bool applies_color() const
Definition: dl_op_flags.h:172
constexpr bool is_stroked(DlDrawStyle style=DlDrawStyle::kStroke) const
Definition: dl_op_flags.h:210
constexpr bool applies_color_filter() const
Definition: dl_op_flags.h:194
constexpr bool ignores_paint() const
Definition: dl_op_flags.h:169
constexpr bool applies_image_filter() const
Definition: dl_op_flags.h:204
constexpr bool applies_shader() const
Definition: dl_op_flags.h:191
const DisplayListSpecialGeometryFlags GeometryFlags(bool is_stroked) const
Definition: dl_op_flags.h:165
constexpr bool applies_anti_alias() const
Definition: dl_op_flags.h:171
constexpr bool applies_mask_filter() const
Definition: dl_op_flags.h:201
constexpr bool applies_blend() const
The primitive honors the DlBlendMode.
Definition: dl_op_flags.h:198
constexpr bool is_flood() const
Definition: dl_op_flags.h:215
void DrawRect(const SkRect &rect, const DlPaint &paint) override
Definition: dl_builder.cc:1116
void Translate(SkScalar tx, SkScalar ty) override
Definition: dl_builder.cc:799
void Rotate(SkScalar degrees) override
Definition: dl_builder.cc:815
void SaveLayer(const SkRect *bounds, const DlPaint *paint=nullptr, const DlImageFilter *backdrop=nullptr) override
Definition: dl_builder.cc:549
sk_sp< DisplayList > Build()
Definition: dl_builder.cc:67
constexpr bool may_have_joins() const
The geometry may have segments connect non-continuously.
Definition: dl_op_flags.h:128
constexpr bool may_have_end_caps() const
The geometry may have segments that end without closing the path.
Definition: dl_op_flags.h:125
constexpr bool may_have_acute_joins() const
Definition: dl_op_flags.h:146
constexpr bool butt_cap_becomes_square() const
Mainly for drawPoints(PointMode) where Butt caps are rendered as squares.
Definition: dl_op_flags.h:131
static std::shared_ptr< DlColorFilter > Make(DlColor color, DlBlendMode mode)
bool can_commute_with_opacity() const override
bool modifies_transparent_black() const override
static std::shared_ptr< DlImageFilter > Make(SkScalar sigma_x, SkScalar sigma_y, DlTileMode tile_mode)
Developer-facing API for rendering anything within the engine.
Definition: dl_canvas.h:38
virtual void DrawRect(const SkRect &rect, const DlPaint &paint)=0
@ 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
virtual SkRect GetDestinationClipBounds() const =0
virtual SkISize GetBaseLayerSize() const =0
virtual SkMatrix GetTransform() const =0
static std::shared_ptr< DlLinearGradientColorSource > MakeLinear(const SkPoint start_point, const SkPoint end_point, uint32_t stop_count, const DlColor *colors, const float *stops, DlTileMode tile_mode, const SkMatrix *matrix=nullptr)
virtual bool modifies_transparent_black() const =0
static sk_sp< DlImage > Make(const SkImage *image)
Definition: dl_image.cc:11
static const std::shared_ptr< DlLinearToSrgbGammaColorFilter > kInstance
static std::shared_ptr< DlColorFilter > Make(const float matrix[20])
bool modifies_transparent_black() const override
bool isAntiAlias() const
Definition: dl_paint.h:57
DlStrokeCap getStrokeCap() const
Definition: dl_paint.h:99
DlColor getColor() const
Definition: dl_paint.h:69
DlPaint & setColor(DlColor color)
Definition: dl_paint.h:70
DlBlendMode getBlendMode() const
Definition: dl_paint.h:83
DlPaint & setColorFilter(const std::shared_ptr< const DlColorFilter > &filter)
Definition: dl_paint.h:144
float getStrokeMiter() const
Definition: dl_paint.h:121
std::shared_ptr< const DlColorSource > getColorSource() const
Definition: dl_paint.h:127
DlStrokeJoin getStrokeJoin() const
Definition: dl_paint.h:107
DlPaint & setAlpha(uint8_t alpha)
Definition: dl_paint.h:76
DlPaint & setBlendMode(DlBlendMode mode)
Definition: dl_paint.h:86
DlDrawStyle getDrawStyle() const
Definition: dl_paint.h:91
std::shared_ptr< const DlMaskFilter > getMaskFilter() const
Definition: dl_paint.h:166
std::shared_ptr< const DlColorFilter > getColorFilter() const
Definition: dl_paint.h:140
DlPaint & setImageFilter(const std::shared_ptr< const DlImageFilter > &filter)
Definition: dl_paint.h:157
float getStrokeWidth() const
Definition: dl_paint.h:115
DlPaint & setOpacity(SkScalar opacity)
Definition: dl_paint.h:78
std::shared_ptr< const DlImageFilter > getImageFilter() const
Definition: dl_paint.h:153
DlPaint & setDrawStyle(DlDrawStyle style)
Definition: dl_paint.h:94
DlPaint & setColorSource(std::shared_ptr< const DlColorSource > source)
Definition: dl_paint.h:131
bool isInvertColors() const
Definition: dl_paint.h:63
Backend implementation of |DlCanvas| for |SkCanvas|.
Definition: dl_sk_canvas.h:20
void DrawDisplayList(const sk_sp< DisplayList > display_list, SkScalar opacity=SK_Scalar1) override
static void DrawShadow(SkCanvas *canvas, const SkPath &path, DlColor color, float elevation, bool transparentOccluder, SkScalar dpr)
static const std::shared_ptr< DlSrgbToLinearGammaColorFilter > kInstance
static std::shared_ptr< DlVertices > Make(DlVertexMode mode, int vertex_count, const SkPoint vertices[], const SkPoint texture_coordinates[], const DlColor colors[], int index_count=0, const uint16_t indices[]=nullptr)
Constructs a DlVector with compact inline storage for all of its required and optional lists of data.
Definition: dl_vertices.cc:39
static SkRect Scale(const SkRect &rect, const SkPoint &scales)
BoundsTolerance(const BoundsTolerance &)=default
BoundsTolerance addBoundsPadding(SkScalar bounds_pad_x, SkScalar bounds_pad_y) const
bool operator==(BoundsTolerance const &other) const
BoundsTolerance addPostClipPadding(SkScalar absolute_pad_x, SkScalar absolute_pad_y) const
bool overflows(SkIRect pix_bounds, int worst_bounds_pad_x, int worst_bounds_pad_y) const
BoundsTolerance addDiscreteOffset(SkScalar discrete_offset) const
BoundsTolerance clip(SkRect clip) const
BoundsTolerance mulScale(SkScalar scale_x, SkScalar scale_y) const
BoundsTolerance addAbsolutePadding(SkScalar absolute_pad_x, SkScalar absolute_pad_y) const
static std::vector< BackendType > TestBackends
static void showBoundsOverflow(const std::string &info, SkIRect &bounds, const BoundsTolerance *tolerance, int pixLeft, int pixTop, int pixRight, int pixBottom)
static void RenderWithAttributes(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static int countModifiedTransparentPixels(const RenderResult *ref_result, const RenderResult *test_result)
static void RenderWithStrokes(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance_in)
static bool checkPixels(const RenderResult *ref_result, const SkRect ref_bounds, const std::string &info, const DlColor bg=DlColor::kTransparent())
static void RenderWithSaveRestore(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static DirectoryStatus CheckDir(const std::string &dir)
static std::unique_ptr< DlSurfaceProvider > GetProvider(BackendType type)
static sk_sp< SkTextBlob > MakeTextBlob(const std::string &string, SkScalar font_height)
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 bool quickCompareToReference(const RenderResult *ref_result, const RenderResult *test_result, bool should_match, const std::string &info)
static void checkGroupOpacity(const RenderEnvironment &env, const sk_sp< DisplayList > &display_list, const RenderResult *ref_result, const std::string &info, DlColor bg)
static void save_to_png(const RenderResult *result, const std::string &op_desc, const std::string &reason)
static void compareToReference(const RenderResult *test_result, const RenderResult *ref_result, const std::string &info, const SkRect *bounds, const BoundsTolerance *tolerance, const DlColor bg, bool fuzzyCompares=false, int width=kTestWidth, int height=kTestHeight, bool printMismatches=false)
static void RenderAll(const TestParameters &params, const BoundsTolerance &tolerance=DefaultTolerance)
static std::vector< std::string > ImpellerFailureImages
static bool fuzzyCompare(uint32_t pixel_a, uint32_t pixel_b, int fudge)
static void quickCompareToReference(const RenderEnvironment &env, const std::string &info)
static void RenderWithTransforms(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance)
static int groupOpacityFudgeFactor(const RenderEnvironment &env)
CaseParameters with_restore(SkRenderer &sk_restore, DlRenderer &dl_restore, bool mutating_layer, bool fuzzy_compare_components=false)
CaseParameters(std::string info, SkSetup &sk_setup, DlSetup &dl_setup, SkRenderer &sk_restore, DlRenderer &dl_restore, DlColor bg, bool has_diff_clip, bool has_mutating_save_layer, bool fuzzy_compare_components)
CaseParameters(std::string info, SkSetup &sk_setup, DlSetup &dl_setup)
std::shared_ptr< DlColorFilter > color_filter_mtb
void test_attributes_image(DlBlendMode mode, DlColor color, DlColorFilter *color_filter, DlImageFilter *image_filter)
void report_results(int all_flags, const sk_sp< DisplayList > &dl, const std::string &desc)
std::unique_ptr< RenderResult > test_data
std::shared_ptr< DlColorFilter > color_filter_nomtb
int check_image_result(const std::unique_ptr< RenderResult > &dst_data, const std::unique_ptr< RenderResult > &result_data, const sk_sp< DisplayList > &dl, const std::string &desc)
std::unique_ptr< RenderResult > test_image_dst_data
void test_mode_color_via_rendering(DlBlendMode mode, DlColor color)
std::unique_ptr< RenderResult > test_image_src_data
int check_color_result(DlColor dst_color, DlColor result_color, const sk_sp< DisplayList > &dl, const std::string &desc)
void test_mode_color_via_filter(DlBlendMode mode, DlColor color)
std::unique_ptr< RenderResult > get_output(int w, int h, bool snapshot, const std::function< void(SkCanvas *)> &renderer)
static bool StartsWith(std::string str, std::string prefix)
virtual bool supports(PixelFormat format) const =0
virtual sk_sp< DlImage > MakeImpellerImage(const sk_sp< DisplayList > &list, int width, int height) const
static std::string BackendName(BackendType type)
static std::unique_ptr< DlSurfaceProvider > Create(BackendType backend_type)
virtual sk_sp< DlPixelData > ImpellerSnapshot(const sk_sp< DisplayList > &list, int width, int height) const
virtual std::shared_ptr< DlSurfaceInstance > MakeOffscreenSurface(size_t width, size_t height, PixelFormat format=kN32PremulPixelFormat) const =0
virtual const std::string backend_name() const =0
ImpellerRenderResult(sk_sp< DlPixelData > screenshot, SkRect render_bounds)
void write(const std::string &path) const override
const uint32_t * addr32(int x, int y) const override
sk_sp< SkImage > image() const override
OncePerBackendWarning(const std::string &warning)
std::unique_ptr< RenderResult > getResult(const RenderJobInfo &info, JobRenderer &renderer) const
void init_ref(SkSetup &sk_setup, SkRenderer &sk_renderer, DlSetup &dl_setup, DlRenderer &dl_renderer, DlRenderer &imp_renderer, DlColor bg=DlColor::kTransparent())
std::unique_ptr< ImpellerRenderResult > getImpellerResult(const RenderJobInfo &info, DlJobRenderer &renderer) const
static RenderEnvironment MakeN32(const DlSurfaceProvider *provider)
const DlSurfaceProvider * provider() const
const sk_sp< SkImage > sk_image() const
const sk_sp< DlImage > dl_image() const
RenderEnvironment(const DlSurfaceProvider *provider, PixelFormat format)
static RenderEnvironment Make565(const DlSurfaceProvider *provider)
std::unique_ptr< RenderResult > getResult(sk_sp< DisplayList > dl) const
const sk_sp< DlImage > impeller_image() const
const RenderResult * ref_dl_result() const
const ImpellerRenderResult * ref_impeller_result() const
const RenderResult * ref_sk_result() const
virtual int width() const =0
virtual const uint32_t * addr32(int x, int y) const =0
virtual int height() const =0
virtual ~RenderResult()=default
virtual sk_sp< SkImage > image() const =0
virtual void write(const std::string &path) const =0
static constexpr SkSamplingOptions kCubic
static constexpr SkSamplingOptions kMipmapLinear
static constexpr SkSamplingOptions kLinear
static constexpr SkSamplingOptions kNearestNeighbor
const uint32_t * addr32(int x, int y) const override
SkRenderResult(const sk_sp< SkSurface > &surface, bool take_snapshot=false)
sk_sp< SkImage > image() const override
void write(const std::string &path) const
const BoundsTolerance lineAdjust(const BoundsTolerance &tolerance, const DlPaint &paint, const SkMatrix &matrix) const
TestParameters(const SkRenderer &sk_renderer, const DlRenderer &dl_renderer, const DisplayListAttributeFlags &flags)
const DlRenderer & imp_renderer() const
TestParameters(const SkRenderer &sk_renderer, const DlRenderer &dl_renderer, const DlRenderer &imp_renderer, const DisplayListAttributeFlags &flags)
const BoundsTolerance adjust(const BoundsTolerance &tolerance, const DlPaint &paint, const SkMatrix &matrix) const
const DlRenderer & dl_renderer() 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
const SkRenderer & sk_renderer() const
T * get() const
Definition: SkRefCnt.h:303
const Paint & paint
Definition: color_source.cc:38
DlColor color
#define FOR_EACH_BLEND_MODE_ENUM(FUNC)
#define TEST_MODE(V)
#define MODE_CASE(m)
const EmbeddedViewParams * params
VkSurfaceKHR surface
Definition: main.cc:49
double frame
Definition: examples.cpp:31
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct a[10]
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
GAsyncResult * result
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_CHECK(condition)
Definition: logging.h:85
#define FML_DCHECK(condition)
Definition: logging.h:103
Dart_NativeFunction function
Definition: fuchsia.cc:51
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
double y
double x
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< const SkImage > image
Definition: SkRecords.h:269
sk_sp< const SkPicture > picture
Definition: SkRecords.h:299
SkRRect rrect
Definition: SkRecords.h:232
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
def match(bench, filt)
Definition: benchmark.py:23
Definition: copy.py:1
void * malloc(size_t size)
Definition: allocation.cc:19
Definition: __init__.py:1
constexpr float kPi
Definition: math.h:27
constexpr SkScalar kXOffsetR3
const std::function< void(const SkRenderContext &)> SkRenderer
constexpr SkScalar kYOffsetD3
TEST_F(DisplayListTest, Defaults)
const std::function< void(const DlSetupContext &)> DlSetup
static void DrawCheckerboard(DlCanvas *canvas)
constexpr SkPoint kVerticalMiterDiamondPoints[]
constexpr int kRenderCenterY
constexpr int kRenderCenterX
static std::string BlendModeToString(DlBlendMode mode)
constexpr SkScalar kXOffsetR1
static const DlSetup kEmptyDlSetup
constexpr SkScalar kYOffsetD1
SkFont CreateTestFontOfSize(SkScalar scalar)
static const SkRenderer kEmptySkRenderer
constexpr SkScalar kMiterExtremeDiamondOffsetX
sk_sp< DisplayList > makeTestDisplayList()
constexpr SkScalar kYOffsetU2
const int kHorizontalMiterDiamondPointCount
constexpr SkScalar kXOffsetL3
constexpr SkScalar kMiter4DiamondOffsetY
static std::shared_ptr< DlImageColorSource > MakeColorSource(const sk_sp< DlImage > &image)
constexpr int kRenderHalfHeight
constexpr SkScalar kYOffsetD2
constexpr int kRenderHalfWidth
DlSurfaceProvider::PixelFormat PixelFormat
constexpr SkRect kTestBounds2
constexpr SkScalar kXOffsetR2
const std::function< void(const SkSetupContext &)> SkSetup
DlCanvas::PointMode PointMode
constexpr SkScalar kXOffsetL1
constexpr SkScalar kMiter10DiamondOffsetY
constexpr SkScalar kMiter10DiamondOffsetX
DlCanvas::ClipOp ClipOp
const SkPoint kHorizontalMiterDiamondPoints[]
constexpr SkScalar kYOffset0
constexpr SkRect kRenderBounds
constexpr SkScalar kYOffsetU1
constexpr SkScalar kYOffsetU3
const int kVerticalMiterDiamondPointCount
static const DlRenderer kEmptyDlRenderer
static const SkSetup kEmptySkSetup
constexpr SkScalar kXOffset0
constexpr SkScalar kMiter4DiamondOffsetX
constexpr SkScalar kRenderCornerRadius
constexpr SkScalar kXOffsetL2
constexpr SkScalar kRenderRadius
const std::function< void(const DlRenderContext &)> DlRenderer
constexpr SkPoint kTestCenter
constexpr SkScalar kMiterExtremeDiamondOffsetY
SkPaint ToSk(const DlPaint &paint)
@ kMiter
extends to miter limit
@ kBevel
connects outside edges
bool NotEquals(const T *a, const T *b)
Definition: dl_comparable.h:70
const DlPoint & ToDlPoint(const SkPoint &point)
DlStrokeCap
Definition: dl_paint.h:28
@ kRound
adds circle
@ kButt
no stroke extension
@ kSquare
adds square
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: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
@ kTriangles
The vertices are taken 3 at a time to form a triangle.
@ kStroke
strokes boundary of shapes
@ kFill
fills interior of shapes
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 which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition: switches.h:145
@ kNormal
fuzzy inside and outside
it will be possible to load the file into Perfetto s trace viewer 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
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer 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
Definition: switches.h:259
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: switches.h:59
@ kSrcOver
r = s + (1-sa)*d
@ kSrcATop
r = s*da + d*(1-sa)
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition: file_posix.cc:97
font
Font Metadata and Metrics.
dst
Definition: cp.py:12
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
static void draw_rect(SkCanvas *canvas, const SkRect &r, const SkPaint &p)
SkSamplingOptions(SkFilterMode::kLinear))
SINT bool isfinite(const Vec< N, T > &v)
Definition: SkVx.h:1003
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
Definition: ref_ptr.h:256
flutter::DlColor DlColor
flutter::DlPaint DlPaint
static SkString to_string(int n)
Definition: nanobench.cpp:119
SkScalar w
int32_t height
int32_t width
Definition: SkMD5.cpp:134
Definition: SkRect.h:32
int32_t fBottom
larger y-axis bounds
Definition: SkRect.h:36
static constexpr SkIRect MakeLTRB(int32_t l, int32_t t, int32_t r, int32_t b)
Definition: SkRect.h:91
constexpr SkISize size() const
Definition: SkRect.h:172
int32_t fTop
smaller y-axis bounds
Definition: SkRect.h:34
static constexpr SkIRect MakeWH(int32_t w, int32_t h)
Definition: SkRect.h:56
SkIRect makeInset(int32_t dx, int32_t dy) const
Definition: SkRect.h:332
int32_t fLeft
smaller x-axis bounds
Definition: SkRect.h:33
bool contains(int32_t x, int32_t y) const
Definition: SkRect.h:463
int32_t fRight
larger x-axis bounds
Definition: SkRect.h:35
Definition: SkSize.h:16
constexpr int32_t width() const
Definition: SkSize.h:36
constexpr int32_t height() const
Definition: SkSize.h:37
constexpr int64_t area() const
Definition: SkSize.h:39
static SkImageInfo MakeN32Premul(int width, int height)
float fX
x-axis value
Definition: SkPoint_impl.h:164
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
float fY
y-axis value
Definition: SkPoint_impl.h:165
static SkRect Make(const SkISize &size)
Definition: SkRect.h:669
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
constexpr SkRect makeOffset(float dx, float dy) const
Definition: SkRect.h:965
bool intersect(const SkRect &r)
Definition: SkRect.cpp:114
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
static SkRect MakeIWH(int w, int h)
Definition: SkRect.h:623
void outset(float dx, float dy)
Definition: SkRect.h:1077
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
SkRect makeInset(float dx, float dy) const
Definition: SkRect.h:987
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
bool contains(SkScalar x, SkScalar y) const
Definition: extension.cpp:19
void roundOut(SkIRect *dst) const
Definition: SkRect.h:1241
bool isEmpty() const
Definition: SkRect.h:693
static constexpr SkRect MakeWH(float w, float h)
Definition: SkRect.h:609
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition: SkRect.h:646
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15
void setEmpty()
Definition: SkRect.h:842
static constexpr DlColor kMagenta()
Definition: dl_color.h:28
static constexpr DlColor kWhite()
Definition: dl_color.h:23
static constexpr DlColor kBlue()
Definition: dl_color.h:26
static constexpr DlColor kBlack()
Definition: dl_color.h:22
static constexpr DlColor kYellow()
Definition: dl_color.h:29
static constexpr DlColor kLightGrey()
Definition: dl_color.h:32
constexpr DlColor withAlpha(uint8_t alpha) const
Definition: dl_color.h:63
constexpr uint32_t premultipliedArgb() const
Definition: dl_color.h:52
static constexpr DlColor kTransparent()
Definition: dl_color.h:21
static constexpr DlColor kRed()
Definition: dl_color.h:24
static constexpr DlColor kGreen()
Definition: dl_color.h:25
constexpr bool isTransparent() const
Definition: dl_color.h:40
static constexpr DlColor kDarkGrey()
Definition: dl_color.h:30
static constexpr DlColor kCyan()
Definition: dl_color.h:27
constexpr uint32_t argb() const
Definition: dl_color.h:82
void Render(SkCanvas *canvas, const RenderJobInfo &info)
DisplayListJobRenderer(sk_sp< DisplayList > display_list)
void Render(DlCanvas *canvas, const RenderJobInfo &info)
sk_sp< DisplayList > MakeDisplayList(const RenderJobInfo &info)
void Render(SkCanvas *sk_canvas, const RenderJobInfo &info) override
DlJobRenderer(const DlSetup &dl_setup, const DlRenderer &dl_render, const DlRenderer &dl_restore, const sk_sp< DlImage > &dl_image)
virtual void Render(SkCanvas *canvas, const RenderJobInfo &info)=0
SkJobRenderer(const SkSetup &sk_setup, const SkRenderer &sk_render, const SkRenderer &sk_restore, const sk_sp< SkImage > &sk_image)
void Render(SkCanvas *canvas, const RenderJobInfo &info) override
sk_sp< SkPicture > MakePicture(const RenderJobInfo &info)
void Render(SkCanvas *canvas, const RenderJobInfo &info)
SkPictureJobRenderer(sk_sp< SkPicture > picture)
#define ERROR(message)
Definition: elf_loader.cc:260
#define EXPECT_TRUE(handle)
Definition: unit_test.h:678