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