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
22#include "flutter/fml/file.h"
23#include "flutter/fml/math.h"
26#ifdef IMPELLER_SUPPORTS_RENDERING
29#endif // IMPELLER_SUPPORTS_RENDERING
30
31#include "third_party/skia/include/core/SkBBHFactory.h"
32#include "third_party/skia/include/core/SkColorFilter.h"
33#include "third_party/skia/include/core/SkColorSpace.h"
34#include "third_party/skia/include/core/SkFontMgr.h"
35#include "third_party/skia/include/core/SkPictureRecorder.h"
36#include "third_party/skia/include/core/SkStream.h"
37#include "third_party/skia/include/core/SkSurface.h"
38#include "third_party/skia/include/core/SkTypeface.h"
39#include "third_party/skia/include/effects/SkDashPathEffect.h"
40#include "third_party/skia/include/effects/SkGradientShader.h"
41#include "third_party/skia/include/effects/SkImageFilters.h"
42#include "third_party/skia/include/encode/SkPngEncoder.h"
43#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
44#include "third_party/skia/include/gpu/ganesh/GrRecordingContext.h"
45#include "third_party/skia/include/gpu/ganesh/GrTypes.h"
46#include "txt/platform.h"
47
48namespace flutter {
49namespace testing {
50
51constexpr int kTestWidth = 200;
52constexpr int kTestHeight = 200;
53constexpr int kRenderWidth = 100;
54constexpr int kRenderHeight = 100;
55constexpr int kRenderLeft = (kTestWidth - kRenderWidth) / 2;
56constexpr int kRenderTop = (kTestHeight - kRenderHeight) / 2;
59constexpr int kRenderCenterX = (kRenderLeft + kRenderRight) / 2;
60constexpr int kRenderCenterY = (kRenderTop + kRenderBottom) / 2;
61constexpr DlScalar kRenderRadius = std::min(kRenderWidth, kRenderHeight) / 2.0;
63
68
69// The tests try 3 miter limit values, 0.0, 4.0 (the default), and 10.0
70// These values will allow us to construct a diamond that spans the
71// width or height of the render box and still show the miter for 4.0
72// and 10.0.
73// These values were discovered by drawing a diamond path in Skia fiddle
74// and then playing with the cross-axis size until the miter was about
75// as large as it could get before it got cut off.
76
77// The X offsets which will be used for tall vertical diamonds are
78// expressed in terms of the rendering height to obtain the proper angle
82
83// The Y offsets which will be used for long horizontal diamonds are
84// expressed in terms of the rendering width to obtain the proper angle
88
89// Render 3 vertical and horizontal diamonds each
90// designed to break at the tested miter limits
91// 0.0, 4.0 and 10.0
92// Center is biased by 0.5 to include more pixel centers in the
93// thin miters
102 // Vertical diamonds:
103 // M10 M4 Mextreme
104 // /\ /|\ /\ top of RenderBounds
105 // / \ / | \ / \ to
106 // <----X--+--X----> RenderCenter
107 // \ / \ | / \ / to
108 // \/ \|/ \/ bottom of RenderBounds
109 // clang-format off
123 // clang-format on
124};
128
137 // Horizontal diamonds
138 // Same configuration as Vertical diamonds above but
139 // rotated 90 degrees
140 // clang-format off
154 // clang-format on
155};
159
160namespace {
161constexpr uint8_t toC(DlScalar fComp) {
162 return round(fComp * 255);
163}
164
165constexpr uint32_t PremultipliedArgb(const DlColor& color) {
166 if (color.isOpaque()) {
167 return color.argb();
168 }
169 DlScalar f = color.getAlphaF();
170 return (color.argb() & 0xFF000000) | //
171 toC(color.getRedF() * f) << 16 | //
172 toC(color.getGreenF() * f) << 8 | //
173 toC(color.getBlueF() * f);
174}
175} // namespace
176
178 public:
179 static constexpr SkSamplingOptions kNearestNeighbor =
180 SkSamplingOptions(SkFilterMode::kNearest);
181 static constexpr SkSamplingOptions kLinear =
182 SkSamplingOptions(SkFilterMode::kLinear);
183 static constexpr SkSamplingOptions kMipmapLinear =
184 SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
185 static constexpr SkSamplingOptions kCubic =
186 SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
187};
188
189static void DrawCheckerboard(DlCanvas* canvas) {
190 DlPaint p0, p1;
192 p0.setColor(DlColor(0xff00fe00)); // off-green
195 // Some pixels need some transparency for DstIn testing
196 p1.setAlpha(128);
197 int cbdim = 5;
198 int width = canvas->GetBaseLayerDimensions().width;
199 int height = canvas->GetBaseLayerDimensions().height;
200 for (int y = 0; y < width; y += cbdim) {
201 for (int x = 0; x < height; x += cbdim) {
202 DlPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1;
203 canvas->DrawRect(DlRect::MakeXYWH(x, y, cbdim, cbdim), cellp);
204 }
205 }
206}
207
208static void DrawCheckerboard(SkCanvas* canvas) {
209 DlSkCanvasAdapter dl_canvas(canvas);
210 DrawCheckerboard(&dl_canvas);
211}
212
213static std::shared_ptr<DlImageColorSource> MakeColorSource(
214 const sk_sp<DlImage>& image) {
215 return std::make_shared<DlImageColorSource>(image, //
219}
220
221static sk_sp<SkShader> MakeColorSource(const sk_sp<SkImage>& image) {
222 return image->makeShader(SkTileMode::kRepeat, //
223 SkTileMode::kRepeat, //
225}
226
227// Used to show "INFO" warnings about tests that are omitted on certain
228// backends, but only once for the entire test run to avoid warning spam
230 public:
231 explicit OncePerBackendWarning(const std::string& warning)
232 : warning_(warning) {}
233
234 void warn(const std::string& name) {
235 if (warnings_sent_.find(name) == warnings_sent_.end()) {
236 warnings_sent_.insert(name);
237 FML_LOG(INFO) << warning_ << " on " << name;
238 }
239 }
240
241 private:
242 std::string warning_;
243 std::set<std::string> warnings_sent_;
244};
245
246// A class to specify how much tolerance to allow in bounds estimates.
247// For some attributes, the machinery must make some conservative
248// assumptions as to the extent of the bounds, but some of our test
249// parameters do not produce bounds that expand by the full conservative
250// estimates. This class provides a number of tweaks to apply to the
251// pixel bounds to account for the conservative factors.
252//
253// An instance is passed along through the methods and if any test adds
254// a paint attribute or other modifier that will cause a more conservative
255// estimate for bounds, it can modify the factors here to account for it.
256// Ideally, all tests will be executed with geometry that will trigger
257// the conservative cases anyway and all attributes will be combined with
258// other attributes that make their output more predictable, but in those
259// cases where a given test sequence cannot really provide attributes to
260// demonstrate the worst case scenario, they can modify these factors to
261// avoid false bounds overflow notifications.
263 public:
264 BoundsTolerance() = default;
266
268 DlScalar bounds_pad_y) const {
269 BoundsTolerance copy = BoundsTolerance(*this);
270 copy.bounds_pad_ += DlPoint(bounds_pad_x, bounds_pad_y);
271 return copy;
272 }
273
274 BoundsTolerance mulScale(DlScalar scale_x, DlScalar scale_y) const {
275 BoundsTolerance copy = BoundsTolerance(*this);
276 copy.scale_ *= DlPoint(scale_x, scale_y);
277 return copy;
278 }
279
281 DlScalar absolute_pad_y) const {
282 BoundsTolerance copy = BoundsTolerance(*this);
283 copy.absolute_pad_ += DlPoint(absolute_pad_x, absolute_pad_y);
284 return copy;
285 }
286
288 DlScalar absolute_pad_y) const {
289 BoundsTolerance copy = BoundsTolerance(*this);
290 copy.clip_pad_ += DlPoint(absolute_pad_x, absolute_pad_y);
291 return copy;
292 }
293
295 BoundsTolerance copy = BoundsTolerance(*this);
296 copy.discrete_offset_ += discrete_offset;
297 return copy;
298 }
299
301 BoundsTolerance copy = BoundsTolerance(*this);
302 copy.clip_ = copy.clip_.IntersectionOrEmpty(clip);
303 return copy;
304 }
305
306 static DlRect Scale(const DlRect& rect, const DlPoint& scales) {
307 DlScalar outset_x = rect.GetWidth() * (scales.x - 1);
308 DlScalar outset_y = rect.GetHeight() * (scales.y - 1);
309 return rect.Expand(outset_x, outset_y);
310 }
311
312 bool overflows(DlIRect pix_bounds,
313 int worst_bounds_pad_x,
314 int worst_bounds_pad_y) const {
315 DlRect allowed = DlRect::Make(pix_bounds);
316 allowed = allowed.Expand(bounds_pad_.x, bounds_pad_.y);
317 allowed = Scale(allowed, scale_);
318 allowed = allowed.Expand(absolute_pad_.x, absolute_pad_.y);
319 allowed = allowed.IntersectionOrEmpty(clip_);
320 allowed = allowed.Expand(clip_pad_.x, clip_pad_.y);
321 DlIRect rounded = DlIRect::RoundOut(allowed);
322 int pad_left = std::max(0, pix_bounds.GetLeft() - rounded.GetLeft());
323 int pad_top = std::max(0, pix_bounds.GetTop() - rounded.GetTop());
324 int pad_right = std::max(0, pix_bounds.GetRight() - rounded.GetRight());
325 int pad_bottom = std::max(0, pix_bounds.GetBottom() - rounded.GetBottom());
326 int allowed_pad_x = std::max(pad_left, pad_right);
327 int allowed_pad_y = std::max(pad_top, pad_bottom);
328 if (worst_bounds_pad_x > allowed_pad_x ||
329 worst_bounds_pad_y > allowed_pad_y) {
330 FML_LOG(ERROR) << "acceptable bounds padding: " //
331 << allowed_pad_x << ", " << allowed_pad_y;
332 }
333 return (worst_bounds_pad_x > allowed_pad_x ||
334 worst_bounds_pad_y > allowed_pad_y);
335 }
336
337 DlScalar discrete_offset() const { return discrete_offset_; }
338
339 bool operator==(BoundsTolerance const& other) const {
340 return bounds_pad_ == other.bounds_pad_ && scale_ == other.scale_ &&
341 absolute_pad_ == other.absolute_pad_ && clip_ == other.clip_ &&
342 clip_pad_ == other.clip_pad_ &&
343 discrete_offset_ == other.discrete_offset_;
344 }
345
346 private:
347 DlPoint bounds_pad_ = {0, 0};
348 DlPoint scale_ = {1, 1};
349 DlPoint absolute_pad_ = {0, 0};
350 DlRect clip_ = DlRect::MakeLTRB(-1E9, -1E9, 1E9, 1E9);
351 DlPoint clip_pad_ = {0, 0};
352
353 DlScalar discrete_offset_ = 0;
354};
355
356template <typename C, typename P, typename I>
368
369using SkSetup = const std::function<void(const SkSetupContext&)>;
370using SkRenderer = const std::function<void(const SkRenderContext&)>;
371using DlSetup = const std::function<void(const DlSetupContext&)>;
372using DlRenderer = const std::function<void(const DlRenderContext&)>;
373static const SkSetup kEmptySkSetup = [](const SkSetupContext&) {};
374static const SkRenderer kEmptySkRenderer = [](const SkRenderContext&) {};
375static const DlSetup kEmptyDlSetup = [](const DlSetupContext&) {};
376static const DlRenderer kEmptyDlRenderer = [](const DlRenderContext&) {};
377
380
382 public:
383 virtual ~RenderResult() = default;
384
385 virtual sk_sp<SkImage> image() const = 0;
386 virtual int width() const = 0;
387 virtual int height() const = 0;
388 virtual const uint32_t* addr32(int x, int y) const = 0;
389 virtual void write(const std::string& path) const = 0;
390};
391
392class SkRenderResult final : public RenderResult {
393 public:
394 explicit SkRenderResult(const sk_sp<SkSurface>& surface,
395 bool take_snapshot = false) {
396 SkImageInfo info = surface->imageInfo();
397 info = SkImageInfo::MakeN32Premul(info.dimensions());
398 addr_ = malloc(info.computeMinByteSize() * info.height());
399 pixmap_.reset(info, addr_, info.minRowBytes());
400 surface->readPixels(pixmap_, 0, 0);
401 if (take_snapshot) {
402 image_ = surface->makeImageSnapshot();
403 }
404 }
405 ~SkRenderResult() override { free(addr_); }
406
407 sk_sp<SkImage> image() const override { return image_; }
408 int width() const override { return pixmap_.width(); }
409 int height() const override { return pixmap_.height(); }
410 const uint32_t* addr32(int x, int y) const override {
411 return pixmap_.addr32(x, y);
412 }
413 void write(const std::string& path) const {
414 auto stream = SkFILEWStream(path.c_str());
415 SkPngEncoder::Options options;
416 SkPngEncoder::Encode(&stream, pixmap_, options);
417 stream.flush();
418 }
419
420 private:
421 sk_sp<SkImage> image_;
422 SkPixmap pixmap_;
423 void* addr_ = nullptr;
424};
425
426class ImpellerRenderResult final : public RenderResult {
427 public:
428 explicit ImpellerRenderResult(sk_sp<DlPixelData> screenshot,
430 : screenshot_(std::move(screenshot)), render_bounds_(render_bounds) {}
431 ~ImpellerRenderResult() override = default;
432
433 sk_sp<SkImage> image() const override { return nullptr; };
434 int width() const override { return screenshot_->width(); };
435 int height() const override { return screenshot_->height(); }
436 const uint32_t* addr32(int x, int y) const override {
437 return screenshot_->addr32(x, y);
438 }
439 void write(const std::string& path) const override {
440 screenshot_->write(path);
441 }
442 const DlRect& render_bounds() const { return render_bounds_; }
443
444 private:
445 const sk_sp<DlPixelData> screenshot_;
446 DlRect render_bounds_;
447};
448
456
458 virtual void Render(SkCanvas* canvas, const RenderJobInfo& info) = 0;
459 virtual bool targets_impeller() const { return false; }
460};
461
463 public:
464 const DlMatrix& setup_matrix() const {
466 return setup_matrix_;
467 }
468
469 const DlIRect& setup_clip_bounds() const {
471 return setup_clip_bounds_;
472 }
473
474 protected:
475 bool is_setup_ = false;
478};
479
481 explicit SkJobRenderer(const SkSetup& sk_setup,
482 const SkRenderer& sk_render,
483 const SkRenderer& sk_restore,
484 const sk_sp<SkImage>& sk_image)
485 : sk_setup_(sk_setup),
486 sk_render_(sk_render),
487 sk_restore_(sk_restore),
488 sk_image_(sk_image) {}
489
490 void Render(SkCanvas* canvas, const RenderJobInfo& info) override {
491 FML_DCHECK(info.opacity == SK_Scalar1);
492 SkPaint paint;
493 sk_setup_({canvas, paint, sk_image_});
494 setup_paint_ = paint;
495 setup_matrix_ = ToDlMatrix(canvas->getLocalToDevice());
496 setup_clip_bounds_ = ToDlIRect(canvas->getDeviceClipBounds());
497 is_setup_ = true;
498 sk_render_({canvas, paint, sk_image_});
499 sk_restore_({canvas, paint, sk_image_});
500 }
501
502 sk_sp<SkPicture> MakePicture(const RenderJobInfo& info) {
503 SkPictureRecorder recorder;
504 SkRTreeFactory rtree_factory;
505 SkCanvas* cv =
506 recorder.beginRecording(ToSkRect(kTestBounds2), &rtree_factory);
507 Render(cv, info);
508 return recorder.finishRecordingAsPicture();
509 }
510
511 const SkPaint& setup_paint() const {
513 return setup_paint_;
514 }
515
516 private:
517 const SkSetup sk_setup_;
518 const SkRenderer sk_render_;
519 const SkRenderer sk_restore_;
520 sk_sp<SkImage> sk_image_;
521 SkPaint setup_paint_;
522};
523
525 explicit DlJobRenderer(const DlSetup& dl_setup,
526 const DlRenderer& dl_render,
527 const DlRenderer& dl_restore,
528 const sk_sp<DlImage>& dl_image)
529 : dl_setup_(dl_setup),
530 dl_render_(dl_render),
531 dl_restore_(dl_restore),
532 dl_image_(dl_image) {}
533
534 void Render(SkCanvas* sk_canvas, const RenderJobInfo& info) override {
535 DlSkCanvasAdapter canvas(sk_canvas);
536 Render(&canvas, info);
537 }
538
539 void Render(DlCanvas* canvas, const RenderJobInfo& info) {
540 FML_DCHECK(info.opacity == SK_Scalar1);
541 DlPaint paint;
542 dl_setup_({canvas, paint, dl_image_});
543 setup_paint_ = paint;
544 setup_matrix_ = canvas->GetMatrix();
547 is_setup_ = true;
548 dl_render_({canvas, paint, dl_image_});
549 dl_restore_({canvas, paint, dl_image_});
550 }
551
552 sk_sp<DisplayList> MakeDisplayList(const RenderJobInfo& info) {
554 Render(&builder, info);
555 return builder.Build();
556 }
557
558 const DlPaint& setup_paint() const {
560 return setup_paint_;
561 }
562
563 bool targets_impeller() const override {
564 return dl_image_->impeller_texture() != nullptr;
565 }
566
567 private:
568 const DlSetup dl_setup_;
569 const DlRenderer dl_render_;
570 const DlRenderer dl_restore_;
571 const sk_sp<DlImage> dl_image_;
572 DlPaint setup_paint_;
573};
574
576 explicit SkPictureJobRenderer(sk_sp<SkPicture> picture)
577 : picture_(std::move(picture)) {}
578
579 void Render(SkCanvas* canvas, const RenderJobInfo& info) {
580 FML_DCHECK(info.opacity == SK_Scalar1);
581 picture_->playback(canvas);
582 }
583
584 private:
585 sk_sp<SkPicture> picture_;
586};
587
589 explicit DisplayListJobRenderer(sk_sp<DisplayList> display_list)
590 : display_list_(std::move(display_list)) {}
591
592 void Render(SkCanvas* canvas, const RenderJobInfo& info) {
593 DlSkCanvasAdapter(canvas).DrawDisplayList(display_list_, info.opacity);
594 }
595
596 private:
597 sk_sp<DisplayList> display_list_;
598};
599
601 public:
602 static bool EnableImpeller;
603
605 : provider_(provider), format_(format) {
606 if (provider->supports(format)) {
607 surface_1x_ =
609 surface_2x_ = provider->MakeOffscreenSurface(kTestWidth * 2,
610 kTestHeight * 2, format);
611 }
612 }
613
615 return RenderEnvironment(provider, PixelFormat::k565PixelFormat);
616 }
617
619 return RenderEnvironment(provider, PixelFormat::kN32PremulPixelFormat);
620 }
621
622 void init_ref(SkSetup& sk_setup,
623 SkRenderer& sk_renderer,
624 DlSetup& dl_setup,
625 DlRenderer& dl_renderer,
626 DlRenderer& imp_renderer,
628 SkJobRenderer sk_job(sk_setup, sk_renderer, kEmptySkRenderer, kTestSkImage);
629 RenderJobInfo info = {
630 .bg = bg,
631 };
632 ref_sk_result_ = getResult(info, sk_job);
633 DlJobRenderer dl_job(dl_setup, dl_renderer, kEmptyDlRenderer, kTestDlImage);
634 ref_dl_result_ = getResult(info, dl_job);
635 ref_dl_paint_ = dl_job.setup_paint();
636 ref_matrix_ = dl_job.setup_matrix();
637 ref_clip_bounds_ = dl_job.setup_clip_bounds();
638 ASSERT_EQ(sk_job.setup_matrix(), ref_matrix_);
639 ASSERT_EQ(sk_job.setup_clip_bounds(), ref_clip_bounds_);
640 if (supports_impeller()) {
641 test_impeller_image_ = makeTestImpellerImage(provider_);
642 DlJobRenderer imp_job(dl_setup, imp_renderer, kEmptyDlRenderer,
643 test_impeller_image_);
644 ref_impeller_result_ = getImpellerResult(info, imp_job);
645 }
646 }
647
648 std::unique_ptr<RenderResult> getResult(const RenderJobInfo& info,
649 JobRenderer& renderer) const {
650 auto surface = getSurface(info.width, info.height);
651 FML_DCHECK(surface != nullptr);
652 auto canvas = surface->getCanvas();
653 canvas->clear(ToSkColor4f(info.bg));
654
655 int restore_count = canvas->save();
656 canvas->scale(info.scale, info.scale);
657 renderer.Render(canvas, info);
658 canvas->restoreToCount(restore_count);
659
660 if (GrDirectContext* dContext =
661 GrAsDirectContext(surface->recordingContext())) {
662 dContext->flushAndSubmit(surface.get(), GrSyncCpu::kYes);
663 }
664 return std::make_unique<SkRenderResult>(surface);
665 }
666
667 std::unique_ptr<RenderResult> getResult(sk_sp<DisplayList> dl) const {
668 DisplayListJobRenderer job(std::move(dl));
669 RenderJobInfo info = {};
670 return getResult(info, job);
671 }
672
673 std::unique_ptr<ImpellerRenderResult> getImpellerResult(
674 const RenderJobInfo& info,
675 DlJobRenderer& renderer) const {
676 FML_DCHECK(info.scale == SK_Scalar1);
677
678 DisplayListBuilder builder;
679 builder.Clear(info.bg);
680 auto render_dl = renderer.MakeDisplayList(info);
681 builder.DrawDisplayList(render_dl);
682 auto dl = builder.Build();
683 auto snap = provider_->ImpellerSnapshot(dl, kTestWidth, kTestHeight);
684 return std::make_unique<ImpellerRenderResult>(std::move(snap),
685 render_dl->GetBounds());
686 }
687
688 const DlSurfaceProvider* provider() const { return provider_; }
689 bool valid() const { return provider_->supports(format_); }
690 const std::string backend_name() const { return provider_->backend_name(); }
691 bool supports_impeller() const {
692 return EnableImpeller && provider_->supports_impeller();
693 }
694
695 PixelFormat format() const { return format_; }
696 const DlPaint& ref_dl_paint() const { return ref_dl_paint_; }
697 const DlMatrix& ref_matrix() const { return ref_matrix_; }
698 const DlIRect& ref_clip_bounds() const { return ref_clip_bounds_; }
699 const RenderResult* ref_sk_result() const { return ref_sk_result_.get(); }
700 const RenderResult* ref_dl_result() const { return ref_dl_result_.get(); }
702 return ref_impeller_result_.get();
703 }
704
705 const sk_sp<SkImage> sk_image() const { return kTestSkImage; }
706 const sk_sp<DlImage> dl_image() const { return kTestDlImage; }
707 const sk_sp<DlImage> impeller_image() const { return test_impeller_image_; }
708
709 private:
710 sk_sp<SkSurface> getSurface(int width, int height) const {
711 FML_DCHECK(valid());
712 FML_DCHECK(surface_1x_ != nullptr);
713 FML_DCHECK(surface_2x_ != nullptr);
714 if (width == kTestWidth && height == kTestHeight) {
715 return surface_1x_->sk_surface();
716 }
717 if (width == kTestWidth * 2 && height == kTestHeight * 2) {
718 return surface_2x_->sk_surface();
719 }
720 FML_LOG(ERROR) << "Test surface size (" << width << " x " << height
721 << ") not supported.";
722 FML_DCHECK(false);
723 return nullptr;
724 }
725
726 const DlSurfaceProvider* provider_;
727 const PixelFormat format_;
728 std::shared_ptr<DlSurfaceInstance> surface_1x_;
729 std::shared_ptr<DlSurfaceInstance> surface_2x_;
730
731 DlPaint ref_dl_paint_;
732 DlMatrix ref_matrix_;
733 DlIRect ref_clip_bounds_;
734 std::unique_ptr<RenderResult> ref_sk_result_;
735 std::unique_ptr<RenderResult> ref_dl_result_;
736 std::unique_ptr<ImpellerRenderResult> ref_impeller_result_;
737 sk_sp<DlImage> test_impeller_image_;
738
739 static const sk_sp<SkImage> kTestSkImage;
740 static const sk_sp<DlImage> kTestDlImage;
741 static const sk_sp<SkImage> makeTestSkImage() {
742 sk_sp<SkSurface> surface = SkSurfaces::Raster(
743 SkImageInfo::MakeN32Premul(kRenderWidth, kRenderHeight));
744 DrawCheckerboard(surface->getCanvas());
745 return surface->makeImageSnapshot();
746 }
747 static const sk_sp<DlImage> makeTestImpellerImage(
748 const DlSurfaceProvider* provider) {
750 DisplayListBuilder builder(DlRect::MakeWH(kRenderWidth, kRenderHeight));
751 DrawCheckerboard(&builder);
752 return provider->MakeImpellerImage(builder.Build(), //
754 }
755};
756
757const sk_sp<SkImage> RenderEnvironment::kTestSkImage = makeTestSkImage();
758const sk_sp<DlImage> RenderEnvironment::kTestDlImage =
760
762 public:
763 explicit CaseParameters(std::string info)
765
767 : CaseParameters(std::move(info),
768 sk_setup,
769 dl_setup,
772 DlColor(SK_ColorTRANSPARENT),
773 false,
774 false,
775 false) {}
776
777 CaseParameters(std::string info,
782 DlColor bg,
783 bool has_diff_clip,
786 : info_(std::move(info)),
787 bg_(bg),
788 sk_setup_(sk_setup),
789 dl_setup_(dl_setup),
790 sk_restore_(sk_restore),
791 dl_restore_(dl_restore),
792 has_diff_clip_(has_diff_clip),
793 has_mutating_save_layer_(has_mutating_save_layer),
794 fuzzy_compare_components_(fuzzy_compare_components) {}
795
798 bool mutating_layer,
799 bool fuzzy_compare_components = false) {
800 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore, dl_restore,
801 bg_, has_diff_clip_, mutating_layer,
803 }
804
806 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore_, dl_restore_,
807 bg, has_diff_clip_, has_mutating_save_layer_,
808 fuzzy_compare_components_);
809 }
810
812 return CaseParameters(info_, sk_setup_, dl_setup_, sk_restore_, dl_restore_,
813 bg_, true, has_mutating_save_layer_,
814 fuzzy_compare_components_);
815 }
816
817 std::string info() const { return info_; }
818 DlColor bg() const { return bg_; }
819 bool has_diff_clip() const { return has_diff_clip_; }
820 bool has_mutating_save_layer() const { return has_mutating_save_layer_; }
821 bool fuzzy_compare_components() const { return fuzzy_compare_components_; }
822
823 SkSetup sk_setup() const { return sk_setup_; }
824 DlSetup dl_setup() const { return dl_setup_; }
825 SkRenderer sk_restore() const { return sk_restore_; }
826 DlRenderer dl_restore() const { return dl_restore_; }
827
828 private:
829 const std::string info_;
830 const DlColor bg_;
831 const SkSetup sk_setup_;
832 const DlSetup dl_setup_;
833 const SkRenderer sk_restore_;
834 const DlRenderer dl_restore_;
835 const bool has_diff_clip_;
836 const bool has_mutating_save_layer_;
837 const bool fuzzy_compare_components_;
838};
839
841 public:
846
848 const DlRenderer& dl_renderer,
850 const DisplayListAttributeFlags& flags)
851 : sk_renderer_(sk_renderer),
852 dl_renderer_(dl_renderer),
853 imp_renderer_(imp_renderer),
854 flags_(flags) {}
855
856 bool uses_paint() const { return !flags_.ignores_paint(); }
857 bool uses_gradient() const { return flags_.applies_shader(); }
858
859 bool impeller_compatible(const DlPaint& paint) const {
860 if (is_draw_text_blob()) {
861 // Non-color text is rendered as paths
862 if (paint.getColorSourcePtr()) {
863 return false;
864 }
865 // Non-filled text (stroke or stroke and fill) is rendered as paths
866 if (paint.getDrawStyle() != DlDrawStyle::kFill) {
867 return false;
868 }
869 }
870 return true;
871 }
872
874 const CaseParameters& caseP,
875 const DlPaint& attr,
876 const MatrixClipJobRenderer& renderer) const {
877 if (caseP.has_mutating_save_layer()) {
878 return false;
879 }
880 if (env.ref_clip_bounds() != renderer.setup_clip_bounds() ||
881 caseP.has_diff_clip()) {
882 return false;
883 }
884 if (env.ref_matrix() != renderer.setup_matrix() && !flags_.is_flood()) {
885 return false;
886 }
887 if (flags_.ignores_paint()) {
888 return true;
889 }
890 const DlPaint& ref_attr = env.ref_dl_paint();
891 if (flags_.applies_anti_alias() && //
892 ref_attr.isAntiAlias() != attr.isAntiAlias()) {
893 if (renderer.targets_impeller()) {
894 // Impeller only does MSAA, ignoring the AA attribute
895 // https://github.com/flutter/flutter/issues/104721
896 } else {
897 return false;
898 }
899 }
900 if (flags_.applies_color() && //
901 ref_attr.getColor() != attr.getColor()) {
902 return false;
903 }
904 if (flags_.applies_blend() && //
905 ref_attr.getBlendMode() != attr.getBlendMode()) {
906 return false;
907 }
908 if (flags_.applies_color_filter() && //
909 (ref_attr.isInvertColors() != attr.isInvertColors() ||
910 NotEquals(ref_attr.getColorFilter(), attr.getColorFilter()))) {
911 return false;
912 }
913 if (flags_.applies_mask_filter() && //
914 NotEquals(ref_attr.getMaskFilter(), attr.getMaskFilter())) {
915 return false;
916 }
917 if (flags_.applies_image_filter() && //
918 ref_attr.getImageFilter() != attr.getImageFilter()) {
919 return false;
920 }
921 if (flags_.applies_shader() && //
922 NotEquals(ref_attr.getColorSource(), attr.getColorSource())) {
923 return false;
924 }
925
926 bool is_stroked = flags_.is_stroked(attr.getDrawStyle());
927 if (flags_.is_stroked(ref_attr.getDrawStyle()) != is_stroked) {
928 return false;
929 }
930 if (!is_stroked) {
931 return true;
932 }
933 if (ref_attr.getStrokeWidth() != attr.getStrokeWidth()) {
934 return false;
935 }
937 flags_.GeometryFlags(is_stroked);
938 if (geo_flags.may_have_end_caps() && //
939 getCap(ref_attr, geo_flags) != getCap(attr, geo_flags)) {
940 return false;
941 }
942 if (geo_flags.may_have_joins()) {
943 if (ref_attr.getStrokeJoin() != attr.getStrokeJoin()) {
944 return false;
945 }
946 if (ref_attr.getStrokeJoin() == DlStrokeJoin::kMiter) {
947 DlScalar ref_miter = ref_attr.getStrokeMiter();
948 DlScalar test_miter = attr.getStrokeMiter();
949 // miter limit < 1.4 affects right angles
950 if (geo_flags.may_have_acute_joins() || //
951 ref_miter < 1.4 || test_miter < 1.4) {
952 if (ref_miter != test_miter) {
953 return false;
954 }
955 }
956 }
957 }
958 return true;
959 }
960
962 DisplayListSpecialGeometryFlags geo_flags) const {
963 DlStrokeCap cap = attr.getStrokeCap();
964 if (geo_flags.butt_cap_becomes_square() && cap == DlStrokeCap::kButt) {
966 }
967 return cap;
968 }
969
970 const BoundsTolerance adjust(const BoundsTolerance& tolerance,
971 const DlPaint& paint,
972 const DlMatrix& matrix) const {
973 if (is_draw_text_blob() && tolerance.discrete_offset() > 0) {
974 // drawTextBlob needs just a little more leeway when using a
975 // discrete path effect.
976 return tolerance.addBoundsPadding(2, 2);
977 }
978 if (is_draw_line()) {
979 return lineAdjust(tolerance, paint, matrix);
980 }
981 if (is_draw_arc_center()) {
982 if (paint.getDrawStyle() != DlDrawStyle::kFill &&
984 // the miter join at the center of an arc does not really affect
985 // its bounds in any of our test cases, but the bounds code needs
986 // to take it into account for the cases where it might, so we
987 // relax our tolerance to reflect the miter bounds padding.
988 DlScalar miter_pad =
989 paint.getStrokeMiter() * paint.getStrokeWidth() * 0.5f;
990 return tolerance.addBoundsPadding(miter_pad, miter_pad);
991 }
992 }
993 return tolerance;
994 }
995
997 const DlPaint& paint,
998 const DlMatrix& matrix) const {
999 DlScalar adjust = 0.0;
1000 DlScalar half_width = paint.getStrokeWidth() * 0.5f;
1001 if (tolerance.discrete_offset() > 0) {
1002 // When a discrete path effect is added, the bounds calculations must
1003 // allow for miters in any direction, but a horizontal line will not
1004 // have miters in the horizontal direction, similarly for vertical
1005 // lines, and diagonal lines will have miters off at a "45 degree"
1006 // angle that don't expand the bounds much at all.
1007 // Also, the discrete offset will not move any points parallel with
1008 // the line, so provide tolerance for both miters and offset.
1009 adjust =
1010 half_width * paint.getStrokeMiter() + tolerance.discrete_offset();
1011 }
1012
1013 DisplayListSpecialGeometryFlags geo_flags = flags_.GeometryFlags(true);
1014 if (paint.getStrokeCap() == DlStrokeCap::kButt &&
1015 !geo_flags.butt_cap_becomes_square()) {
1016 adjust = std::max(adjust, half_width);
1017 }
1018 if (adjust == 0) {
1019 return tolerance;
1020 }
1021 DlScalar h_tolerance;
1022 DlScalar v_tolerance;
1023 if (is_horizontal_line()) {
1025 h_tolerance = adjust;
1026 v_tolerance = 0;
1027 } else if (is_vertical_line()) {
1028 h_tolerance = 0;
1029 v_tolerance = adjust;
1030 } else {
1031 // The perpendicular miters just do not impact the bounds of
1032 // diagonal lines at all as they are aimed in the wrong direction
1033 // to matter. So allow tolerance in both axes.
1034 h_tolerance = v_tolerance = adjust;
1035 }
1036 BoundsTolerance new_tolerance =
1037 tolerance.addBoundsPadding(h_tolerance, v_tolerance);
1038 return new_tolerance;
1039 }
1040
1041 const SkRenderer& sk_renderer() const { return sk_renderer_; }
1042 const DlRenderer& dl_renderer() const { return dl_renderer_; }
1043 const DlRenderer& imp_renderer() const { return imp_renderer_; }
1044
1045 // Tests that call drawTextBlob with an sk_ref paint attribute will cause
1046 // those attributes to be stored in an internal Skia cache so we need
1047 // to expect that the |sk_ref.unique()| call will fail in those cases.
1048 // See: (TBD(flar) - file Skia bug)
1049 bool is_draw_text_blob() const { return is_draw_text_blob_; }
1050 bool is_draw_display_list() const { return is_draw_display_list_; }
1051 bool is_draw_line() const { return is_draw_line_; }
1052 bool is_draw_arc_center() const { return is_draw_arc_center_; }
1053 bool is_draw_path() const { return is_draw_path_; }
1054 bool is_horizontal_line() const { return is_horizontal_line_; }
1055 bool is_vertical_line() const { return is_vertical_line_; }
1056 bool ignores_dashes() const { return ignores_dashes_; }
1057
1059 is_draw_text_blob_ = true;
1060 return *this;
1061 }
1063 is_draw_display_list_ = true;
1064 return *this;
1065 }
1067 is_draw_line_ = true;
1068 return *this;
1069 }
1071 is_draw_arc_center_ = true;
1072 return *this;
1073 }
1075 is_draw_path_ = true;
1076 return *this;
1077 }
1079 ignores_dashes_ = true;
1080 return *this;
1081 }
1083 is_horizontal_line_ = true;
1084 return *this;
1085 }
1087 is_vertical_line_ = true;
1088 return *this;
1089 }
1090
1091 private:
1092 const SkRenderer sk_renderer_;
1093 const DlRenderer dl_renderer_;
1094 const DlRenderer imp_renderer_;
1095 const DisplayListAttributeFlags flags_;
1096
1097 bool is_draw_text_blob_ = false;
1098 bool is_draw_display_list_ = false;
1099 bool is_draw_line_ = false;
1100 bool is_draw_arc_center_ = false;
1101 bool is_draw_path_ = false;
1102 bool ignores_dashes_ = false;
1103 bool is_horizontal_line_ = false;
1104 bool is_vertical_line_ = false;
1105};
1106
1108 public:
1109 static std::vector<BackendType> TestBackends;
1112 static std::vector<std::string> ImpellerFailureImages;
1114
1115 static std::unique_ptr<DlSurfaceProvider> GetProvider(BackendType type) {
1116 auto provider = DlSurfaceProvider::Create(type);
1117 if (provider == nullptr) {
1118 FML_LOG(ERROR) << "provider " << DlSurfaceProvider::BackendName(type)
1119 << " not supported (ignoring)";
1120 return nullptr;
1121 }
1122 provider->InitializeSurface(kTestWidth, kTestHeight,
1123 PixelFormat::kN32PremulPixelFormat);
1124 return provider;
1125 }
1126
1127 static void ClearProviders() { TestBackends.clear(); }
1128
1130 auto provider = GetProvider(type);
1131 if (!provider) {
1132 return false;
1133 }
1134 if (provider->supports_impeller()) {
1135 ImpellerSupported = true;
1136 }
1137 TestBackends.push_back(type);
1138 return true;
1139 }
1140
1142
1143 static void RenderAll(const TestParameters& params,
1144 const BoundsTolerance& tolerance = DefaultTolerance) {
1145 for (auto& back_end : TestBackends) {
1146 auto provider = GetProvider(back_end);
1147 RenderEnvironment env = RenderEnvironment::MakeN32(provider.get());
1148 env.init_ref(kEmptySkSetup, params.sk_renderer(), //
1149 kEmptyDlSetup, params.dl_renderer(), params.imp_renderer());
1150 quickCompareToReference(env, "default");
1151 if (env.supports_impeller()) {
1152 auto impeller_result = env.ref_impeller_result();
1153 if (!checkPixels(impeller_result, impeller_result->render_bounds(),
1154 "Impeller reference")) {
1155 std::string test_name =
1156 ::testing::UnitTest::GetInstance()->current_test_info()->name();
1157 save_to_png(impeller_result, test_name + " (Impeller reference)",
1158 "base rendering was blank or out of bounds");
1159 }
1160 } else {
1161 static OncePerBackendWarning warnings("No Impeller output tests");
1162 warnings.warn(env.backend_name());
1163 }
1164
1165 RenderWithTransforms(params, env, tolerance);
1166 RenderWithClips(params, env, tolerance);
1167 RenderWithSaveRestore(params, env, tolerance);
1168 // Only test attributes if the canvas version uses the paint object
1169 if (params.uses_paint()) {
1170 RenderWithAttributes(params, env, tolerance);
1171 }
1172 }
1173 }
1174
1175 static void RenderWithSaveRestore(const TestParameters& testP,
1176 const RenderEnvironment& env,
1177 const BoundsTolerance& tolerance) {
1178 DlRect clip =
1181 DlColor alpha_layer_color = DlColor::kCyan().withAlpha(0x7f);
1182 SkRenderer sk_safe_restore = [=](const SkRenderContext& ctx) {
1183 // Draw another primitive to disable peephole optimizations
1184 ctx.canvas->drawRect(ToSkRect(kRenderBounds).makeOffset(500, 500),
1185 SkPaint());
1186 ctx.canvas->restore();
1187 };
1188 DlRenderer dl_safe_restore = [=](const DlRenderContext& ctx) {
1189 // Draw another primitive to disable peephole optimizations
1190 // As the rendering op rejection in the DisplayList Builder
1191 // gets smarter and smarter, this operation has had to get
1192 // sneakier and sneakier about specifying an operation that
1193 // won't practically show up in the output, but technically
1194 // can't be culled.
1195 ctx.canvas->DrawRect(
1197 DlPaint());
1198 ctx.canvas->Restore();
1199 };
1200 SkRenderer sk_opt_restore = [=](const SkRenderContext& ctx) {
1201 // Just a simple restore to allow peephole optimizations to occur
1202 ctx.canvas->restore();
1203 };
1204 DlRenderer dl_opt_restore = [=](const DlRenderContext& ctx) {
1205 // Just a simple restore to allow peephole optimizations to occur
1206 ctx.canvas->Restore();
1207 };
1208 DlRect layer_bounds = kRenderBounds.Expand(-15, -15);
1209 RenderWith(testP, env, tolerance,
1211 "With prior save/clip/restore",
1212 [=](const SkSetupContext& ctx) {
1213 ctx.canvas->save();
1214 ctx.canvas->clipRect(ToSkRect(clip), SkClipOp::kIntersect,
1215 false);
1216 SkPaint p2;
1217 ctx.canvas->drawRect(ToSkRect(rect), p2);
1218 p2.setBlendMode(SkBlendMode::kClear);
1219 ctx.canvas->drawRect(ToSkRect(rect), p2);
1220 ctx.canvas->restore();
1221 },
1222 [=](const DlSetupContext& ctx) {
1223 ctx.canvas->Save();
1224 ctx.canvas->ClipRect(clip, DlClipOp::kIntersect, false);
1225 DlPaint p2;
1226 ctx.canvas->DrawRect(rect, p2);
1227 p2.setBlendMode(DlBlendMode::kClear);
1228 ctx.canvas->DrawRect(rect, p2);
1229 ctx.canvas->Restore();
1230 }));
1231 RenderWith(testP, env, tolerance,
1233 "saveLayer no paint, no bounds",
1234 [=](const SkSetupContext& ctx) {
1235 ctx.canvas->saveLayer(nullptr, nullptr);
1236 },
1237 [=](const DlSetupContext& ctx) {
1238 ctx.canvas->SaveLayer(std::nullopt, nullptr);
1239 })
1240 .with_restore(sk_safe_restore, dl_safe_restore, false));
1241 RenderWith(testP, env, tolerance,
1243 "saveLayer no paint, with bounds",
1244 [=](const SkSetupContext& ctx) {
1245 ctx.canvas->saveLayer(ToSkRect(layer_bounds), nullptr);
1246 },
1247 [=](const DlSetupContext& ctx) {
1248 ctx.canvas->SaveLayer(layer_bounds, nullptr);
1249 })
1250 .with_restore(sk_safe_restore, dl_safe_restore, true));
1251 RenderWith(testP, env, tolerance,
1253 "saveLayer with alpha, no bounds",
1254 [=](const SkSetupContext& ctx) {
1255 SkPaint save_p;
1256 save_p.setColor(ToSkColor4f(alpha_layer_color));
1257 ctx.canvas->saveLayer(nullptr, &save_p);
1258 },
1259 [=](const DlSetupContext& ctx) {
1260 DlPaint save_p;
1261 save_p.setColor(alpha_layer_color);
1262 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1263 })
1264 .with_restore(sk_safe_restore, dl_safe_restore, true));
1265 RenderWith(testP, env, tolerance,
1267 "saveLayer with peephole alpha, no bounds",
1268 [=](const SkSetupContext& ctx) {
1269 SkPaint save_p;
1270 save_p.setColor(ToSkColor4f(alpha_layer_color));
1271 ctx.canvas->saveLayer(nullptr, &save_p);
1272 },
1273 [=](const DlSetupContext& ctx) {
1274 DlPaint save_p;
1275 save_p.setColor(alpha_layer_color);
1276 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1277 })
1278 .with_restore(sk_opt_restore, dl_opt_restore, true, true));
1279 RenderWith(testP, env, tolerance,
1281 "saveLayer with alpha and bounds",
1282 [=](const SkSetupContext& ctx) {
1283 SkPaint save_p;
1284 save_p.setColor(ToSkColor4f(alpha_layer_color));
1285 ctx.canvas->saveLayer(ToSkRect(layer_bounds), &save_p);
1286 },
1287 [=](const DlSetupContext& ctx) {
1288 DlPaint save_p;
1289 save_p.setColor(alpha_layer_color);
1290 ctx.canvas->SaveLayer(layer_bounds, &save_p);
1291 })
1292 .with_restore(sk_safe_restore, dl_safe_restore, true));
1293 {
1294 // Being able to see a backdrop blur requires a non-default background
1295 // so we create a new environment for these tests that has a checkerboard
1296 // background that can be blurred by the backdrop filter. We also want
1297 // to avoid the rendered primitive from obscuring the blurred background
1298 // so we set an alpha value which works for all primitives except for
1299 // drawColor which can override the alpha with its color, but it now uses
1300 // a non-opaque color to avoid that problem.
1301 RenderEnvironment backdrop_env =
1303 SkSetup sk_backdrop_setup = [=](const SkSetupContext& ctx) {
1304 SkPaint setup_p;
1305 setup_p.setShader(MakeColorSource(ctx.image));
1306 ctx.canvas->drawPaint(setup_p);
1307 };
1308 DlSetup dl_backdrop_setup = [=](const DlSetupContext& ctx) {
1309 DlPaint setup_p;
1310 setup_p.setColorSource(MakeColorSource(ctx.image));
1311 ctx.canvas->DrawPaint(setup_p);
1312 };
1313 SkSetup sk_content_setup = [=](const SkSetupContext& ctx) {
1314 ctx.paint.setAlpha(ctx.paint.getAlpha() / 2);
1315 };
1316 DlSetup dl_content_setup = [=](const DlSetupContext& ctx) {
1317 ctx.paint.setAlpha(ctx.paint.getAlpha() / 2);
1318 };
1319 backdrop_env.init_ref(sk_backdrop_setup, testP.sk_renderer(),
1320 dl_backdrop_setup, testP.dl_renderer(),
1321 testP.imp_renderer());
1322 quickCompareToReference(backdrop_env, "backdrop");
1323
1324 DlBlurImageFilter dl_backdrop(5, 5, DlTileMode::kDecal);
1325 auto sk_backdrop =
1326 SkImageFilters::Blur(5, 5, SkTileMode::kDecal, nullptr);
1327 RenderWith(
1328 testP, backdrop_env, tolerance,
1330 "saveLayer with backdrop",
1331 [=](const SkSetupContext& ctx) {
1332 sk_backdrop_setup(ctx);
1333 ctx.canvas->saveLayer(
1334 SkCanvas::SaveLayerRec(nullptr, nullptr, sk_backdrop.get(),
1335 SkTileMode::kDecal, nullptr, 0));
1336 sk_content_setup(ctx);
1337 },
1338 [=](const DlSetupContext& ctx) {
1339 dl_backdrop_setup(ctx);
1340 ctx.canvas->SaveLayer(std::nullopt, nullptr, &dl_backdrop);
1341 dl_content_setup(ctx);
1342 })
1343 .with_restore(sk_safe_restore, dl_safe_restore, true));
1344 RenderWith(testP, backdrop_env, tolerance,
1346 "saveLayer with bounds and backdrop",
1347 [=](const SkSetupContext& ctx) {
1348 sk_backdrop_setup(ctx);
1349 ctx.canvas->saveLayer(SkCanvas::SaveLayerRec(
1350 &ToSkRect(layer_bounds), nullptr, sk_backdrop.get(),
1351 SkTileMode::kDecal, nullptr, 0));
1352 sk_content_setup(ctx);
1353 },
1354 [=](const DlSetupContext& ctx) {
1355 dl_backdrop_setup(ctx);
1356 ctx.canvas->SaveLayer(layer_bounds, nullptr,
1357 &dl_backdrop);
1358 dl_content_setup(ctx);
1359 })
1360 .with_restore(sk_safe_restore, dl_safe_restore, true));
1361 RenderWith(
1362 testP, backdrop_env, tolerance,
1364 "clipped saveLayer with backdrop",
1365 [=](const SkSetupContext& ctx) {
1366 sk_backdrop_setup(ctx);
1367 ctx.canvas->clipRect(ToSkRect(layer_bounds));
1368 ctx.canvas->saveLayer(
1369 SkCanvas::SaveLayerRec(nullptr, nullptr, sk_backdrop.get(),
1370 SkTileMode::kDecal, nullptr, 0));
1371 sk_content_setup(ctx);
1372 },
1373 [=](const DlSetupContext& ctx) {
1374 dl_backdrop_setup(ctx);
1375 ctx.canvas->ClipRect(layer_bounds);
1376 ctx.canvas->SaveLayer(std::nullopt, nullptr, &dl_backdrop);
1377 dl_content_setup(ctx);
1378 })
1379 .with_restore(sk_safe_restore, dl_safe_restore, true));
1380 }
1381
1382 {
1383 // clang-format off
1384 constexpr float rotate_alpha_color_matrix[20] = {
1385 0, 1, 0, 0 , 0,
1386 0, 0, 1, 0 , 0,
1387 1, 0, 0, 0 , 0,
1388 0, 0, 0, 0.5, 0,
1389 };
1390 // clang-format on
1391 auto dl_alpha_rotate_filter =
1392 DlColorFilter::MakeMatrix(rotate_alpha_color_matrix);
1393 auto sk_alpha_rotate_filter =
1394 SkColorFilters::Matrix(rotate_alpha_color_matrix);
1395 {
1396 RenderWith(testP, env, tolerance,
1398 "saveLayer ColorFilter, no bounds",
1399 [=](const SkSetupContext& ctx) {
1400 SkPaint save_p;
1401 save_p.setColorFilter(sk_alpha_rotate_filter);
1402 ctx.canvas->saveLayer(nullptr, &save_p);
1403 ctx.paint.setStrokeWidth(5.0);
1404 },
1405 [=](const DlSetupContext& ctx) {
1406 DlPaint save_p;
1407 save_p.setColorFilter(dl_alpha_rotate_filter);
1408 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1409 ctx.paint.setStrokeWidth(5.0);
1410 })
1411 .with_restore(sk_safe_restore, dl_safe_restore, true));
1412 }
1413 {
1414 RenderWith(testP, env, tolerance,
1416 "saveLayer ColorFilter and bounds",
1417 [=](const SkSetupContext& ctx) {
1418 SkPaint save_p;
1419 save_p.setColorFilter(sk_alpha_rotate_filter);
1420 ctx.canvas->saveLayer(ToSkRect(kRenderBounds),
1421 &save_p);
1422 ctx.paint.setStrokeWidth(5.0);
1423 },
1424 [=](const DlSetupContext& ctx) {
1425 DlPaint save_p;
1426 save_p.setColorFilter(dl_alpha_rotate_filter);
1427 ctx.canvas->SaveLayer(kRenderBounds, &save_p);
1428 ctx.paint.setStrokeWidth(5.0);
1429 })
1430 .with_restore(sk_safe_restore, dl_safe_restore, true));
1431 }
1432 }
1433
1434 {
1435 // clang-format off
1436 constexpr float color_matrix[20] = {
1437 0.5, 0, 0, 0, 0.5,
1438 0, 0.5, 0, 0, 0.5,
1439 0, 0, 0.5, 0, 0.5,
1440 0, 0, 0, 1, 0,
1441 };
1442 // clang-format on
1443 auto dl_color_filter = DlColorFilter::MakeMatrix(color_matrix);
1444 auto dl_cf_image_filter = DlImageFilter::MakeColorFilter(dl_color_filter);
1445 auto sk_cf_image_filter = SkImageFilters::ColorFilter(
1446 SkColorFilters::Matrix(color_matrix), nullptr);
1447 {
1448 RenderWith(testP, env, tolerance,
1450 "saveLayer ImageFilter, no bounds",
1451 [=](const SkSetupContext& ctx) {
1452 SkPaint save_p;
1453 save_p.setImageFilter(sk_cf_image_filter);
1454 ctx.canvas->saveLayer(nullptr, &save_p);
1455 ctx.paint.setStrokeWidth(5.0);
1456 },
1457 [=](const DlSetupContext& ctx) {
1458 DlPaint save_p;
1459 save_p.setImageFilter(dl_cf_image_filter);
1460 ctx.canvas->SaveLayer(std::nullopt, &save_p);
1461 ctx.paint.setStrokeWidth(5.0);
1462 })
1463 .with_restore(sk_safe_restore, dl_safe_restore, true));
1464 }
1465 {
1466 RenderWith(testP, env, tolerance,
1468 "saveLayer ImageFilter and bounds",
1469 [=](const SkSetupContext& ctx) {
1470 SkPaint save_p;
1471 save_p.setImageFilter(sk_cf_image_filter);
1472 ctx.canvas->saveLayer(ToSkRect(kRenderBounds),
1473 &save_p);
1474 ctx.paint.setStrokeWidth(5.0);
1475 },
1476 [=](const DlSetupContext& ctx) {
1477 DlPaint save_p;
1478 save_p.setImageFilter(dl_cf_image_filter);
1479 ctx.canvas->SaveLayer(kRenderBounds, &save_p);
1480 ctx.paint.setStrokeWidth(5.0);
1481 })
1482 .with_restore(sk_safe_restore, dl_safe_restore, true));
1483 }
1484 }
1485 }
1486
1487 static void RenderWithAttributes(const TestParameters& testP,
1488 const RenderEnvironment& env,
1489 const BoundsTolerance& tolerance) {
1490 RenderWith(testP, env, tolerance, CaseParameters("Defaults Test"));
1491
1492 {
1493 // CPU renderer with default line width of 0 does not show antialiasing
1494 // for stroked primitives, so we make a new reference with a non-trivial
1495 // stroke width to demonstrate the differences
1497 // Tweak the bounds tolerance for the displacement of 1/10 of a pixel
1498 const BoundsTolerance aa_tolerance = tolerance.addBoundsPadding(1, 1);
1499 auto sk_aa_setup = [=](SkSetupContext ctx, bool is_aa) {
1500 ctx.canvas->translate(0.1, 0.1);
1501 ctx.paint.setAntiAlias(is_aa);
1502 ctx.paint.setStrokeWidth(5.0);
1503 };
1504 auto dl_aa_setup = [=](DlSetupContext ctx, bool is_aa) {
1505 ctx.canvas->Translate(0.1, 0.1);
1506 ctx.paint.setAntiAlias(is_aa);
1507 ctx.paint.setStrokeWidth(5.0);
1508 };
1509 aa_env.init_ref(
1510 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, false); },
1511 testP.sk_renderer(),
1512 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); },
1513 testP.dl_renderer(), testP.imp_renderer());
1514 quickCompareToReference(aa_env, "AntiAlias");
1515 RenderWith(
1516 testP, aa_env, aa_tolerance,
1518 "AntiAlias == True",
1519 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, true); },
1520 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, true); }));
1521 RenderWith(
1522 testP, aa_env, aa_tolerance,
1524 "AntiAlias == False",
1525 [=](const SkSetupContext& ctx) { sk_aa_setup(ctx, false); },
1526 [=](const DlSetupContext& ctx) { dl_aa_setup(ctx, false); }));
1527 }
1528
1529 RenderWith( //
1530 testP, env, tolerance,
1532 "Color == Blue",
1533 [=](const SkSetupContext& ctx) {
1534 ctx.paint.setColor(SK_ColorBLUE);
1535 },
1536 [=](const DlSetupContext& ctx) {
1537 ctx.paint.setColor(DlColor::kBlue());
1538 }));
1539 RenderWith( //
1540 testP, env, tolerance,
1542 "Color == Green",
1543 [=](const SkSetupContext& ctx) {
1544 ctx.paint.setColor(SK_ColorGREEN);
1545 },
1546 [=](const DlSetupContext& ctx) {
1547 ctx.paint.setColor(DlColor::kGreen());
1548 }));
1549
1550 RenderWithStrokes(testP, env, tolerance);
1551
1552 {
1553 // half opaque cyan
1554 DlColor blendable_color = DlColor::kCyan().withAlpha(0x7f);
1555 DlColor bg = DlColor::kWhite();
1556
1557 RenderWith(testP, env, tolerance,
1559 "Blend == SrcIn",
1560 [=](const SkSetupContext& ctx) {
1561 ctx.paint.setBlendMode(SkBlendMode::kSrcIn);
1562 ctx.paint.setColor(blendable_color.argb());
1563 },
1564 [=](const DlSetupContext& ctx) {
1565 ctx.paint.setBlendMode(DlBlendMode::kSrcIn);
1566 ctx.paint.setColor(blendable_color);
1567 })
1568 .with_bg(bg));
1569 RenderWith(testP, env, tolerance,
1571 "Blend == DstIn",
1572 [=](const SkSetupContext& ctx) {
1573 ctx.paint.setBlendMode(SkBlendMode::kDstIn);
1574 ctx.paint.setColor(blendable_color.argb());
1575 },
1576 [=](const DlSetupContext& ctx) {
1577 ctx.paint.setBlendMode(DlBlendMode::kDstIn);
1578 ctx.paint.setColor(blendable_color);
1579 })
1580 .with_bg(bg));
1581 }
1582
1583 {
1584 // Being able to see a blur requires some non-default attributes,
1585 // like a non-trivial stroke width and a shader rather than a color
1586 // (for drawPaint) so we create a new environment for these tests.
1588 SkSetup sk_blur_setup = [=](const SkSetupContext& ctx) {
1589 ctx.paint.setShader(MakeColorSource(ctx.image));
1590 ctx.paint.setStrokeWidth(5.0);
1591 };
1592 DlSetup dl_blur_setup = [=](const DlSetupContext& ctx) {
1593 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1594 ctx.paint.setStrokeWidth(5.0);
1595 };
1596 blur_env.init_ref(sk_blur_setup, testP.sk_renderer(), //
1597 dl_blur_setup, testP.dl_renderer(),
1598 testP.imp_renderer());
1599 quickCompareToReference(blur_env, "blur");
1600 DlBlurImageFilter dl_filter_decal_5(5.0, 5.0, DlTileMode::kDecal);
1601 auto sk_filter_decal_5 =
1602 SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr);
1603 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1604 {
1605 RenderWith(testP, blur_env, blur_5_tolerance,
1607 "ImageFilter == Decal Blur 5",
1608 [=](const SkSetupContext& ctx) {
1609 sk_blur_setup(ctx);
1610 ctx.paint.setImageFilter(sk_filter_decal_5);
1611 },
1612 [=](const DlSetupContext& ctx) {
1613 dl_blur_setup(ctx);
1614 ctx.paint.setImageFilter(&dl_filter_decal_5);
1615 }));
1616 }
1617 DlBlurImageFilter dl_filter_clamp_5(5.0, 5.0, DlTileMode::kClamp);
1618 auto sk_filter_clamp_5 =
1619 SkImageFilters::Blur(5.0, 5.0, SkTileMode::kClamp, nullptr);
1620 {
1621 RenderWith(testP, blur_env, blur_5_tolerance,
1623 "ImageFilter == Clamp Blur 5",
1624 [=](const SkSetupContext& ctx) {
1625 sk_blur_setup(ctx);
1626 ctx.paint.setImageFilter(sk_filter_clamp_5);
1627 },
1628 [=](const DlSetupContext& ctx) {
1629 dl_blur_setup(ctx);
1630 ctx.paint.setImageFilter(&dl_filter_clamp_5);
1631 }));
1632 }
1633 }
1634
1635 {
1636 // Being able to see a dilate requires some non-default attributes,
1637 // like a non-trivial stroke width and a shader rather than a color
1638 // (for drawPaint) so we create a new environment for these tests.
1640 SkSetup sk_dilate_setup = [=](const SkSetupContext& ctx) {
1641 ctx.paint.setShader(MakeColorSource(ctx.image));
1642 ctx.paint.setStrokeWidth(5.0);
1643 };
1644 DlSetup dl_dilate_setup = [=](const DlSetupContext& ctx) {
1645 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1646 ctx.paint.setStrokeWidth(5.0);
1647 };
1648 dilate_env.init_ref(sk_dilate_setup, testP.sk_renderer(), //
1649 dl_dilate_setup, testP.dl_renderer(),
1650 testP.imp_renderer());
1651 quickCompareToReference(dilate_env, "dilate");
1652 DlDilateImageFilter dl_dilate_filter_5(5.0, 5.0);
1653 auto sk_dilate_filter_5 = SkImageFilters::Dilate(5.0, 5.0, nullptr);
1654 RenderWith(testP, dilate_env, tolerance,
1656 "ImageFilter == Dilate 5",
1657 [=](const SkSetupContext& ctx) {
1658 sk_dilate_setup(ctx);
1659 ctx.paint.setImageFilter(sk_dilate_filter_5);
1660 },
1661 [=](const DlSetupContext& ctx) {
1662 dl_dilate_setup(ctx);
1663 ctx.paint.setImageFilter(&dl_dilate_filter_5);
1664 }));
1665 }
1666
1667 {
1668 // Being able to see an erode requires some non-default attributes,
1669 // like a non-trivial stroke width and a shader rather than a color
1670 // (for drawPaint) so we create a new environment for these tests.
1672 SkSetup sk_erode_setup = [=](const SkSetupContext& ctx) {
1673 ctx.paint.setShader(MakeColorSource(ctx.image));
1674 ctx.paint.setStrokeWidth(6.0);
1675 };
1676 DlSetup dl_erode_setup = [=](const DlSetupContext& ctx) {
1677 ctx.paint.setColorSource(MakeColorSource(ctx.image));
1678 ctx.paint.setStrokeWidth(6.0);
1679 };
1680 erode_env.init_ref(sk_erode_setup, testP.sk_renderer(), //
1681 dl_erode_setup, testP.dl_renderer(),
1682 testP.imp_renderer());
1683 quickCompareToReference(erode_env, "erode");
1684 // do not erode too much, because some tests assert there are enough
1685 // pixels that are changed.
1686 DlErodeImageFilter dl_erode_filter_1(1.0, 1.0);
1687 auto sk_erode_filter_1 = SkImageFilters::Erode(1.0, 1.0, nullptr);
1688 RenderWith(testP, erode_env, tolerance,
1690 "ImageFilter == Erode 1",
1691 [=](const SkSetupContext& ctx) {
1692 sk_erode_setup(ctx);
1693 ctx.paint.setImageFilter(sk_erode_filter_1);
1694 },
1695 [=](const DlSetupContext& ctx) {
1696 dl_erode_setup(ctx);
1697 ctx.paint.setImageFilter(&dl_erode_filter_1);
1698 }));
1699 }
1700
1701 {
1702 // clang-format off
1703 constexpr float rotate_color_matrix[20] = {
1704 0, 1, 0, 0, 0,
1705 0, 0, 1, 0, 0,
1706 1, 0, 0, 0, 0,
1707 0, 0, 0, 1, 0,
1708 };
1709 constexpr float invert_color_matrix[20] = {
1710 -1.0, 0, 0, 1.0, 0,
1711 0, -1.0, 0, 1.0, 0,
1712 0, 0, -1.0, 1.0, 0,
1713 1.0, 1.0, 1.0, 1.0, 0,
1714 };
1715 // clang-format on
1716 auto dl_color_filter = DlColorFilter::MakeMatrix(rotate_color_matrix);
1717 auto sk_color_filter = SkColorFilters::Matrix(rotate_color_matrix);
1718 {
1719 DlColor bg = DlColor::kWhite();
1720 RenderWith(testP, env, tolerance,
1722 "ColorFilter == RotateRGB",
1723 [=](const SkSetupContext& ctx) {
1724 ctx.paint.setColor(SK_ColorYELLOW);
1725 ctx.paint.setColorFilter(sk_color_filter);
1726 },
1727 [=](const DlSetupContext& ctx) {
1728 ctx.paint.setColor(DlColor::kYellow());
1729 ctx.paint.setColorFilter(dl_color_filter);
1730 })
1731 .with_bg(bg));
1732 }
1733 {
1734 DlColor bg = DlColor::kWhite();
1735 RenderWith(testP, env, tolerance,
1737 "ColorFilter == Invert",
1738 [=](const SkSetupContext& ctx) {
1739 ctx.paint.setColor(SK_ColorYELLOW);
1740 ctx.paint.setColorFilter(
1741 SkColorFilters::Matrix(invert_color_matrix));
1742 },
1743 [=](const DlSetupContext& ctx) {
1744 ctx.paint.setColor(DlColor::kYellow());
1745 ctx.paint.setInvertColors(true);
1746 })
1747 .with_bg(bg));
1748 }
1749 }
1750
1751 {
1752 const DlBlurMaskFilter dl_mask_filter(DlBlurStyle::kNormal, 5.0);
1753 auto sk_mask_filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0);
1754 BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
1755 {
1756 // Stroked primitives need some non-trivial stroke size to be blurred
1757 RenderWith(testP, env, blur_5_tolerance,
1759 "MaskFilter == Blur 5",
1760 [=](const SkSetupContext& ctx) {
1761 ctx.paint.setStrokeWidth(5.0);
1762 ctx.paint.setMaskFilter(sk_mask_filter);
1763 },
1764 [=](const DlSetupContext& ctx) {
1765 ctx.paint.setStrokeWidth(5.0);
1766 ctx.paint.setMaskFilter(&dl_mask_filter);
1767 }));
1768 }
1769 }
1770
1771 {
1772 DlPoint dl_end_points[] = {
1775 };
1776 DlColor dl_colors[] = {
1780 };
1781 SkColor sk_colors[] = {
1782 SK_ColorGREEN,
1783 SkColorSetA(SK_ColorYELLOW, 0x7f),
1784 SK_ColorBLUE,
1785 };
1786 float stops[] = {
1787 0.0,
1788 0.5,
1789 1.0,
1790 };
1791 auto dl_gradient =
1792 DlColorSource::MakeLinear(dl_end_points[0], dl_end_points[1], 3,
1793 dl_colors, stops, DlTileMode::kMirror);
1794 auto sk_gradient = SkGradientShader::MakeLinear(
1795 ToSkPoints(dl_end_points), sk_colors, stops, 3, SkTileMode::kMirror,
1796 0, nullptr);
1797 {
1798 RenderWith(testP, env, tolerance,
1800 "LinearGradient GYB",
1801 [=](const SkSetupContext& ctx) {
1802 ctx.paint.setShader(sk_gradient);
1803 ctx.paint.setDither(testP.uses_gradient());
1804 },
1805 [=](const DlSetupContext& ctx) {
1806 ctx.paint.setColorSource(dl_gradient);
1807 }));
1808 }
1809 }
1810 }
1811
1812 static void RenderWithStrokes(const TestParameters& testP,
1813 const RenderEnvironment& env,
1814 const BoundsTolerance& tolerance_in) {
1815 // The test cases were generated with geometry that will try to fill
1816 // out the various miter limits used for testing, but they can be off
1817 // by a couple of pixels so we will relax bounds testing for strokes by
1818 // a couple of pixels.
1819 BoundsTolerance tolerance = tolerance_in.addBoundsPadding(2, 2);
1820 RenderWith(testP, env, tolerance,
1822 "Fill",
1823 [=](const SkSetupContext& ctx) {
1824 ctx.paint.setStyle(SkPaint::kFill_Style);
1825 },
1826 [=](const DlSetupContext& ctx) {
1827 ctx.paint.setDrawStyle(DlDrawStyle::kFill);
1828 }));
1829 // Skia on HW produces a strong miter consistent with width=1.0
1830 // for any width less than a pixel, but the bounds computations of
1831 // both DL and SkPicture do not account for this. We will get
1832 // OOB pixel errors for the highly mitered drawPath geometry if
1833 // we don't set stroke width to 1.0 for that test on HW.
1834 // See https://bugs.chromium.org/p/skia/issues/detail?id=14046
1835 bool no_hairlines =
1836 testP.is_draw_path() &&
1837 env.provider()->backend_type() != BackendType::kSoftwareBackend;
1838 RenderWith(testP, env, tolerance,
1840 "Stroke + defaults",
1841 [=](const SkSetupContext& ctx) {
1842 if (no_hairlines) {
1843 ctx.paint.setStrokeWidth(1.0);
1844 }
1845 ctx.paint.setStyle(SkPaint::kStroke_Style);
1846 },
1847 [=](const DlSetupContext& ctx) {
1848 if (no_hairlines) {
1849 ctx.paint.setStrokeWidth(1.0);
1850 }
1851 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1852 }));
1853
1854 RenderWith(testP, env, tolerance,
1856 "Fill + unnecessary StrokeWidth 10",
1857 [=](const SkSetupContext& ctx) {
1858 ctx.paint.setStyle(SkPaint::kFill_Style);
1859 ctx.paint.setStrokeWidth(10.0);
1860 },
1861 [=](const DlSetupContext& ctx) {
1862 ctx.paint.setDrawStyle(DlDrawStyle::kFill);
1863 ctx.paint.setStrokeWidth(10.0);
1864 }));
1865
1866 RenderEnvironment stroke_base_env =
1868 SkSetup sk_stroke_setup = [=](const SkSetupContext& ctx) {
1869 ctx.paint.setStyle(SkPaint::kStroke_Style);
1870 ctx.paint.setStrokeWidth(5.0);
1871 };
1872 DlSetup dl_stroke_setup = [=](const DlSetupContext& ctx) {
1873 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1874 ctx.paint.setStrokeWidth(5.0);
1875 };
1876 stroke_base_env.init_ref(sk_stroke_setup, testP.sk_renderer(),
1877 dl_stroke_setup, testP.dl_renderer(),
1878 testP.imp_renderer());
1879 quickCompareToReference(stroke_base_env, "stroke");
1880
1881 RenderWith(testP, stroke_base_env, tolerance,
1883 "Stroke Width 10",
1884 [=](const SkSetupContext& ctx) {
1885 ctx.paint.setStyle(SkPaint::kStroke_Style);
1886 ctx.paint.setStrokeWidth(10.0);
1887 },
1888 [=](const DlSetupContext& ctx) {
1889 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1890 ctx.paint.setStrokeWidth(10.0);
1891 }));
1892 RenderWith(testP, stroke_base_env, tolerance,
1894 "Stroke Width 5",
1895 [=](const SkSetupContext& ctx) {
1896 ctx.paint.setStyle(SkPaint::kStroke_Style);
1897 ctx.paint.setStrokeWidth(5.0);
1898 },
1899 [=](const DlSetupContext& ctx) {
1900 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1901 ctx.paint.setStrokeWidth(5.0);
1902 }));
1903
1904 RenderWith(testP, stroke_base_env, tolerance,
1906 "Stroke Width 5, Square Cap",
1907 [=](const SkSetupContext& ctx) {
1908 ctx.paint.setStyle(SkPaint::kStroke_Style);
1909 ctx.paint.setStrokeWidth(5.0);
1910 ctx.paint.setStrokeCap(SkPaint::kSquare_Cap);
1911 },
1912 [=](const DlSetupContext& ctx) {
1913 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1914 ctx.paint.setStrokeWidth(5.0);
1915 ctx.paint.setStrokeCap(DlStrokeCap::kSquare);
1916 }));
1917 RenderWith(testP, stroke_base_env, tolerance,
1919 "Stroke Width 5, Round Cap",
1920 [=](const SkSetupContext& ctx) {
1921 ctx.paint.setStyle(SkPaint::kStroke_Style);
1922 ctx.paint.setStrokeWidth(5.0);
1923 ctx.paint.setStrokeCap(SkPaint::kRound_Cap);
1924 },
1925 [=](const DlSetupContext& ctx) {
1926 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1927 ctx.paint.setStrokeWidth(5.0);
1928 ctx.paint.setStrokeCap(DlStrokeCap::kRound);
1929 }));
1930
1931 RenderWith(testP, stroke_base_env, tolerance,
1933 "Stroke Width 5, Bevel Join",
1934 [=](const SkSetupContext& ctx) {
1935 ctx.paint.setStyle(SkPaint::kStroke_Style);
1936 ctx.paint.setStrokeWidth(5.0);
1937 ctx.paint.setStrokeJoin(SkPaint::kBevel_Join);
1938 },
1939 [=](const DlSetupContext& ctx) {
1940 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1941 ctx.paint.setStrokeWidth(5.0);
1942 ctx.paint.setStrokeJoin(DlStrokeJoin::kBevel);
1943 }));
1944 RenderWith(testP, stroke_base_env, tolerance,
1946 "Stroke Width 5, Round Join",
1947 [=](const SkSetupContext& ctx) {
1948 ctx.paint.setStyle(SkPaint::kStroke_Style);
1949 ctx.paint.setStrokeWidth(5.0);
1950 ctx.paint.setStrokeJoin(SkPaint::kRound_Join);
1951 },
1952 [=](const DlSetupContext& ctx) {
1953 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1954 ctx.paint.setStrokeWidth(5.0);
1955 ctx.paint.setStrokeJoin(DlStrokeJoin::kRound);
1956 }));
1957
1958 RenderWith(testP, stroke_base_env, tolerance,
1960 "Stroke Width 5, Miter 10",
1961 [=](const SkSetupContext& ctx) {
1962 ctx.paint.setStyle(SkPaint::kStroke_Style);
1963 ctx.paint.setStrokeWidth(5.0);
1964 ctx.paint.setStrokeMiter(10.0);
1965 ctx.paint.setStrokeJoin(SkPaint::kMiter_Join);
1966 },
1967 [=](const DlSetupContext& ctx) {
1968 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1969 ctx.paint.setStrokeWidth(5.0);
1970 ctx.paint.setStrokeMiter(10.0);
1971 ctx.paint.setStrokeJoin(DlStrokeJoin::kMiter);
1972 }));
1973
1974 RenderWith(testP, stroke_base_env, tolerance,
1976 "Stroke Width 5, Miter 0",
1977 [=](const SkSetupContext& ctx) {
1978 ctx.paint.setStyle(SkPaint::kStroke_Style);
1979 ctx.paint.setStrokeWidth(5.0);
1980 ctx.paint.setStrokeMiter(0.0);
1981 ctx.paint.setStrokeJoin(SkPaint::kMiter_Join);
1982 },
1983 [=](const DlSetupContext& ctx) {
1984 ctx.paint.setDrawStyle(DlDrawStyle::kStroke);
1985 ctx.paint.setStrokeWidth(5.0);
1986 ctx.paint.setStrokeMiter(0.0);
1987 ctx.paint.setStrokeJoin(DlStrokeJoin::kMiter);
1988 }));
1989 }
1990
1991 static void RenderWithTransforms(const TestParameters& testP,
1992 const RenderEnvironment& env,
1993 const BoundsTolerance& tolerance) {
1994 // If the rendering method does not fill the corners of the original
1995 // bounds, then the estimate under rotation or skewing will be off
1996 // so we scale the padding by about 5% to compensate.
1997 BoundsTolerance skewed_tolerance = tolerance.mulScale(1.05, 1.05);
1998 RenderWith( //
1999 testP, env, tolerance,
2001 "Translate 5, 10", //
2002 [=](const SkSetupContext& ctx) { ctx.canvas->translate(5, 10); },
2003 [=](const DlSetupContext& ctx) { ctx.canvas->Translate(5, 10); }));
2004 RenderWith( //
2005 testP, env, tolerance,
2007 "Scale +5%", //
2008 [=](const SkSetupContext& ctx) { ctx.canvas->scale(1.05, 1.05); },
2009 [=](const DlSetupContext& ctx) { ctx.canvas->Scale(1.05, 1.05); }));
2010 RenderWith( //
2011 testP, env, skewed_tolerance,
2013 "Rotate 5 degrees", //
2014 [=](const SkSetupContext& ctx) { ctx.canvas->rotate(5); },
2015 [=](const DlSetupContext& ctx) { ctx.canvas->Rotate(5); }));
2016 RenderWith( //
2017 testP, env, skewed_tolerance,
2019 "Skew 5%", //
2020 [=](const SkSetupContext& ctx) { ctx.canvas->skew(0.05, 0.05); },
2021 [=](const DlSetupContext& ctx) { ctx.canvas->Skew(0.05, 0.05); }));
2022 {
2023 // This rather odd transform can cause slight differences in
2024 // computing in-bounds samples depending on which base rendering
2025 // routine Skia uses. Making sure our matrix values are powers
2026 // of 2 reduces, but does not eliminate, these slight differences
2027 // in calculation when we are comparing rendering with an alpha
2028 // to rendering opaque colors in the group opacity tests, for
2029 // example.
2030 DlScalar tweak = 1.0 / 16.0;
2031 DlMatrix matrix = DlMatrix::MakeRow(
2032 // clang-format off
2033 1.0 + tweak, tweak, 0, 5,
2034 tweak, 1.0 + tweak, 0, 10,
2035 0, 0, 1, 0,
2036 0, 0, 0, 1
2037 // clang-format on
2038 );
2039 RenderWith( //
2040 testP, env, skewed_tolerance,
2042 "Transform 2D Affine Matrix",
2043 [=](const SkSetupContext& ctx) {
2044 ctx.canvas->concat(ToSkMatrix(matrix));
2045 },
2046 [=](const DlSetupContext& ctx) {
2047 ctx.canvas->Transform(matrix);
2048 }));
2049 RenderWith( //
2050 testP, env, skewed_tolerance,
2052 "Transform 2D Affine inline",
2053 [=](const SkSetupContext& ctx) {
2054 ctx.canvas->concat(SkMatrix::MakeAll(1.0 + tweak, tweak, 5, //
2055 tweak, 1.0 + tweak, 10, //
2056 0, 0, 1));
2057 },
2058 [=](const DlSetupContext& ctx) {
2059 ctx.canvas->Transform2DAffine(1.0 + tweak, tweak, 5, //
2060 tweak, 1.0 + tweak, 10);
2061 }));
2062 }
2063 {
2064 DlMatrix matrix = DlMatrix::MakeRow(1.0f, 0.0f, 0.0f, kRenderCenterX, //
2065 0.0f, 1.0f, 0.0f, kRenderCenterY, //
2066 0.0f, 0.0f, 1.0f, 0.0f, //
2067 0.0f, 0.0f, .001f, 1.0f);
2068 matrix = matrix * DlMatrix::MakeRotationX(DlDegrees(3));
2069 matrix = matrix * DlMatrix::MakeRotationY(DlDegrees(4));
2070 matrix = matrix.Translate({-kRenderCenterX, -kRenderCenterY, 0.0f});
2071 RenderWith( //
2072 testP, env, skewed_tolerance,
2074 "Transform Full Perspective Matrix",
2075 [=](const SkSetupContext& ctx) {
2076 ctx.canvas->concat(ToSkM44(matrix));
2077 },
2078 [=](const DlSetupContext& ctx) {
2079 ctx.canvas->Transform(matrix);
2080 }));
2081 RenderWith( //
2082 testP, env, skewed_tolerance,
2084 "Transform Full Perspective inline",
2085 [=](const SkSetupContext& ctx) {
2086 ctx.canvas->concat(SkM44(
2087 // These values match what ends up in matrix above
2088 // clang-format off
2089 0.997564, 0.000000, 0.069756, 0.243591,
2090 0.003651, 0.998630, -0.052208, -0.228027,
2091 -0.069661, 0.052336, 0.996197, 1.732491,
2092 -0.000070, 0.000052, 0.000996, 1.001732
2093 // clang-format on
2094 ));
2095 },
2096 [=](const DlSetupContext& ctx) {
2097 ctx.canvas->TransformFullPerspective(
2098 // These values match what ends up in matrix above
2099 // clang-format off
2100 0.997564, 0.000000, 0.069756, 0.243591,
2101 0.003651, 0.998630, -0.052208, -0.228027,
2102 -0.069661, 0.052336, 0.996197, 1.732491,
2103 -0.000070, 0.000052, 0.000996, 1.001732
2104 // clang-format on
2105 );
2106 }));
2107 }
2108 }
2109
2110 static void RenderWithClips(const TestParameters& testP,
2111 const RenderEnvironment& env,
2112 const BoundsTolerance& diff_tolerance) {
2113 // We used to use an inset of 15.5 pixels here, but since Skia's rounding
2114 // behavior at the center of pixels does not match between HW and SW, we
2115 // ended up with some clips including different pixels between the two
2116 // destinations and this interacted poorly with the carefully chosen
2117 // geometry in some of the tests which was designed to have just the
2118 // right features fully filling the clips based on the SW rounding. By
2119 // moving to a 15.4 inset, the edge of the clip is never on the "rounding
2120 // edge" of a pixel.
2121 DlRect r_clip = kRenderBounds.Expand(-15.4, -15.4);
2122 BoundsTolerance intersect_tolerance = diff_tolerance.clip(r_clip);
2123 intersect_tolerance = intersect_tolerance.addPostClipPadding(1, 1);
2124 RenderWith(testP, env, intersect_tolerance,
2126 "Hard ClipRect inset by 15.4",
2127 [=](const SkSetupContext& ctx) {
2128 ctx.canvas->clipRect(ToSkRect(r_clip),
2129 SkClipOp::kIntersect, false);
2130 },
2131 [=](const DlSetupContext& ctx) {
2132 ctx.canvas->ClipRect(r_clip, DlClipOp::kIntersect, false);
2133 }));
2134 RenderWith(testP, env, intersect_tolerance,
2136 "AntiAlias ClipRect inset by 15.4",
2137 [=](const SkSetupContext& ctx) {
2138 ctx.canvas->clipRect(ToSkRect(r_clip),
2139 SkClipOp::kIntersect, true);
2140 },
2141 [=](const DlSetupContext& ctx) {
2142 ctx.canvas->ClipRect(r_clip, DlClipOp::kIntersect, true);
2143 }));
2144 RenderWith(testP, env, diff_tolerance,
2146 "Hard ClipRect Diff, inset by 15.4",
2147 [=](const SkSetupContext& ctx) {
2148 ctx.canvas->clipRect(ToSkRect(r_clip),
2149 SkClipOp::kDifference, false);
2150 },
2151 [=](const DlSetupContext& ctx) {
2152 ctx.canvas->ClipRect(r_clip, DlClipOp::kDifference, false);
2153 })
2154 .with_diff_clip());
2155 RenderWith(testP, env, intersect_tolerance,
2157 "Hard ClipOval",
2158 [=](const SkSetupContext& ctx) {
2159 // Skia lacks clipOval so we use an oval SkRRect
2160 ctx.canvas->clipRRect(SkRRect::MakeOval(ToSkRect(r_clip)),
2161 SkClipOp::kIntersect, false);
2162 },
2163 [=](const DlSetupContext& ctx) {
2164 ctx.canvas->ClipOval(r_clip, DlClipOp::kIntersect, false);
2165 }));
2166 RenderWith(testP, env, intersect_tolerance,
2168 "AntiAlias ClipOval",
2169 [=](const SkSetupContext& ctx) {
2170 // Skia lacks clipOval so we use an oval SkRRect
2171 ctx.canvas->clipRRect(SkRRect::MakeOval(ToSkRect(r_clip)),
2172 SkClipOp::kIntersect, true);
2173 },
2174 [=](const DlSetupContext& ctx) {
2175 ctx.canvas->ClipOval(r_clip, DlClipOp::kIntersect, true);
2176 }));
2177 RenderWith(testP, env, diff_tolerance,
2179 "Hard ClipOval Diff",
2180 [=](const SkSetupContext& ctx) {
2181 // Skia lacks clipOval so we use an oval SkRRect
2182 ctx.canvas->clipRRect(SkRRect::MakeOval(ToSkRect(r_clip)),
2183 SkClipOp::kDifference, false);
2184 },
2185 [=](const DlSetupContext& ctx) {
2186 ctx.canvas->ClipOval(r_clip, DlClipOp::kDifference, false);
2187 })
2188 .with_diff_clip());
2189 // This test RR clip used to use very small radii, but due to
2190 // optimizations in the HW rrect rasterization, this caused small
2191 // bulges in the corners of the RRect which were interpreted as
2192 // "clip overruns" by the clip OOB pixel testing code. Using less
2193 // abusively small radii fixes the problem.
2194 DlRoundRect rr_clip = DlRoundRect::MakeRectXY(r_clip, 9, 9);
2195 RenderWith(testP, env, intersect_tolerance,
2197 "Hard ClipRRect with radius of 9",
2198 [=](const SkSetupContext& ctx) {
2199 ctx.canvas->clipRRect(ToSkRRect(rr_clip),
2200 SkClipOp::kIntersect, false);
2201 },
2202 [=](const DlSetupContext& ctx) {
2203 ctx.canvas->ClipRoundRect(rr_clip, DlClipOp::kIntersect,
2204 false);
2205 }));
2206 RenderWith(testP, env, intersect_tolerance,
2208 "AntiAlias ClipRRect with radius of 9",
2209 [=](const SkSetupContext& ctx) {
2210 ctx.canvas->clipRRect(ToSkRRect(rr_clip),
2211 SkClipOp::kIntersect, true);
2212 },
2213 [=](const DlSetupContext& ctx) {
2214 ctx.canvas->ClipRoundRect(rr_clip, DlClipOp::kIntersect,
2215 true);
2216 }));
2217 RenderWith(testP, env, diff_tolerance,
2219 "Hard ClipRRect Diff, with radius of 9",
2220 [=](const SkSetupContext& ctx) {
2221 ctx.canvas->clipRRect(ToSkRRect(rr_clip),
2222 SkClipOp::kDifference, false);
2223 },
2224 [=](const DlSetupContext& ctx) {
2225 ctx.canvas->ClipRoundRect(rr_clip, DlClipOp::kDifference,
2226 false);
2227 })
2228 .with_diff_clip());
2229 DlPathBuilder path_builder;
2230 path_builder.SetFillType(DlPathFillType::kOdd);
2231 path_builder.AddRect(r_clip);
2232 path_builder.AddCircle(DlPoint(kRenderCenterX, kRenderCenterY), 1.0f);
2233 DlPath path_clip = path_builder.TakePath();
2234 RenderWith(testP, env, intersect_tolerance,
2236 "Hard ClipPath inset by 15.4",
2237 [=](const SkSetupContext& ctx) {
2238 ctx.canvas->clipPath(path_clip.GetSkPath(),
2239 SkClipOp::kIntersect, false);
2240 },
2241 [=](const DlSetupContext& ctx) {
2242 ctx.canvas->ClipPath(path_clip, DlClipOp::kIntersect,
2243 false);
2244 }));
2245 RenderWith(testP, env, intersect_tolerance,
2247 "AntiAlias ClipPath inset by 15.4",
2248 [=](const SkSetupContext& ctx) {
2249 ctx.canvas->clipPath(path_clip.GetSkPath(),
2250 SkClipOp::kIntersect, true);
2251 },
2252 [=](const DlSetupContext& ctx) {
2253 ctx.canvas->ClipPath(path_clip, DlClipOp::kIntersect,
2254 true);
2255 }));
2256 RenderWith(testP, env, diff_tolerance,
2258 "Hard ClipPath Diff, inset by 15.4",
2259 [=](const SkSetupContext& ctx) {
2260 ctx.canvas->clipPath(path_clip.GetSkPath(),
2261 SkClipOp::kDifference, false);
2262 },
2263 [=](const DlSetupContext& ctx) {
2264 ctx.canvas->ClipPath(path_clip, DlClipOp::kDifference,
2265 false);
2266 })
2267 .with_diff_clip());
2268 }
2269
2270 enum class DirectoryStatus {
2271 kExisted,
2272 kCreated,
2273 kFailed,
2274 };
2275
2276 static DirectoryStatus CheckDir(const std::string& dir) {
2277 auto ret =
2279 if (ret.is_valid()) {
2281 }
2282 ret =
2284 if (ret.is_valid()) {
2286 }
2287 FML_LOG(ERROR) << "Could not create directory (" << dir
2288 << ") for impeller failure images" << ", ret = " << ret.get()
2289 << ", errno = " << errno;
2291 }
2292
2294 std::string base_dir = "./impeller_failure_images";
2295 if (CheckDir(base_dir) == DirectoryStatus::kFailed) {
2296 return;
2297 }
2298 for (int i = 0; i < 10000; i++) {
2299 std::string sub_dir = std::to_string(i);
2300 while (sub_dir.length() < 4) {
2301 sub_dir = "0" + sub_dir;
2302 }
2303 std::string try_dir = base_dir + "/" + sub_dir;
2304 switch (CheckDir(try_dir)) {
2306 break;
2309 return;
2311 return;
2312 }
2313 }
2314 FML_LOG(ERROR) << "Too many output directories for Impeller failure images";
2315 }
2316
2317 static void save_to_png(const RenderResult* result,
2318 const std::string& op_desc,
2319 const std::string& reason) {
2321 return;
2322 }
2323 if (ImpellerFailureImageDirectory.length() == 0) {
2325 if (ImpellerFailureImageDirectory.length() == 0) {
2327 return;
2328 }
2329 }
2330
2331 std::string filename = ImpellerFailureImageDirectory + "/";
2332 for (const char& ch : op_desc) {
2333 filename += (ch == ':' || ch == ' ') ? '_' : ch;
2334 }
2335 filename = filename + ".png";
2336 result->write(filename);
2337 ImpellerFailureImages.push_back(filename);
2338 FML_LOG(ERROR) << reason << ": " << filename;
2339 }
2340
2341 static void RenderWith(const TestParameters& testP,
2342 const RenderEnvironment& env,
2343 const BoundsTolerance& tolerance_in,
2344 const CaseParameters& caseP) {
2345 std::string test_name =
2346 ::testing::UnitTest::GetInstance()->current_test_info()->name();
2347 const std::string info =
2348 env.backend_name() + ": " + test_name + " (" + caseP.info() + ")";
2349 const DlColor bg = caseP.bg();
2350 RenderJobInfo base_info = {
2351 .bg = bg,
2352 };
2353
2354 // sk_result is a direct rendering via SkCanvas to SkSurface
2355 // DisplayList mechanisms are not involved in this operation
2356 SkJobRenderer sk_job(caseP.sk_setup(), //
2357 testP.sk_renderer(), //
2358 caseP.sk_restore(), //
2359 env.sk_image());
2360 auto sk_result = env.getResult(base_info, sk_job);
2361
2362 DlJobRenderer dl_job(caseP.dl_setup(), //
2363 testP.dl_renderer(), //
2364 caseP.dl_restore(), //
2365 env.dl_image());
2366 auto dl_result = env.getResult(base_info, dl_job);
2367
2368 if (sk_job.setup_matrix() != dl_job.setup_matrix()) {
2369 EXPECT_EQ(sk_job.setup_matrix(), dl_job.setup_matrix());
2370 }
2371 EXPECT_EQ(sk_job.setup_clip_bounds(), dl_job.setup_clip_bounds());
2372 ASSERT_EQ(sk_result->width(), kTestWidth) << info;
2373 ASSERT_EQ(sk_result->height(), kTestHeight) << info;
2374 ASSERT_EQ(dl_result->width(), kTestWidth) << info;
2375 ASSERT_EQ(dl_result->height(), kTestHeight) << info;
2376
2377 const BoundsTolerance tolerance =
2378 testP.adjust(tolerance_in, dl_job.setup_paint(), dl_job.setup_matrix());
2379 const sk_sp<SkPicture> sk_picture = sk_job.MakePicture(base_info);
2380 const sk_sp<DisplayList> display_list = dl_job.MakeDisplayList(base_info);
2381
2382 DlRect sk_bounds = ToDlRect(sk_picture->cullRect());
2383 checkPixels(sk_result.get(), sk_bounds, info + " (Skia reference)", bg);
2384
2385 if (testP.should_match(env, caseP, dl_job.setup_paint(), dl_job)) {
2386 quickCompareToReference(env.ref_sk_result(), sk_result.get(), true,
2387 info + " (attribute should not have effect)");
2388 } else {
2389 quickCompareToReference(env.ref_sk_result(), sk_result.get(), false,
2390 info + " (attribute should affect rendering)");
2391 }
2392
2393 // If either the reference setup or the test setup contain attributes
2394 // that Impeller doesn't support, we skip the Impeller testing. This
2395 // is mostly stroked or patterned text which is vectored through drawPath
2396 // for Impeller.
2397 if (env.supports_impeller() &&
2398 testP.impeller_compatible(dl_job.setup_paint()) &&
2399 testP.impeller_compatible(env.ref_dl_paint())) {
2400 DlJobRenderer imp_job(caseP.dl_setup(), //
2401 testP.imp_renderer(), //
2402 caseP.dl_restore(), //
2403 env.impeller_image());
2404 auto imp_result = env.getImpellerResult(base_info, imp_job);
2405 std::string imp_info = info + " (Impeller)";
2406 bool success = checkPixels(imp_result.get(), imp_result->render_bounds(),
2407 imp_info, bg);
2408 if (testP.should_match(env, caseP, imp_job.setup_paint(), imp_job)) {
2409 success = success && //
2411 env.ref_impeller_result(), imp_result.get(), true,
2412 imp_info + " (attribute should not have effect)");
2413 } else {
2414 success = success && //
2416 env.ref_impeller_result(), imp_result.get(), false,
2417 imp_info + " (attribute should affect rendering)");
2418 }
2419 if (SaveImpellerFailureImages && !success) {
2420 FML_LOG(ERROR) << "Impeller issue encountered for: "
2421 << *imp_job.MakeDisplayList(base_info);
2422 save_to_png(imp_result.get(), info + " (Impeller Result)",
2423 "output saved in");
2424 save_to_png(env.ref_impeller_result(), info + " (Impeller Reference)",
2425 "compare to reference without attributes");
2426 save_to_png(sk_result.get(), info + " (Skia Result)",
2427 "and to Skia reference with attributes");
2428 save_to_png(env.ref_sk_result(), info + " (Skia Reference)",
2429 "and to Skia reference without attributes");
2430 }
2431 }
2432
2433 quickCompareToReference(sk_result.get(), dl_result.get(), true,
2434 info + " (DlCanvas output matches SkCanvas)");
2435
2436 {
2437 DlRect dl_bounds = display_list->GetBounds();
2438 DlRect sk_padded_bounds = DlRect::RoundOut(sk_bounds);
2439 if (!sk_padded_bounds.Contains(dl_bounds)) {
2440 FML_LOG(ERROR) << "For " << info;
2441 FML_LOG(ERROR) << "sk ref: " << sk_bounds;
2442 FML_LOG(ERROR) << "dl: " << dl_bounds;
2443 if (!dl_bounds.Contains(sk_bounds)) {
2444 FML_LOG(ERROR) << "DisplayList bounds are too small!";
2445 }
2446 if (!dl_bounds.IsEmpty() &&
2447 !sk_padded_bounds.Contains(DlRect::RoundOut(dl_bounds))) {
2448 FML_LOG(ERROR) << "###### DisplayList bounds larger than reference!";
2449 }
2450 }
2451
2452 // This EXPECT sometimes triggers, but when it triggers and I examine
2453 // the ref_bounds, they are always unnecessarily large and since the
2454 // pixel OOB tests in the compare method do not trigger, we will trust
2455 // the DL bounds.
2456 // EXPECT_TRUE(dl_bounds.contains(ref_bounds)) << info;
2457
2458 // When we are drawing a DisplayList, the display_list built above
2459 // will contain just a single drawDisplayList call plus the case
2460 // attribute. The sk_picture will, however, contain a list of all
2461 // of the embedded calls in the display list and so the op counts
2462 // will not be equal between the two.
2463 if (!testP.is_draw_display_list()) {
2464 EXPECT_EQ(static_cast<int>(display_list->op_count()),
2465 sk_picture->approximateOpCount())
2466 << info;
2467 EXPECT_EQ(static_cast<int>(display_list->op_count()),
2468 sk_picture->approximateOpCount())
2469 << info;
2470 }
2471
2472 DisplayListJobRenderer dl_builder_job(display_list);
2473 auto dl_builder_result = env.getResult(base_info, dl_builder_job);
2474 if (caseP.fuzzy_compare_components()) {
2476 dl_builder_result.get(), dl_result.get(),
2477 info + " (DlCanvas DL output close to Builder Dl output)",
2478 &dl_bounds, &tolerance, bg, true);
2479 } else {
2481 dl_builder_result.get(), dl_result.get(), true,
2482 info + " (DlCanvas DL output matches Builder Dl output)");
2483 }
2484
2485 compareToReference(dl_result.get(), sk_result.get(),
2486 info + " (DisplayList built directly -> surface)",
2487 &dl_bounds, &tolerance, bg,
2488 caseP.fuzzy_compare_components());
2489
2490 if (display_list->can_apply_group_opacity()) {
2491 checkGroupOpacity(env, display_list, dl_result.get(),
2492 info + " with Group Opacity", bg);
2493 }
2494 }
2495
2496 {
2497 // This sequence uses an SkPicture generated previously from the SkCanvas
2498 // calls and a DisplayList generated previously from the DlCanvas calls
2499 // and renders both back under a transform (scale(2x)) to see if their
2500 // rendering is affected differently by a change of matrix between
2501 // recording time and rendering time.
2502 const int test_width_2 = kTestWidth * 2;
2503 const int test_height_2 = kTestHeight * 2;
2504 const DlScalar test_scale = 2.0;
2505
2506 SkPictureJobRenderer sk_job_x2(sk_picture);
2507 RenderJobInfo info_2x = {
2508 .width = test_width_2,
2509 .height = test_height_2,
2510 .bg = bg,
2511 .scale = test_scale,
2512 };
2513 auto ref_x2_result = env.getResult(info_2x, sk_job_x2);
2514 ASSERT_EQ(ref_x2_result->width(), test_width_2) << info;
2515 ASSERT_EQ(ref_x2_result->height(), test_height_2) << info;
2516
2517 DisplayListJobRenderer dl_job_x2(display_list);
2518 auto test_x2_result = env.getResult(info_2x, dl_job_x2);
2519 compareToReference(test_x2_result.get(), ref_x2_result.get(),
2520 info + " (Both rendered scaled 2x)", nullptr, nullptr,
2521 bg, caseP.fuzzy_compare_components(), //
2522 test_width_2, test_height_2, false);
2523 }
2524 }
2525
2526 static bool fuzzyCompare(uint32_t pixel_a, uint32_t pixel_b, int fudge) {
2527 for (int i = 0; i < 32; i += 8) {
2528 int comp_a = (pixel_a >> i) & 0xff;
2529 int comp_b = (pixel_b >> i) & 0xff;
2530 if (std::abs(comp_a - comp_b) > fudge) {
2531 return false;
2532 }
2533 }
2534 return true;
2535 }
2536
2538 if (env.format() == PixelFormat::k565PixelFormat) {
2539 return 9;
2540 }
2541 if (env.provider()->backend_type() == BackendType::kOpenGlBackend) {
2542 // OpenGL gets a little fuzzy at times. Still, "within 5" (aka +/-4)
2543 // for byte samples is not bad, though the other backends give +/-1
2544 return 5;
2545 }
2546 return 2;
2547 }
2548 static void checkGroupOpacity(const RenderEnvironment& env,
2549 const sk_sp<DisplayList>& display_list,
2550 const RenderResult* ref_result,
2551 const std::string& info,
2552 DlColor bg) {
2553 DlScalar opacity = 128.0 / 255.0;
2554
2555 DisplayListJobRenderer opacity_job(display_list);
2556 RenderJobInfo opacity_info = {
2557 .bg = bg,
2558 .opacity = opacity,
2559 };
2560 auto group_opacity_result = env.getResult(opacity_info, opacity_job);
2561
2562 ASSERT_EQ(group_opacity_result->width(), kTestWidth) << info;
2563 ASSERT_EQ(group_opacity_result->height(), kTestHeight) << info;
2564
2565 ASSERT_EQ(ref_result->width(), kTestWidth) << info;
2566 ASSERT_EQ(ref_result->height(), kTestHeight) << info;
2567
2568 int pixels_touched = 0;
2569 int pixels_different = 0;
2570 int max_diff = 0;
2571 // We need to allow some slight differences per component due to the
2572 // fact that rearranging discrete calculations can compound round off
2573 // errors. Off-by-2 is enough for 8 bit components, but for the 565
2574 // tests we allow at least 9 which is the maximum distance between
2575 // samples when converted to 8 bits. (You might think it would be a
2576 // max step of 8 converting 5 bits to 8 bits, but it is really
2577 // converting 31 steps to 255 steps with an average step size of
2578 // 8.23 - 24 of the steps are by 8, but 7 of them are by 9.)
2579 int fudge = groupOpacityFudgeFactor(env);
2580 for (int y = 0; y < kTestHeight; y++) {
2581 const uint32_t* ref_row = ref_result->addr32(0, y);
2582 const uint32_t* test_row = group_opacity_result->addr32(0, y);
2583 for (int x = 0; x < kTestWidth; x++) {
2584 uint32_t ref_pixel = ref_row[x];
2585 uint32_t test_pixel = test_row[x];
2586 if (ref_pixel != bg.argb() || test_pixel != bg.argb()) {
2587 pixels_touched++;
2588 for (int i = 0; i < 32; i += 8) {
2589 int ref_comp = (ref_pixel >> i) & 0xff;
2590 int bg_comp = (bg.argb() >> i) & 0xff;
2591 DlScalar faded_comp = bg_comp + (ref_comp - bg_comp) * opacity;
2592 int test_comp = (test_pixel >> i) & 0xff;
2593 if (std::abs(faded_comp - test_comp) > fudge) {
2594 int diff = std::abs(faded_comp - test_comp);
2595 if (max_diff < diff) {
2596 max_diff = diff;
2597 }
2598 pixels_different++;
2599 break;
2600 }
2601 }
2602 }
2603 }
2604 }
2605 ASSERT_GT(pixels_touched, 20) << info;
2606 if (pixels_different > 1) {
2607 FML_LOG(ERROR) << "max diff == " << max_diff << " for " << info;
2608 }
2609 ASSERT_LE(pixels_different, 1) << info;
2610 }
2611
2612 static bool checkPixels(const RenderResult* ref_result,
2613 const DlRect ref_bounds,
2614 const std::string& info,
2615 const DlColor bg = DlColor::kTransparent()) {
2616 uint32_t untouched = PremultipliedArgb(bg);
2617 int pixels_touched = 0;
2618 int pixels_oob = 0;
2619 DlIRect i_bounds = DlIRect::RoundOut(ref_bounds);
2620 EXPECT_EQ(ref_result->width(), kTestWidth) << info;
2621 EXPECT_EQ(ref_result->height(), kTestWidth) << info;
2622 for (int y = 0; y < kTestHeight; y++) {
2623 const uint32_t* ref_row = ref_result->addr32(0, y);
2624 for (int x = 0; x < kTestWidth; x++) {
2625 if (ref_row[x] != untouched) {
2626 pixels_touched++;
2627 if (!i_bounds.Contains({x, y})) {
2628 pixels_oob++;
2629 }
2630 }
2631 }
2632 }
2633 EXPECT_EQ(pixels_oob, 0) << info;
2634 EXPECT_GT(pixels_touched, 0) << info;
2635 return pixels_oob == 0 && pixels_touched > 0;
2636 }
2637
2638 static int countModifiedTransparentPixels(const RenderResult* ref_result,
2639 const RenderResult* test_result) {
2640 int count = 0;
2641 for (int y = 0; y < kTestHeight; y++) {
2642 const uint32_t* ref_row = ref_result->addr32(0, y);
2643 const uint32_t* test_row = test_result->addr32(0, y);
2644 for (int x = 0; x < kTestWidth; x++) {
2645 if (ref_row[x] != test_row[x]) {
2646 if (ref_row[x] == 0) {
2647 count++;
2648 }
2649 }
2650 }
2651 }
2652 return count;
2653 }
2654
2656 const std::string& info) {
2658 info + " reference rendering");
2659 }
2660
2661 static bool quickCompareToReference(const RenderResult* ref_result,
2662 const RenderResult* test_result,
2663 bool should_match,
2664 const std::string& info) {
2665 int w = test_result->width();
2666 int h = test_result->height();
2667 EXPECT_EQ(w, ref_result->width()) << info;
2668 EXPECT_EQ(h, ref_result->height()) << info;
2669 int pixels_different = 0;
2670 for (int y = 0; y < h; y++) {
2671 const uint32_t* ref_row = ref_result->addr32(0, y);
2672 const uint32_t* test_row = test_result->addr32(0, y);
2673 for (int x = 0; x < w; x++) {
2674 if (ref_row[x] != test_row[x]) {
2675 if (should_match && pixels_different < 5) {
2676 FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
2677 }
2678 pixels_different++;
2679 }
2680 }
2681 }
2682 if (should_match) {
2683 EXPECT_EQ(pixels_different, 0) << info;
2684 return pixels_different == 0;
2685 } else {
2686 EXPECT_NE(pixels_different, 0) << info;
2687 return pixels_different != 0;
2688 }
2689 }
2690
2691 static void compareToReference(const RenderResult* test_result,
2692 const RenderResult* ref_result,
2693 const std::string& info,
2694 const DlRect* bounds,
2695 const BoundsTolerance* tolerance,
2696 const DlColor bg,
2697 bool fuzzyCompares = false,
2698 int width = kTestWidth,
2699 int height = kTestHeight,
2700 bool printMismatches = false) {
2701 uint32_t untouched = PremultipliedArgb(bg);
2702 ASSERT_EQ(test_result->width(), width) << info;
2703 ASSERT_EQ(test_result->height(), height) << info;
2704 DlIRect i_bounds =
2705 bounds ? DlIRect::RoundOut(*bounds) : DlIRect::MakeWH(width, height);
2706
2707 int pixels_different = 0;
2708 int pixels_oob = 0;
2709 int min_x = width;
2710 int min_y = height;
2711 int max_x = 0;
2712 int max_y = 0;
2713 for (int y = 0; y < height; y++) {
2714 const uint32_t* ref_row = ref_result->addr32(0, y);
2715 const uint32_t* test_row = test_result->addr32(0, y);
2716 for (int x = 0; x < width; x++) {
2717 if (bounds && test_row[x] != untouched) {
2718 if (min_x > x) {
2719 min_x = x;
2720 }
2721 if (min_y > y) {
2722 min_y = y;
2723 }
2724 if (max_x <= x) {
2725 max_x = x + 1;
2726 }
2727 if (max_y <= y) {
2728 max_y = y + 1;
2729 }
2730 if (!i_bounds.Contains({x, y})) {
2731 pixels_oob++;
2732 }
2733 }
2734 bool match = fuzzyCompares ? fuzzyCompare(test_row[x], ref_row[x], 1)
2735 : test_row[x] == ref_row[x];
2736 if (!match) {
2737 if (printMismatches && pixels_different < 5) {
2738 FML_LOG(ERROR) << "pix[" << x << ", " << y
2739 << "] mismatch: " << std::hex << test_row[x]
2740 << "(test) != (ref)" << ref_row[x] << std::dec;
2741 }
2742 pixels_different++;
2743 }
2744 }
2745 }
2746 if (pixels_oob > 0) {
2747 FML_LOG(ERROR) << "pix bounds["
2748 << DlIRect::MakeLTRB(min_x, min_y, max_x, max_y) //
2749 << "]";
2750 FML_LOG(ERROR) << "dl_bounds[" << bounds << "]";
2751 } else if (bounds) {
2752 showBoundsOverflow(info, i_bounds, tolerance, min_x, min_y, max_x, max_y);
2753 }
2754 ASSERT_EQ(pixels_oob, 0) << info;
2755 ASSERT_EQ(pixels_different, 0) << info;
2756 }
2757
2758 static void showBoundsOverflow(const std::string& info,
2759 DlIRect& bounds,
2760 const BoundsTolerance* tolerance,
2761 int pixLeft,
2762 int pixTop,
2763 int pixRight,
2764 int pixBottom) {
2765 int pad_left = std::max(0, pixLeft - bounds.GetLeft());
2766 int pad_top = std::max(0, pixTop - bounds.GetTop());
2767 int pad_right = std::max(0, bounds.GetRight() - pixRight);
2768 int pad_bottom = std::max(0, bounds.GetBottom() - pixBottom);
2769 DlIRect pix_bounds =
2770 DlIRect::MakeLTRB(pixLeft, pixTop, pixRight, pixBottom);
2771 DlISize pix_size = pix_bounds.GetSize();
2772 int pix_width = pix_size.width;
2773 int pix_height = pix_size.height;
2774 int worst_pad_x = std::max(pad_left, pad_right);
2775 int worst_pad_y = std::max(pad_top, pad_bottom);
2776 if (tolerance->overflows(pix_bounds, worst_pad_x, worst_pad_y)) {
2777 FML_LOG(ERROR) << "Computed bounds for " << info;
2778 FML_LOG(ERROR) << "pix bounds[" //
2779 << pixLeft << ", " << pixTop << " => " //
2780 << pixRight << ", " << pixBottom //
2781 << "]";
2782 FML_LOG(ERROR) << "dl_bounds[" << bounds << "]";
2783 FML_LOG(ERROR) << "Bounds overly conservative by up to " //
2784 << worst_pad_x << ", " << worst_pad_y //
2785 << " (" << (worst_pad_x * 100.0 / pix_width) //
2786 << "%, " << (worst_pad_y * 100.0 / pix_height) << "%)";
2787 int pix_area = pix_size.Area();
2788 int dl_area = bounds.Area();
2789 FML_LOG(ERROR) << "Total overflow area: " << (dl_area - pix_area) //
2790 << " (+" << (dl_area * 100.0 / pix_area - 100.0) //
2791 << "% larger)";
2792 FML_LOG(ERROR);
2793 }
2794 }
2795
2796 static sk_sp<SkTextBlob> MakeTextBlob(const std::string& string,
2797 DlScalar font_height) {
2798 SkFont font = CreateTestFontOfSize(font_height);
2799 sk_sp<SkTypeface> face = font.refTypeface();
2800 FML_CHECK(face);
2801 FML_CHECK(face->countGlyphs() > 0) << "No glyphs in font";
2802 return SkTextBlob::MakeFromText(string.c_str(), string.size(), font,
2803 SkTextEncoding::kUTF8);
2804 }
2805};
2806
2807std::vector<BackendType> CanvasCompareTester::TestBackends;
2810std::vector<std::string> CanvasCompareTester::ImpellerFailureImages;
2813
2815 BoundsTolerance().addAbsolutePadding(1, 1);
2816
2817// Eventually this bare bones testing::Test fixture will subsume the
2818// CanvasCompareTester and the TestParameters could then become just
2819// configuration calls made upon the fixture.
2820template <typename BaseT>
2821class DisplayListRenderingTestBase : public BaseT,
2822 protected DisplayListOpFlags {
2823 public:
2825
2826 static bool StartsWith(std::string str, std::string prefix) {
2827 if (prefix.length() > str.length()) {
2828 return false;
2829 }
2830 for (size_t i = 0; i < prefix.length(); i++) {
2831 if (str[i] != prefix[i]) {
2832 return false;
2833 }
2834 }
2835 return true;
2836 }
2837
2838 static void SetUpTestSuite() {
2839 bool do_software = true;
2840 bool do_opengl = false;
2841 bool do_metal = false;
2842 std::vector<std::string> args = ::testing::internal::GetArgvs();
2843 for (auto p_arg = std::next(args.begin()); p_arg != args.end(); p_arg++) {
2844 std::string arg = *p_arg;
2845 bool enable = true;
2846 if (arg == "--save-impeller-failures") {
2848 continue;
2849 }
2850 if (StartsWith(arg, "--no")) {
2851 enable = false;
2852 arg = "-" + arg.substr(4);
2853 } else if (StartsWith(arg, "--disable")) {
2854 enable = false;
2855 arg = "--en" + arg.substr(5);
2856 }
2857 if (arg == "--enable-software") {
2858 do_software = enable;
2859 } else if (arg == "--enable-opengl" || arg == "--enable-gl") {
2860 do_opengl = enable;
2861 } else if (arg == "--enable-metal") {
2862 do_metal = enable;
2863 } else if (arg == "--enable-impeller") {
2865 }
2866 }
2867 // Multiple test suites use this test base. Make sure that they don't
2868 // double-register the supported providers.
2870 if (do_software) {
2871 CanvasCompareTester::AddProvider(BackendType::kSoftwareBackend);
2872 }
2873 if (do_opengl) {
2874 CanvasCompareTester::AddProvider(BackendType::kOpenGlBackend);
2875 }
2876 if (do_metal) {
2877 CanvasCompareTester::AddProvider(BackendType::kMetalBackend);
2878 }
2879 std::string providers = "";
2880 for (auto& back_end : CanvasCompareTester::TestBackends) {
2881 providers += " " + DlSurfaceProvider::BackendName(back_end);
2882 }
2883 std::string libraries = " Skia";
2886 libraries += " Impeller";
2887 }
2888 FML_LOG(INFO) << "Running tests on [" << providers //
2889 << " ], and [" << libraries << " ]";
2890 }
2891
2892 static void TearDownTestSuite() {
2894 FML_LOG(INFO);
2896 << " images saved in "
2898 for (const auto& filename : CanvasCompareTester::ImpellerFailureImages) {
2899 FML_LOG(INFO) << " " << filename;
2900 }
2901 FML_LOG(INFO);
2902 }
2903 }
2904
2905 private:
2907};
2909
2913 [=](const SkRenderContext& ctx) { //
2914 ctx.canvas->drawPaint(ctx.paint);
2915 },
2916 [=](const DlRenderContext& ctx) { //
2917 ctx.canvas->DrawPaint(ctx.paint);
2918 },
2919 kDrawPaintFlags));
2920}
2921
2922TEST_F(DisplayListRendering, DrawOpaqueColor) {
2923 // We use a non-opaque color to avoid obliterating any backdrop filter output
2926 [=](const SkRenderContext& ctx) {
2927 // DrawColor is not tested against attributes because it is supposed
2928 // to ignore them. So, if the paint has an alpha, it is because we
2929 // are doing a saveLayer+backdrop test and we need to not flood over
2930 // the backdrop output with a solid color. So, we perform an alpha
2931 // drawColor for that case only.
2932 SkColor color = SkColorSetA(SK_ColorMAGENTA, ctx.paint.getAlpha());
2933 ctx.canvas->drawColor(color);
2934 },
2935 [=](const DlRenderContext& ctx) {
2936 // DrawColor is not tested against attributes because it is supposed
2937 // to ignore them. So, if the paint has an alpha, it is because we
2938 // are doing a saveLayer+backdrop test and we need to not flood over
2939 // the backdrop output with a solid color. So, we transfer the alpha
2940 // from the paint for that case only.
2941 ctx.canvas->DrawColor(
2942 DlColor::kMagenta().withAlpha(ctx.paint.getAlpha()));
2943 },
2944 kDrawColorFlags));
2945}
2946
2948 // We use a non-opaque color to avoid obliterating any backdrop filter output
2951 [=](const SkRenderContext& ctx) {
2952 ctx.canvas->drawColor(0x7FFF00FF);
2953 },
2954 [=](const DlRenderContext& ctx) {
2955 ctx.canvas->DrawColor(DlColor(0x7FFF00FF));
2956 },
2957 kDrawColorFlags));
2958}
2959
2960TEST_F(DisplayListRendering, DrawDiagonalLines) {
2965 // Adding some edge to edge diagonals that run through the points about
2966 // 16 units in from the center of that edge.
2967 // Adding some edge center to edge center diagonals to better fill
2968 // out the RRect Clip so bounds checking sees less empty bounds space.
2973
2976 [=](const SkRenderContext& ctx) { //
2977 // Skia requires kStroke style on horizontal and vertical
2978 // lines to get the bounds correct.
2979 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
2980 SkPaint p = ctx.paint;
2981 p.setStyle(SkPaint::kStroke_Style);
2982 ctx.canvas->drawLine(ToSkPoint(p1), ToSkPoint(p2), p);
2983 ctx.canvas->drawLine(ToSkPoint(p3), ToSkPoint(p4), p);
2984 ctx.canvas->drawLine(ToSkPoint(p5), ToSkPoint(p6), p);
2985 ctx.canvas->drawLine(ToSkPoint(p7), ToSkPoint(p8), p);
2986 },
2987 [=](const DlRenderContext& ctx) { //
2988 ctx.canvas->DrawLine(p1, p2, ctx.paint);
2989 ctx.canvas->DrawLine(p3, p4, ctx.paint);
2990 ctx.canvas->DrawLine(p5, p6, ctx.paint);
2991 ctx.canvas->DrawLine(p7, p8, ctx.paint);
2992 },
2993 kDrawLineFlags)
2994 .set_draw_line());
2995}
2996
2997TEST_F(DisplayListRendering, DrawHorizontalLines) {
3004
3007 [=](const SkRenderContext& ctx) { //
3008 // Skia requires kStroke style on horizontal and vertical
3009 // lines to get the bounds correct.
3010 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3011 SkPaint p = ctx.paint;
3012 p.setStyle(SkPaint::kStroke_Style);
3013 ctx.canvas->drawLine(ToSkPoint(p1), ToSkPoint(p2), p);
3014 ctx.canvas->drawLine(ToSkPoint(p3), ToSkPoint(p4), p);
3015 ctx.canvas->drawLine(ToSkPoint(p5), ToSkPoint(p6), p);
3016 },
3017 [=](const DlRenderContext& ctx) { //
3018 ctx.canvas->DrawLine(p1, p2, ctx.paint);
3019 ctx.canvas->DrawLine(p3, p4, ctx.paint);
3020 ctx.canvas->DrawLine(p5, p6, ctx.paint);
3021 },
3022 kDrawHVLineFlags)
3023 .set_draw_line()
3025}
3026
3027TEST_F(DisplayListRendering, DrawVerticalLines) {
3034
3037 [=](const SkRenderContext& ctx) { //
3038 // Skia requires kStroke style on horizontal and vertical
3039 // lines to get the bounds correct.
3040 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3041 SkPaint p = ctx.paint;
3042 p.setStyle(SkPaint::kStroke_Style);
3043 ctx.canvas->drawLine(ToSkPoint(p1), ToSkPoint(p2), p);
3044 ctx.canvas->drawLine(ToSkPoint(p3), ToSkPoint(p4), p);
3045 ctx.canvas->drawLine(ToSkPoint(p5), ToSkPoint(p6), p);
3046 },
3047 [=](const DlRenderContext& ctx) { //
3048 ctx.canvas->DrawLine(p1, p2, ctx.paint);
3049 ctx.canvas->DrawLine(p3, p4, ctx.paint);
3050 ctx.canvas->DrawLine(p5, p6, ctx.paint);
3051 },
3052 kDrawHVLineFlags)
3053 .set_draw_line()
3055}
3056
3057TEST_F(DisplayListRendering, DrawDiagonalDashedLines) {
3062 // Adding some edge to edge diagonals that run through the points about
3063 // 16 units in from the center of that edge.
3064 // Adding some edge center to edge center diagonals to better fill
3065 // out the RRect Clip so bounds checking sees less empty bounds space.
3070
3071 // Full diagonals are 100x100 which are 140 in length
3072 // Dashing them with 25 on, 5 off means that the last
3073 // dash goes from 120 to 145 which means both ends of the
3074 // diagonals will be in an "on" dash for maximum bounds
3075
3076 // Edge to edge diagonals are 50x50 which are 70 in length
3077 // Dashing them with 25 on, 5 off means that the last
3078 // dash goes from 60 to 85 which means both ends of the
3079 // edge diagonals will be in a dash segment
3080
3083 [=](const SkRenderContext& ctx) { //
3084 // Skia requires kStroke style on horizontal and vertical
3085 // lines to get the bounds correct.
3086 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3087 SkPaint p = ctx.paint;
3088 p.setStyle(SkPaint::kStroke_Style);
3089 DlScalar intervals[2] = {25.0f, 5.0f};
3090 p.setPathEffect(SkDashPathEffect::Make({intervals, 2}, 0.0f));
3091 ctx.canvas->drawLine(ToSkPoint(p1), ToSkPoint(p2), p);
3092 ctx.canvas->drawLine(ToSkPoint(p3), ToSkPoint(p4), p);
3093 ctx.canvas->drawLine(ToSkPoint(p5), ToSkPoint(p6), p);
3094 ctx.canvas->drawLine(ToSkPoint(p7), ToSkPoint(p8), p);
3095 },
3096 [=](const DlRenderContext& ctx) { //
3097 ctx.canvas->DrawDashedLine(p1, p2, 25.0f, 5.0f, ctx.paint);
3098 ctx.canvas->DrawDashedLine(p3, p4, 25.0f, 5.0f, ctx.paint);
3099 ctx.canvas->DrawDashedLine(p5, p6, 25.0f, 5.0f, ctx.paint);
3100 ctx.canvas->DrawDashedLine(p7, p8, 25.0f, 5.0f, ctx.paint);
3101 },
3102 kDrawLineFlags)
3103 .set_draw_line());
3104}
3105
3107 // Bounds are offset by 0.5 pixels to induce AA
3108 DlRect rect = kRenderBounds.Shift(0.5f, 0.5f);
3109
3112 [=](const SkRenderContext& ctx) { //
3113 ctx.canvas->drawRect(ToSkRect(rect), ctx.paint);
3114 },
3115 [=](const DlRenderContext& ctx) { //
3116 ctx.canvas->DrawRect(rect, ctx.paint);
3117 },
3118 kDrawRectFlags));
3119}
3120
3122 DlRect rect = kRenderBounds.Expand(0, -10);
3123
3126 [=](const SkRenderContext& ctx) { //
3127 ctx.canvas->drawOval(ToSkRect(rect), ctx.paint);
3128 },
3129 [=](const DlRenderContext& ctx) { //
3130 ctx.canvas->DrawOval(rect, ctx.paint);
3131 },
3132 kDrawOvalFlags));
3133}
3134
3136 DlPoint center = kRenderBounds.GetCenter();
3137
3140 [=](const SkRenderContext& ctx) { //
3141 ctx.canvas->drawCircle(ToSkPoint(center), kRenderRadius, ctx.paint);
3142 },
3143 [=](const DlRenderContext& ctx) { //
3144 ctx.canvas->DrawCircle(center, kRenderRadius, ctx.paint);
3145 },
3146 kDrawCircleFlags));
3147}
3148
3153
3156 [=](const SkRenderContext& ctx) { //
3157 ctx.canvas->drawRRect(ToSkRRect(rrect), ctx.paint);
3158 },
3159 [=](const DlRenderContext& ctx) { //
3160 ctx.canvas->DrawRoundRect(rrect, ctx.paint);
3161 },
3162 kDrawRRectFlags));
3163}
3164
3165TEST_F(DisplayListRendering, DrawDiffRoundRect) {
3169 DlRect inner_bounds = kRenderBounds.Expand(-30.0f, -30.0f);
3170 DlRoundRect inner = DlRoundRect::MakeRectXY(inner_bounds, //
3173
3176 [=](const SkRenderContext& ctx) { //
3177 ctx.canvas->drawDRRect(ToSkRRect(outer), ToSkRRect(inner),
3178 ctx.paint);
3179 },
3180 [=](const DlRenderContext& ctx) { //
3181 ctx.canvas->DrawDiffRoundRect(outer, inner, ctx.paint);
3182 },
3183 kDrawDRRectFlags));
3184}
3185
3187 DlPathBuilder path_builder;
3188
3189 // unclosed lines to show some caps
3190 path_builder.MoveTo(DlPoint(kRenderLeft + 15, kRenderTop + 15));
3191 path_builder.LineTo(DlPoint(kRenderRight - 15, kRenderBottom - 15));
3192 path_builder.MoveTo(DlPoint(kRenderLeft + 15, kRenderBottom - 15));
3193 path_builder.LineTo(DlPoint(kRenderRight - 15, kRenderTop + 15));
3194
3195 path_builder.AddRect(kRenderBounds);
3196
3197 // miter diamonds horizontally and vertically to show miters
3198 path_builder.MoveTo(kVerticalMiterDiamondPoints[0]);
3199 for (int i = 1; i < kVerticalMiterDiamondPointCount; i++) {
3200 path_builder.LineTo(kVerticalMiterDiamondPoints[i]);
3201 }
3202 path_builder.Close();
3203 path_builder.MoveTo(kHorizontalMiterDiamondPoints[0]);
3204 for (int i = 1; i < kHorizontalMiterDiamondPointCount; i++) {
3205 path_builder.LineTo(kHorizontalMiterDiamondPoints[i]);
3206 }
3207 path_builder.Close();
3208
3209 DlPath path = path_builder.TakePath();
3210
3213 [=](const SkRenderContext& ctx) { //
3214 ctx.canvas->drawPath(path.GetSkPath(), ctx.paint);
3215 },
3216 [=](const DlRenderContext& ctx) { //
3217 ctx.canvas->DrawPath(path, ctx.paint);
3218 },
3219 kDrawPathFlags)
3220 .set_draw_path());
3221}
3222
3226 [=](const SkRenderContext& ctx) { //
3227 ctx.canvas->drawArc(ToSkRect(kRenderBounds), 60, 330, false,
3228 ctx.paint);
3229 },
3230 [=](const DlRenderContext& ctx) { //
3231 ctx.canvas->DrawArc(kRenderBounds, 60, 330, false, ctx.paint);
3232 },
3233 kDrawArcNoCenterFlags));
3234}
3235
3237 // Center arcs that inscribe nearly a whole circle except for a small
3238 // arc extent gap have 2 angles that may appear or disappear at the
3239 // various miter limits tested (0, 4, and 10).
3240 // The center angle here is 12 degrees which shows a miter
3241 // at limit=10, but not 0 or 4.
3242 // The arcs at the corners where it turns in towards the
3243 // center show miters at 4 and 10, but not 0.
3244 // Limit == 0, neither corner does a miter
3245 // Limit == 4, only the edge "turn-in" corners miter
3246 // Limit == 10, edge and center corners all miter
3249 [=](const SkRenderContext& ctx) { //
3250 ctx.canvas->drawArc(ToSkRect(kRenderBounds), 60, 360 - 12, true,
3251 ctx.paint);
3252 },
3253 [=](const DlRenderContext& ctx) { //
3254 ctx.canvas->DrawArc(kRenderBounds, 60, 360 - 12, true, ctx.paint);
3255 },
3256 kDrawArcWithCenterFlags)
3257 .set_draw_arc_center());
3258}
3259
3260TEST_F(DisplayListRendering, DrawPointsAsPoints) {
3261 // The +/- 16 points are designed to fall just inside the clips
3262 // that are tested against so we avoid lots of undrawn pixels
3263 // in the accumulated bounds.
3264 const DlScalar x0 = kRenderLeft;
3265 const DlScalar x1 = kRenderLeft + 16;
3266 const DlScalar x2 = (kRenderLeft + kRenderCenterX) * 0.5;
3267 const DlScalar x3 = kRenderCenterX + 0.1;
3268 const DlScalar x4 = (kRenderRight + kRenderCenterX) * 0.5;
3269 const DlScalar x5 = kRenderRight - 16;
3270 const DlScalar x6 = kRenderRight - 1;
3271
3272 const DlScalar y0 = kRenderTop;
3273 const DlScalar y1 = kRenderTop + 16;
3274 const DlScalar y2 = (kRenderTop + kRenderCenterY) * 0.5;
3275 const DlScalar y3 = kRenderCenterY + 0.1;
3276 const DlScalar y4 = (kRenderBottom + kRenderCenterY) * 0.5;
3277 const DlScalar y5 = kRenderBottom - 16;
3278 const DlScalar y6 = kRenderBottom - 1;
3279
3280 // clang-format off
3281 const DlPoint points[] = {
3282 {x0, y0}, {x1, y0}, {x2, y0}, {x3, y0}, {x4, y0}, {x5, y0}, {x6, y0},
3283 {x0, y1}, {x1, y1}, {x2, y1}, {x3, y1}, {x4, y1}, {x5, y1}, {x6, y1},
3284 {x0, y2}, {x1, y2}, {x2, y2}, {x3, y2}, {x4, y2}, {x5, y2}, {x6, y2},
3285 {x0, y3}, {x1, y3}, {x2, y3}, {x3, y3}, {x4, y3}, {x5, y3}, {x6, y3},
3286 {x0, y4}, {x1, y4}, {x2, y4}, {x3, y4}, {x4, y4}, {x5, y4}, {x6, y4},
3287 {x0, y5}, {x1, y5}, {x2, y5}, {x3, y5}, {x4, y5}, {x5, y5}, {x6, y5},
3288 {x0, y6}, {x1, y6}, {x2, y6}, {x3, y6}, {x4, y6}, {x5, y6}, {x6, y6},
3289 };
3290 // clang-format on
3291 const int count = sizeof(points) / sizeof(points[0]);
3292
3295 [=](const SkRenderContext& ctx) {
3296 // Skia requires kStroke style on horizontal and vertical
3297 // lines to get the bounds correct.
3298 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3299 SkPaint p = ctx.paint;
3300 p.setStyle(SkPaint::kStroke_Style);
3301 auto mode = SkCanvas::kPoints_PointMode;
3302 ctx.canvas->drawPoints(mode, {ToSkPoints(points), count}, p);
3303 },
3304 [=](const DlRenderContext& ctx) {
3306 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
3307 },
3308 kDrawPointsAsPointsFlags)
3309 .set_draw_line()
3311}
3312
3313TEST_F(DisplayListRendering, DrawPointsAsLines) {
3314 const DlScalar x0 = kRenderLeft + 1;
3315 const DlScalar x1 = kRenderLeft + 16;
3316 const DlScalar x2 = kRenderRight - 16;
3317 const DlScalar x3 = kRenderRight - 1;
3318
3319 const DlScalar y0 = kRenderTop;
3320 const DlScalar y1 = kRenderTop + 16;
3321 const DlScalar y2 = kRenderBottom - 16;
3322 const DlScalar y3 = kRenderBottom - 1;
3323
3324 // clang-format off
3325 const DlPoint points[] = {
3326 // Outer box
3327 {x0, y0}, {x3, y0},
3328 {x3, y0}, {x3, y3},
3329 {x3, y3}, {x0, y3},
3330 {x0, y3}, {x0, y0},
3331
3332 // Diagonals
3333 {x0, y0}, {x3, y3}, {x3, y0}, {x0, y3},
3334
3335 // Inner box
3336 {x1, y1}, {x2, y1},
3337 {x2, y1}, {x2, y2},
3338 {x2, y2}, {x1, y2},
3339 {x1, y2}, {x1, y1},
3340 };
3341 // clang-format on
3342
3343 const int count = sizeof(points) / sizeof(points[0]);
3344 ASSERT_TRUE((count & 1) == 0);
3347 [=](const SkRenderContext& ctx) {
3348 // Skia requires kStroke style on horizontal and vertical
3349 // lines to get the bounds correct.
3350 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3351 SkPaint p = ctx.paint;
3352 p.setStyle(SkPaint::kStroke_Style);
3353 auto mode = SkCanvas::kLines_PointMode;
3354 ctx.canvas->drawPoints(mode, {ToSkPoints(points), count}, p);
3355 },
3356 [=](const DlRenderContext& ctx) {
3358 ctx.canvas->DrawPoints(mode, count, points, ctx.paint);
3359 },
3360 kDrawPointsAsLinesFlags));
3361}
3362
3363TEST_F(DisplayListRendering, DrawPointsAsPolygon) {
3364 const DlPoint points1[] = {
3365 // RenderBounds box with a diamond
3371
3377 };
3378 const int count1 = sizeof(points1) / sizeof(points1[0]);
3379
3382 [=](const SkRenderContext& ctx) {
3383 // Skia requires kStroke style on horizontal and vertical
3384 // lines to get the bounds correct.
3385 // See https://bugs.chromium.org/p/skia/issues/detail?id=12446
3386 SkPaint p = ctx.paint;
3387 p.setStyle(SkPaint::kStroke_Style);
3388 auto mode = SkCanvas::kPolygon_PointMode;
3389 ctx.canvas->drawPoints(mode, {ToSkPoints(points1), count1}, p);
3390 },
3391 [=](const DlRenderContext& ctx) {
3393 ctx.canvas->DrawPoints(mode, count1, points1, ctx.paint);
3394 },
3395 kDrawPointsAsPolygonFlags));
3396}
3397
3398TEST_F(DisplayListRendering, DrawVerticesWithColors) {
3399 // Cover as many sides of the box with only 6 vertices:
3400 // +----------+
3401 // |xxxxxxxxxx|
3402 // | xxxxxx|
3403 // | xxx|
3404 // |xxx |
3405 // |xxxxxx |
3406 // |xxxxxxxxxx|
3407 // +----------|
3408 const DlPoint pts[6] = {
3409 // Upper-Right corner, full top, half right coverage
3413 // Lower-Left corner, full bottom, half left coverage
3417 };
3418 const DlColor dl_colors[6] = {
3421 };
3422 const SkColor sk_colors[6] = {
3423 SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN,
3424 SK_ColorCYAN, SK_ColorYELLOW, SK_ColorMAGENTA,
3425 };
3426 const std::shared_ptr<DlVertices> dl_vertices =
3427 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, nullptr, dl_colors);
3428 const auto sk_vertices =
3429 SkVertices::MakeCopy(SkVertices::VertexMode::kTriangles_VertexMode, 6,
3430 ToSkPoints(pts), nullptr, sk_colors);
3431
3434 [=](const SkRenderContext& ctx) {
3435 ctx.canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver,
3436 ctx.paint);
3437 },
3438 [=](const DlRenderContext& ctx) {
3439 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
3440 ctx.paint);
3441 },
3442 kDrawVerticesFlags));
3443}
3444
3445TEST_F(DisplayListRendering, DrawVerticesWithImage) {
3446 // Cover as many sides of the box with only 6 vertices:
3447 // +----------+
3448 // |xxxxxxxxxx|
3449 // | xxxxxx|
3450 // | xxx|
3451 // |xxx |
3452 // |xxxxxx |
3453 // |xxxxxxxxxx|
3454 // +----------|
3455 const DlPoint pts[6] = {
3456 // Upper-Right corner, full top, half right coverage
3460 // Lower-Left corner, full bottom, half left coverage
3464 };
3465 const DlPoint tex[6] = {
3466 DlPoint(kRenderWidth / 2.0, 0),
3470 DlPoint(0, 0),
3472 };
3473 const std::shared_ptr<DlVertices> dl_vertices =
3474 DlVertices::Make(DlVertexMode::kTriangles, 6, pts, tex, nullptr);
3475 const auto sk_vertices =
3476 SkVertices::MakeCopy(SkVertices::VertexMode::kTriangles_VertexMode, 6,
3477 ToSkPoints(pts), ToSkPoints(tex), nullptr);
3478
3481 [=](const SkRenderContext& ctx) { //
3482 SkPaint v_paint = ctx.paint;
3483 if (v_paint.getShader() == nullptr) {
3484 v_paint.setShader(MakeColorSource(ctx.image));
3485 }
3486 ctx.canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver,
3487 v_paint);
3488 },
3489 [=](const DlRenderContext& ctx) { //
3490 DlPaint v_paint = ctx.paint;
3491 if (v_paint.getColorSource() == nullptr) {
3492 v_paint.setColorSource(MakeColorSource(ctx.image));
3493 }
3494 ctx.canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver,
3495 v_paint);
3496 },
3497 kDrawVerticesFlags));
3498}
3499
3500TEST_F(DisplayListRendering, DrawImageNearest) {
3503 [=](const SkRenderContext& ctx) {
3504 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3506 &ctx.paint);
3507 },
3508 [=](const DlRenderContext& ctx) {
3509 ctx.canvas->DrawImage(ctx.image, DlPoint(kRenderLeft, kRenderTop),
3511 &ctx.paint);
3512 },
3513 kDrawImageWithPaintFlags));
3514}
3515
3516TEST_F(DisplayListRendering, DrawImageNearestNoPaint) {
3519 [=](const SkRenderContext& ctx) {
3520 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3522 },
3523 [=](const DlRenderContext& ctx) {
3524 ctx.canvas->DrawImage(ctx.image, DlPoint(kRenderLeft, kRenderTop),
3526 },
3527 kDrawImageFlags));
3528}
3529
3530TEST_F(DisplayListRendering, DrawImageLinear) {
3533 [=](const SkRenderContext& ctx) {
3534 ctx.canvas->drawImage(ctx.image, kRenderLeft, kRenderTop,
3535 SkImageSampling::kLinear, &ctx.paint);
3536 },
3537 [=](const DlRenderContext& ctx) {
3538 ctx.canvas->DrawImage(ctx.image, DlPoint(kRenderLeft, kRenderTop),
3539 DlImageSampling::kLinear, &ctx.paint);
3540 },
3541 kDrawImageWithPaintFlags));
3542}
3543
3544TEST_F(DisplayListRendering, DrawImageRectNearest) {
3546 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3549 [=](const SkRenderContext& ctx) {
3550 ctx.canvas->drawImageRect(ctx.image, ToSkRect(src), ToSkRect(dst),
3552 &ctx.paint,
3553 SkCanvas::kFast_SrcRectConstraint);
3554 },
3555 [=](const DlRenderContext& ctx) {
3556 ctx.canvas->DrawImageRect(ctx.image, src, dst,
3558 &ctx.paint, DlSrcRectConstraint::kFast);
3559 },
3560 kDrawImageRectWithPaintFlags));
3561}
3562
3563TEST_F(DisplayListRendering, DrawImageRectNearestNoPaint) {
3565 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3568 [=](const SkRenderContext& ctx) {
3569 ctx.canvas->drawImageRect(ctx.image, ToSkRect(src), ToSkRect(dst),
3571 nullptr,
3572 SkCanvas::kFast_SrcRectConstraint);
3573 },
3574 [=](const DlRenderContext& ctx) {
3575 ctx.canvas->DrawImageRect(ctx.image, src, dst,
3578 },
3579 kDrawImageRectFlags));
3580}
3581
3582TEST_F(DisplayListRendering, DrawImageRectLinear) {
3584 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3587 [=](const SkRenderContext& ctx) {
3588 ctx.canvas->drawImageRect(ctx.image, ToSkRect(src), ToSkRect(dst),
3589 SkImageSampling::kLinear, &ctx.paint,
3590 SkCanvas::kFast_SrcRectConstraint);
3591 },
3592 [=](const DlRenderContext& ctx) { //
3593 ctx.canvas->DrawImageRect(ctx.image, src, dst,
3595 &ctx.paint, DlSrcRectConstraint::kFast);
3596 },
3597 kDrawImageRectWithPaintFlags));
3598}
3599
3600TEST_F(DisplayListRendering, DrawImageNineNearest) {
3602 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3605 [=](const SkRenderContext& ctx) {
3606 ctx.canvas->drawImageNine(ctx.image.get(), ToSkIRect(src),
3607 ToSkRect(dst), SkFilterMode::kNearest,
3608 &ctx.paint);
3609 },
3610 [=](const DlRenderContext& ctx) {
3611 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3612 DlFilterMode::kNearest, &ctx.paint);
3613 },
3614 kDrawImageNineWithPaintFlags));
3615}
3616
3617TEST_F(DisplayListRendering, DrawImageNineNearestNoPaint) {
3619 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3622 [=](const SkRenderContext& ctx) {
3623 ctx.canvas->drawImageNine(ctx.image.get(), ToSkIRect(src),
3624 ToSkRect(dst), SkFilterMode::kNearest,
3625 nullptr);
3626 },
3627 [=](const DlRenderContext& ctx) {
3628 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3629 DlFilterMode::kNearest, nullptr);
3630 },
3631 kDrawImageNineFlags));
3632}
3633
3634TEST_F(DisplayListRendering, DrawImageNineLinear) {
3636 DlRect dst = kRenderBounds.Expand(-10.5f, -10.5f);
3639 [=](const SkRenderContext& ctx) {
3640 ctx.canvas->drawImageNine(ctx.image.get(), ToSkIRect(src),
3641 ToSkRect(dst), SkFilterMode::kLinear,
3642 &ctx.paint);
3643 },
3644 [=](const DlRenderContext& ctx) {
3645 ctx.canvas->DrawImageNine(ctx.image, src, dst,
3646 DlFilterMode::kLinear, &ctx.paint);
3647 },
3648 kDrawImageNineWithPaintFlags));
3649}
3650
3651TEST_F(DisplayListRendering, DrawAtlasNearest) {
3652 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
3653 DlScalar relative_right, DlScalar relative_bottom) {
3654 return DlRect::MakeLTRB(
3655 kRenderWidth * relative_left, kRenderHeight * relative_top,
3656 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
3657 };
3658
3659 const SkRSXform sk_xform[] = {
3660 // clang-format off
3661 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3662 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3663 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3664 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3665 // clang-format on
3666 };
3667 const DlRSTransform dl_xform[] = {
3668 // clang-format off
3669 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3670 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3671 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3672 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3673 // clang-format on
3674 };
3675 const DlRect tex[] = {
3676 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
3677 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
3678 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
3679 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
3680 };
3681 const SkColor sk_colors[] = {
3682 SK_ColorBLUE,
3683 SK_ColorGREEN,
3684 SK_ColorYELLOW,
3685 SK_ColorMAGENTA,
3686 };
3687 const DlColor dl_colors[] = {
3692 };
3694 const SkSamplingOptions sk_sampling = SkImageSampling::kNearestNeighbor;
3697 [=](const SkRenderContext& ctx) {
3698 ctx.canvas->drawAtlas(ctx.image.get(), {sk_xform, 4},
3699 {ToSkRects(tex), 4}, {sk_colors, 4},
3700 SkBlendMode::kSrcOver, sk_sampling, nullptr,
3701 &ctx.paint);
3702 },
3703 [=](const DlRenderContext& ctx) {
3704 ctx.canvas->DrawAtlas(ctx.image, dl_xform, tex, dl_colors, 4,
3705 DlBlendMode::kSrcOver, dl_sampling, nullptr,
3706 &ctx.paint);
3707 },
3708 kDrawAtlasWithPaintFlags));
3709}
3710
3711TEST_F(DisplayListRendering, DrawAtlasNearestNoPaint) {
3712 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
3713 DlScalar relative_right, DlScalar relative_bottom) {
3714 return DlRect::MakeLTRB(
3715 kRenderWidth * relative_left, kRenderHeight * relative_top,
3716 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
3717 };
3718
3719 const SkRSXform sk_xform[] = {
3720 // clang-format off
3721 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3722 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3723 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3724 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3725 // clang-format on
3726 };
3727 const DlRSTransform dl_xform[] = {
3728 // clang-format off
3729 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3730 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3731 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3732 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3733 // clang-format on
3734 };
3735 const DlRect tex[] = {
3736 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
3737 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
3738 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
3739 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
3740 };
3741 const SkColor sk_colors[] = {
3742 SK_ColorBLUE,
3743 SK_ColorGREEN,
3744 SK_ColorYELLOW,
3745 SK_ColorMAGENTA,
3746 };
3747 const DlColor dl_colors[] = {
3752 };
3754 const SkSamplingOptions sk_sampling = SkImageSampling::kNearestNeighbor;
3757 [=](const SkRenderContext& ctx) {
3758 ctx.canvas->drawAtlas(ctx.image.get(), {sk_xform, 4},
3759 {ToSkRects(tex), 4}, {sk_colors, 4},
3760 SkBlendMode::kSrcOver, sk_sampling, nullptr,
3761 nullptr);
3762 },
3763 [=](const DlRenderContext& ctx) {
3764 ctx.canvas->DrawAtlas(ctx.image, dl_xform, tex, dl_colors, 4,
3765 DlBlendMode::kSrcOver, dl_sampling, nullptr,
3766 nullptr);
3767 },
3768 kDrawAtlasFlags));
3769}
3770
3771TEST_F(DisplayListRendering, DrawAtlasLinear) {
3772 auto relative_rect = [](DlScalar relative_left, DlScalar relative_top,
3773 DlScalar relative_right, DlScalar relative_bottom) {
3774 return DlRect::MakeLTRB(
3775 kRenderWidth * relative_left, kRenderHeight * relative_top,
3776 kRenderWidth * relative_right, kRenderHeight * relative_bottom);
3777 };
3778
3779 const SkRSXform sk_xform[] = {
3780 // clang-format off
3781 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3782 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3783 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3784 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3785 // clang-format on
3786 };
3787 const DlRSTransform dl_xform[] = {
3788 // clang-format off
3789 { 1.2f, 0.0f, kRenderLeft, kRenderTop},
3790 { 0.0f, 1.2f, kRenderRight, kRenderTop},
3791 {-1.2f, 0.0f, kRenderRight, kRenderBottom},
3792 { 0.0f, -1.2f, kRenderLeft, kRenderBottom},
3793 // clang-format on
3794 };
3795 const DlRect tex[] = {
3796 relative_rect(0.0f, 0.0f, 0.5f, 0.5f),
3797 relative_rect(0.5f, 0.0f, 1.0f, 0.5f),
3798 relative_rect(0.5f, 0.5f, 1.0f, 1.0f),
3799 relative_rect(0.0f, 0.5f, 0.5f, 1.0f),
3800 };
3801 const SkColor sk_colors[] = {
3802 SK_ColorBLUE,
3803 SK_ColorGREEN,
3804 SK_ColorYELLOW,
3805 SK_ColorMAGENTA,
3806 };
3807 const DlColor dl_colors[] = {
3812 };
3813 const DlImageSampling dl_sampling = DlImageSampling::kLinear;
3814 const SkSamplingOptions sk_sampling = SkImageSampling::kLinear;
3817 [=](const SkRenderContext& ctx) {
3818 ctx.canvas->drawAtlas(ctx.image.get(), {sk_xform, 2},
3819 {ToSkRects(tex), 2}, {sk_colors, 2},
3820 SkBlendMode::kSrcOver, sk_sampling, nullptr,
3821 &ctx.paint);
3822 },
3823 [=](const DlRenderContext& ctx) {
3824 ctx.canvas->DrawAtlas(ctx.image, dl_xform, tex, dl_colors, 2,
3825 DlBlendMode::kSrcOver, dl_sampling, nullptr,
3826 &ctx.paint);
3827 },
3828 kDrawAtlasWithPaintFlags));
3829}
3830
3831sk_sp<DisplayList> makeTestDisplayList() {
3832 DisplayListBuilder builder;
3833 DlPaint paint;
3835 paint.setColor(DlColor(SK_ColorRED));
3838 paint);
3839 paint.setColor(DlColor(SK_ColorBLUE));
3842 paint);
3843 paint.setColor(DlColor(SK_ColorGREEN));
3846 paint);
3847 paint.setColor(DlColor(SK_ColorYELLOW));
3850 paint);
3851 return builder.Build();
3852}
3853
3854TEST_F(DisplayListRendering, DrawDisplayList) {
3855 sk_sp<DisplayList> display_list = makeTestDisplayList();
3858 [=](const SkRenderContext& ctx) { //
3859 DlSkCanvasAdapter(ctx.canvas).DrawDisplayList(display_list);
3860 },
3861 [=](const DlRenderContext& ctx) { //
3862 ctx.canvas->DrawDisplayList(display_list);
3863 },
3864 kDrawDisplayListFlags)
3865 .set_draw_display_list());
3866}
3867
3869 // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the
3870 // performance overlay can use Fuchsia's font manager instead of the empty
3871 // default.
3872#if defined(OS_FUCHSIA)
3873 GTEST_SKIP() << "Rendering comparisons require a valid default font manager";
3874#else
3875 sk_sp<SkTextBlob> blob =
3877 std::shared_ptr<DlText> skiaText = DlTextSkia::Make(blob);
3878#ifdef IMPELLER_SUPPORTS_RENDERING
3879 auto frame = impeller::MakeTextFrameFromTextBlobSkia(blob);
3880 std::shared_ptr<DlText> impellerText = DlTextImpeller::Make(frame);
3881#endif // IMPELLER_SUPPORTS_RENDERING
3882 DlScalar render_y_1_3 = kRenderTop + kRenderHeight * 0.3;
3883 DlScalar render_y_2_3 = kRenderTop + kRenderHeight * 0.6;
3886 [=](const SkRenderContext& ctx) {
3887 auto paint = ctx.paint;
3888 ctx.canvas->drawTextBlob(blob, kRenderLeft, render_y_1_3, paint);
3889 ctx.canvas->drawTextBlob(blob, kRenderLeft, render_y_2_3, paint);
3890 ctx.canvas->drawTextBlob(blob, kRenderLeft, kRenderBottom, paint);
3891 },
3892 [=](const DlRenderContext& ctx) {
3893 auto paint = ctx.paint;
3894 ctx.canvas->DrawText(skiaText, kRenderLeft, render_y_1_3, paint);
3895 ctx.canvas->DrawText(skiaText, kRenderLeft, render_y_2_3, paint);
3896 ctx.canvas->DrawText(skiaText, kRenderLeft, kRenderBottom, paint);
3897 },
3898#ifdef IMPELLER_SUPPORTS_RENDERING
3899 [=](const DlRenderContext& ctx) {
3900 auto paint = ctx.paint;
3901 ctx.canvas->DrawText(impellerText, kRenderLeft, render_y_1_3,
3902 paint);
3903 ctx.canvas->DrawText(impellerText, kRenderLeft, render_y_2_3,
3904 paint);
3905 ctx.canvas->DrawText(impellerText, kRenderLeft, kRenderBottom,
3906 paint);
3907 },
3908#endif // IMPELLER_SUPPORTS_RENDERING
3909 kDrawTextFlags)
3910 .set_draw_text_blob(),
3911 // From examining the bounds differential for the "Default" case, the
3912 // SkTextBlob adds a padding of ~32 on the left, ~30 on the right,
3913 // ~12 on top and ~8 on the bottom, so we add 33h & 13v allowed
3914 // padding to the tolerance
3916#endif // OS_FUCHSIA
3917}
3918
3920 DlPathBuilder path_builder;
3923 kRenderRight - 10, kRenderBottom - 20),
3925 DlPath path = path_builder.TakePath();
3926
3927 const DlColor color = DlColor::kDarkGrey();
3928 const DlScalar elevation = 7;
3929
3932 [=](const SkRenderContext& ctx) { //
3933 DlSkCanvasDispatcher::DrawShadow(ctx.canvas, path.GetSkPath(),
3934 color, elevation, false, 1.0);
3935 },
3936 [=](const DlRenderContext& ctx) { //
3937 ctx.canvas->DrawShadow(path, color, elevation, false, 1.0);
3938 },
3939 kDrawShadowFlags),
3940 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3941}
3942
3943TEST_F(DisplayListRendering, DrawShadowTransparentOccluder) {
3944 DlPathBuilder path_builder;
3947 kRenderRight - 10, kRenderBottom - 20),
3949 DlPath path = path_builder.TakePath();
3950
3951 const DlColor color = DlColor::kDarkGrey();
3952 const DlScalar elevation = 7;
3953
3956 [=](const SkRenderContext& ctx) { //
3957 DlSkCanvasDispatcher::DrawShadow(ctx.canvas, path.GetSkPath(),
3958 color, elevation, true, 1.0);
3959 },
3960 [=](const DlRenderContext& ctx) { //
3961 ctx.canvas->DrawShadow(path, color, elevation, true, 1.0);
3962 },
3963 kDrawShadowFlags),
3964 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3965}
3966
3968 DlPathBuilder path_builder;
3971 kRenderRight - 10, kRenderBottom - 20),
3973 DlPath path = path_builder.TakePath();
3974
3975 const DlColor color = DlColor::kDarkGrey();
3976 const DlScalar elevation = 7;
3977
3980 [=](const SkRenderContext& ctx) { //
3981 DlSkCanvasDispatcher::DrawShadow(ctx.canvas, path.GetSkPath(),
3982 color, elevation, false, 1.5);
3983 },
3984 [=](const DlRenderContext& ctx) { //
3985 ctx.canvas->DrawShadow(path, color, elevation, false, 1.5);
3986 },
3987 kDrawShadowFlags),
3988 CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3));
3989}
3990
3991TEST_F(DisplayListRendering, SaveLayerClippedContentStillFilters) {
3992 // draw rect is just outside of render bounds on the right
3993 const DlRect draw_rect = DlRect::MakeLTRB( //
3994 kRenderRight + 1, //
3995 kRenderTop, //
3997 kRenderBottom //
3998 );
3999 TestParameters test_params(
4000 [=](const SkRenderContext& ctx) {
4001 auto layer_filter =
4002 SkImageFilters::Blur(10.0f, 10.0f, SkTileMode::kDecal, nullptr);
4003 SkPaint layer_paint;
4004 layer_paint.setImageFilter(layer_filter);
4005 ctx.canvas->save();
4006 ctx.canvas->clipRect(ToSkRect(kRenderBounds), SkClipOp::kIntersect,
4007 false);
4008 ctx.canvas->saveLayer(&ToSkRect(kTestBounds2), &layer_paint);
4009 ctx.canvas->drawRect(ToSkRect(draw_rect), ctx.paint);
4010 ctx.canvas->restore();
4011 ctx.canvas->restore();
4012 },
4013 [=](const DlRenderContext& ctx) {
4014 auto layer_filter =
4016 DlPaint layer_paint;
4017 layer_paint.setImageFilter(layer_filter);
4018 ctx.canvas->Save();
4019 ctx.canvas->ClipRect(kRenderBounds, DlClipOp::kIntersect, false);
4020 ctx.canvas->SaveLayer(kTestBounds2, &layer_paint);
4021 ctx.canvas->DrawRect(draw_rect, ctx.paint);
4022 ctx.canvas->Restore();
4023 ctx.canvas->Restore();
4024 },
4025 kSaveLayerWithPaintFlags);
4026 CaseParameters case_params("Filtered SaveLayer with clipped content");
4027 BoundsTolerance tolerance = BoundsTolerance().addAbsolutePadding(6.0f, 6.0f);
4028
4029 for (auto& back_end : CanvasCompareTester::TestBackends) {
4030 auto provider = CanvasCompareTester::GetProvider(back_end);
4031 RenderEnvironment env = RenderEnvironment::MakeN32(provider.get());
4032 env.init_ref(kEmptySkSetup, test_params.sk_renderer(), //
4033 kEmptyDlSetup, test_params.dl_renderer(),
4034 test_params.imp_renderer());
4036 CanvasCompareTester::RenderWith(test_params, env, tolerance, case_params);
4037 }
4038}
4039
4040TEST_F(DisplayListRendering, SaveLayerConsolidation) {
4041 float commutable_color_matrix[]{
4042 // clang-format off
4043 0, 1, 0, 0, 0,
4044 0, 0, 1, 0, 0,
4045 1, 0, 0, 0, 0,
4046 0, 0, 0, 1, 0,
4047 // clang-format on
4048 };
4049 float non_commutable_color_matrix[]{
4050 // clang-format off
4051 0, 1, 0, .1, 0,
4052 0, 0, 1, .1, 0,
4053 1, 0, 0, .1, 0,
4054 0, 0, 0, .7, 0,
4055 // clang-format on
4056 };
4057 DlMatrix contract_matrix;
4058 contract_matrix.Translate({kRenderCenterX, kRenderCenterY});
4059 contract_matrix.Scale({0.9f, 0.9f});
4060 contract_matrix.Translate({kRenderCenterX, kRenderCenterY});
4061
4062 std::vector<DlScalar> opacities = {
4063 0,
4064 0.5f,
4065 SK_Scalar1,
4066 };
4067 std::vector<std::shared_ptr<const DlColorFilter>> color_filters = {
4068 DlColorFilter::MakeBlend(DlColor::kCyan(), DlBlendMode::kSrcATop),
4069 DlColorFilter::MakeMatrix(commutable_color_matrix),
4070 DlColorFilter::MakeMatrix(non_commutable_color_matrix),
4073 };
4074 std::vector<std::shared_ptr<DlImageFilter>> image_filters = {
4076 DlImageFilter::MakeDilate(5.0f, 5.0f),
4077 DlImageFilter::MakeErode(5.0f, 5.0f),
4079 };
4080
4081 auto render_content = [](DisplayListBuilder& builder) {
4082 builder.DrawRect(DlRect::MakeLTRB(kRenderLeft, kRenderTop, //
4085 builder.DrawRect(DlRect::MakeLTRB(kRenderCenterX, kRenderTop, //
4088 builder.DrawRect(DlRect::MakeLTRB(kRenderLeft, kRenderCenterY, //
4091 builder.DrawRect(DlRect::MakeLTRB(kRenderCenterX, kRenderCenterY, //
4093 DlPaint(DlColor::kRed().modulateOpacity(0.5f)));
4094 };
4095
4096 auto test_attributes_env =
4097 [render_content](DlPaint& paint1, DlPaint& paint2,
4098 const DlPaint& paint_both, bool same, bool rev_same,
4099 const std::string& desc1, const std::string& desc2,
4100 const RenderEnvironment* env) {
4101 DisplayListBuilder nested_builder;
4102 nested_builder.SaveLayer(kTestBounds2, &paint1);
4103 nested_builder.SaveLayer(kTestBounds2, &paint2);
4104 render_content(nested_builder);
4105 auto nested_results = env->getResult(nested_builder.Build());
4106
4107 DisplayListBuilder reverse_builder;
4108 reverse_builder.SaveLayer(kTestBounds2, &paint2);
4109 reverse_builder.SaveLayer(kTestBounds2, &paint1);
4110 render_content(reverse_builder);
4111 auto reverse_results = env->getResult(reverse_builder.Build());
4112
4113 DisplayListBuilder combined_builder;
4114 combined_builder.SaveLayer(kTestBounds2, &paint_both);
4115 render_content(combined_builder);
4116 auto combined_results = env->getResult(combined_builder.Build());
4117
4118 // Set this boolean to true to test if combinations that are marked
4119 // as incompatible actually are compatible despite our predictions.
4120 // Some of the combinations that we treat as incompatible actually
4121 // are compatible with swapping the order of the operations, but
4122 // it would take a bit of new infrastructure to really identify
4123 // those combinations. The only hard constraint to test here is
4124 // when we claim that they are compatible and they aren't.
4125 const bool always = false;
4126
4127 // In some circumstances, Skia can combine image filter evaluations
4128 // and elide a renderpass. In this case rounding and precision of inputs
4129 // to color filters may cause the output to differ by 1.
4130 if (always || same) {
4132 nested_results.get(), combined_results.get(),
4133 "nested " + desc1 + " then " + desc2, /*bounds=*/nullptr,
4134 /*tolerance=*/nullptr, DlColor::kTransparent(),
4135 /*fuzzyCompares=*/true, combined_results->width(),
4136 combined_results->height(), /*printMismatches=*/true);
4137 }
4138 if (always || rev_same) {
4140 reverse_results.get(), combined_results.get(),
4141 "nested " + desc2 + " then " + desc1, /*bounds=*/nullptr,
4142 /*tolerance=*/nullptr, DlColor::kTransparent(),
4143 /*fuzzyCompares=*/true, combined_results->width(),
4144 combined_results->height(), /*printMismatches=*/true);
4145 }
4146 };
4147
4148 auto test_attributes = [test_attributes_env](DlPaint& paint1, DlPaint& paint2,
4149 const DlPaint& paint_both,
4150 bool same, bool rev_same,
4151 const std::string& desc1,
4152 const std::string& desc2) {
4153 for (auto& back_end : CanvasCompareTester::TestBackends) {
4154 auto provider = CanvasCompareTester::GetProvider(back_end);
4155 auto env = std::make_unique<RenderEnvironment>(
4156 provider.get(), PixelFormat::kN32PremulPixelFormat);
4157 test_attributes_env(paint1, paint2, paint_both, //
4158 same, rev_same, desc1, desc2, env.get());
4159 }
4160 };
4161
4162 // CF then Opacity should always work.
4163 // The reverse sometimes works.
4164 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
4165 const auto& color_filter = color_filters[cfi];
4166 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
4167 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
4168
4169 for (size_t oi = 0; oi < opacities.size(); oi++) {
4170 DlScalar opacity = opacities[oi];
4171 std::string op_desc = "opacity " + std::to_string(opacity);
4172 DlPaint nested_paint2 = DlPaint().setOpacity(opacity);
4173
4174 DlPaint combined_paint = nested_paint1;
4175 combined_paint.setOpacity(opacity);
4176
4177 bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 ||
4178 color_filter->can_commute_with_opacity();
4179
4180 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
4181 op_then_cf_works, cf_desc, op_desc);
4182 }
4183 }
4184
4185 // Opacity then IF should always work.
4186 // The reverse can also work for some values of opacity.
4187 // The reverse should also theoretically work for some IFs, but we
4188 // get some rounding errors that are more than just trivial.
4189 for (size_t oi = 0; oi < opacities.size(); oi++) {
4190 DlScalar opacity = opacities[oi];
4191 std::string op_desc = "opacity " + std::to_string(opacity);
4192 DlPaint nested_paint1 = DlPaint().setOpacity(opacity);
4193
4194 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
4195 const auto& image_filter = image_filters[ifi];
4196 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
4197 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
4198
4199 DlPaint combined_paint = nested_paint1;
4200 combined_paint.setImageFilter(image_filter);
4201
4202 bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0;
4203 test_attributes(nested_paint1, nested_paint2, combined_paint, true,
4204 if_then_op_works, op_desc, if_desc);
4205 }
4206 }
4207
4208 // CF then IF should always work.
4209 // The reverse might work, but we lack the infrastructure to check it.
4210 for (size_t cfi = 0; cfi < color_filters.size(); cfi++) {
4211 const auto& color_filter = color_filters[cfi];
4212 std::string cf_desc = "color filter #" + std::to_string(cfi + 1);
4213 DlPaint nested_paint1 = DlPaint().setColorFilter(color_filter);
4214
4215 for (size_t ifi = 0; ifi < image_filters.size(); ifi++) {
4216 const auto& image_filter = image_filters[ifi];
4217 std::string if_desc = "image filter #" + std::to_string(ifi + 1);
4218 DlPaint nested_paint2 = DlPaint().setImageFilter(image_filter);
4219
4220 DlPaint combined_paint = nested_paint1;
4221 combined_paint.setImageFilter(image_filter);
4222
4223 test_attributes(nested_paint1, nested_paint2, combined_paint, true, false,
4224 cf_desc, if_desc);
4225 }
4226 }
4227}
4228
4229TEST_F(DisplayListRendering, MatrixColorFilterModifyTransparencyCheck) {
4230 auto test_matrix = [](int element, DlScalar value) {
4231 // clang-format off
4232 float matrix[] = {
4233 1, 0, 0, 0, 0,
4234 0, 1, 0, 0, 0,
4235 0, 0, 1, 0, 0,
4236 0, 0, 0, 1, 0,
4237 };
4238 // clang-format on
4239 std::string desc =
4240 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
4241 float original_value = matrix[element];
4242 matrix[element] = value;
4243 // Here we instantiate a DlMatrixColorFilter directly so that it is
4244 // not affected by the "NOP" detection in the factory. We sould not
4245 // need to do this if we tested by just rendering the filter color
4246 // over the source color with the filter blend mode instead of
4247 // rendering via a ColorFilter, but this test is more "black box".
4248 DlMatrixColorFilter filter(matrix);
4249 auto dl_filter = DlColorFilter::MakeMatrix(matrix);
4250 bool is_identity = (dl_filter == nullptr || original_value == value);
4251
4252 DlPaint paint(DlColor(0x7f7f7f7f));
4253 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4254
4255 DisplayListBuilder builder1;
4257 builder1.Rotate(45);
4258 builder1.Translate(-kTestCenter2.x, -kTestCenter2.y);
4259 builder1.DrawRect(kRenderBounds, paint);
4260 auto display_list1 = builder1.Build();
4261
4262 DisplayListBuilder builder2;
4264 builder2.Rotate(45);
4265 builder2.Translate(-kTestCenter2.x, -kTestCenter2.y);
4266 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
4267 builder2.DrawRect(kRenderBounds, paint);
4268 builder2.Restore();
4269 auto display_list2 = builder2.Build();
4270
4271 for (auto& back_end : CanvasCompareTester::TestBackends) {
4272 auto provider = CanvasCompareTester::GetProvider(back_end);
4273 auto env = std::make_unique<RenderEnvironment>(
4274 provider.get(), PixelFormat::kN32PremulPixelFormat);
4275 auto results1 = env->getResult(display_list1);
4276 auto results2 = env->getResult(display_list2);
4278 results1.get(), results2.get(), is_identity,
4279 desc + " filter affects rendering");
4280 int modified_transparent_pixels =
4282 results2.get());
4283 EXPECT_EQ(filter.modifies_transparent_black(),
4284 modified_transparent_pixels != 0)
4285 << desc;
4286 }
4287 };
4288
4289 // Tests identity (matrix[0] already == 1 in an identity filter)
4290 test_matrix(0, 1);
4291 // test_matrix(19, 1);
4292 for (int i = 0; i < 20; i++) {
4293 test_matrix(i, -0.25);
4294 test_matrix(i, 0);
4295 test_matrix(i, 0.25);
4296 test_matrix(i, 1);
4297 test_matrix(i, 1.25);
4298 test_matrix(i, SK_ScalarNaN);
4299 test_matrix(i, SK_ScalarInfinity);
4300 test_matrix(i, -SK_ScalarInfinity);
4301 }
4302}
4303
4304TEST_F(DisplayListRendering, MatrixColorFilterOpacityCommuteCheck) {
4305 auto test_matrix = [](int element, DlScalar value) {
4306 // clang-format off
4307 float matrix[] = {
4308 1, 0, 0, 0, 0,
4309 0, 1, 0, 0, 0,
4310 0, 0, 1, 0, 0,
4311 0, 0, 0, 1, 0,
4312 };
4313 // clang-format on
4314 std::string desc =
4315 "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
4316 matrix[element] = value;
4317 auto filter = DlColorFilter::MakeMatrix(matrix);
4318 EXPECT_EQ(std::isfinite(value), filter != nullptr);
4319
4320 DlPaint paint(DlColor(0x80808080));
4321 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
4322 DlPaint filter_save_paint = DlPaint().setColorFilter(filter);
4323
4324 DisplayListBuilder builder1;
4325 builder1.SaveLayer(kTestBounds2, &opacity_save_paint);
4326 builder1.SaveLayer(kTestBounds2, &filter_save_paint);
4327 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4328 builder1.DrawRect(kRenderBounds, paint);
4329 builder1.Restore();
4330 builder1.Restore();
4331 auto display_list1 = builder1.Build();
4332
4333 DisplayListBuilder builder2;
4334 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
4335 builder2.SaveLayer(kTestBounds2, &opacity_save_paint);
4336 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4337 builder2.DrawRect(kRenderBounds, paint);
4338 builder2.Restore();
4339 builder2.Restore();
4340 auto display_list2 = builder2.Build();
4341
4342 for (auto& back_end : CanvasCompareTester::TestBackends) {
4343 auto provider = CanvasCompareTester::GetProvider(back_end);
4344 auto env = std::make_unique<RenderEnvironment>(
4345 provider.get(), PixelFormat::kN32PremulPixelFormat);
4346 auto results1 = env->getResult(display_list1);
4347 auto results2 = env->getResult(display_list2);
4348 if (!filter || filter->can_commute_with_opacity()) {
4350 results2.get(), results1.get(), desc, nullptr, nullptr,
4352 } else {
4354 results1.get(), results2.get(), false, desc);
4355 }
4356 }
4357 };
4358
4359 // Tests identity (matrix[0] already == 1 in an identity filter)
4360 test_matrix(0, 1);
4361 // test_matrix(19, 1);
4362 for (int i = 0; i < 20; i++) {
4363 test_matrix(i, -0.25);
4364 test_matrix(i, 0);
4365 test_matrix(i, 0.25);
4366 test_matrix(i, 1);
4367 test_matrix(i, 1.1);
4368 test_matrix(i, SK_ScalarNaN);
4369 test_matrix(i, SK_ScalarInfinity);
4370 test_matrix(i, -SK_ScalarInfinity);
4371 }
4372}
4373
4374#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
4375 FUNC(kClear) \
4376 FUNC(kSrc) \
4377 FUNC(kDst) \
4378 FUNC(kSrcOver) \
4379 FUNC(kDstOver) \
4380 FUNC(kSrcIn) \
4381 FUNC(kDstIn) \
4382 FUNC(kSrcOut) \
4383 FUNC(kDstOut) \
4384 FUNC(kSrcATop) \
4385 FUNC(kDstATop) \
4386 FUNC(kXor) \
4387 FUNC(kPlus) \
4388 FUNC(kModulate) \
4389 FUNC(kScreen) \
4390 FUNC(kOverlay) \
4391 FUNC(kDarken) \
4392 FUNC(kLighten) \
4393 FUNC(kColorDodge) \
4394 FUNC(kColorBurn) \
4395 FUNC(kHardLight) \
4396 FUNC(kSoftLight) \
4397 FUNC(kDifference) \
4398 FUNC(kExclusion) \
4399 FUNC(kMultiply) \
4400 FUNC(kHue) \
4401 FUNC(kSaturation) \
4402 FUNC(kColor) \
4403 FUNC(kLuminosity)
4404
4405TEST_F(DisplayListRendering, BlendColorFilterModifyTransparencyCheck) {
4406 auto test_mode_color = [](DlBlendMode mode, DlColor color) {
4407 std::stringstream desc_str;
4408 std::string mode_string = BlendModeToString(mode);
4409 desc_str << "blend[" << mode_string << ", " << color << "]";
4410 std::string desc = desc_str.str();
4411 DlBlendColorFilter filter(color, mode);
4412 if (filter.modifies_transparent_black()) {
4413 ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc;
4414 }
4415
4416 DlPaint paint(DlColor(0x7f7f7f7f));
4417 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4418
4419 DisplayListBuilder builder1;
4421 builder1.Rotate(45);
4422 builder1.Translate(-kTestCenter2.x, -kTestCenter2.y);
4423 builder1.DrawRect(kRenderBounds, paint);
4424 auto display_list1 = builder1.Build();
4425
4426 DisplayListBuilder builder2;
4428 builder2.Rotate(45);
4429 builder2.Translate(-kTestCenter2.x, -kTestCenter2.y);
4430 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
4431 builder2.DrawRect(kRenderBounds, paint);
4432 builder2.Restore();
4433 auto display_list2 = builder2.Build();
4434
4435 for (auto& back_end : CanvasCompareTester::TestBackends) {
4436 auto provider = CanvasCompareTester::GetProvider(back_end);
4437 auto env = std::make_unique<RenderEnvironment>(
4438 provider.get(), PixelFormat::kN32PremulPixelFormat);
4439 auto results1 = env->getResult(display_list1);
4440 auto results2 = env->getResult(display_list2);
4441 int modified_transparent_pixels =
4443 results2.get());
4444 EXPECT_EQ(filter.modifies_transparent_black(),
4445 modified_transparent_pixels != 0)
4446 << desc;
4447 }
4448 };
4449
4450 auto test_mode = [&test_mode_color](DlBlendMode mode) {
4451 test_mode_color(mode, DlColor::kTransparent());
4452 test_mode_color(mode, DlColor::kWhite());
4453 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
4454 test_mode_color(mode, DlColor::kBlack());
4455 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
4456 };
4457
4458#define TEST_MODE(V) test_mode(DlBlendMode::V);
4460#undef TEST_MODE
4461}
4462
4463TEST_F(DisplayListRendering, BlendColorFilterOpacityCommuteCheck) {
4464 auto test_mode_color = [](DlBlendMode mode, DlColor color) {
4465 std::stringstream desc_str;
4466 std::string mode_string = BlendModeToString(mode);
4467 desc_str << "blend[" << mode_string << ", " << color << "]";
4468 std::string desc = desc_str.str();
4469 DlBlendColorFilter filter(color, mode);
4470 if (filter.can_commute_with_opacity()) {
4471 // If it can commute with opacity, then it might also be a NOP,
4472 // so we won't necessarily get a non-null return from |::Make()|
4473 } else {
4474 ASSERT_NE(DlColorFilter::MakeBlend(color, mode), nullptr) << desc;
4475 }
4476
4477 DlPaint paint(DlColor(0x80808080));
4478 DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
4479 DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
4480
4481 DisplayListBuilder builder1;
4482 builder1.SaveLayer(kTestBounds2, &opacity_save_paint);
4483 builder1.SaveLayer(kTestBounds2, &filter_save_paint);
4484 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4485 builder1.DrawRect(kRenderBounds, paint);
4486 builder1.Restore();
4487 builder1.Restore();
4488 auto display_list1 = builder1.Build();
4489
4490 DisplayListBuilder builder2;
4491 builder2.SaveLayer(kTestBounds2, &filter_save_paint);
4492 builder2.SaveLayer(kTestBounds2, &opacity_save_paint);
4493 // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
4494 builder2.DrawRect(kRenderBounds, paint);
4495 builder2.Restore();
4496 builder2.Restore();
4497 auto display_list2 = builder2.Build();
4498
4499 for (auto& back_end : CanvasCompareTester::TestBackends) {
4500 auto provider = CanvasCompareTester::GetProvider(back_end);
4501 auto env = std::make_unique<RenderEnvironment>(
4502 provider.get(), PixelFormat::kN32PremulPixelFormat);
4503 auto results1 = env->getResult(display_list1);
4504 auto results2 = env->getResult(display_list2);
4505 if (filter.can_commute_with_opacity()) {
4507 results2.get(), results1.get(), desc, nullptr, nullptr,
4509 } else {
4511 results1.get(), results2.get(), false, desc);
4512 }
4513 }
4514 };
4515
4516 auto test_mode = [&test_mode_color](DlBlendMode mode) {
4517 test_mode_color(mode, DlColor::kTransparent());
4518 test_mode_color(mode, DlColor::kWhite());
4519 test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
4520 test_mode_color(mode, DlColor::kBlack());
4521 test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
4522 };
4523
4524#define TEST_MODE(V) test_mode(DlBlendMode::V);
4526#undef TEST_MODE
4527}
4528
4530 // The following code uses the acronym MTB for "modifies_transparent_black"
4531
4532 protected:
4534 test_src_colors = {
4535 DlColor::kBlack().withAlpha(0), // transparent black
4536 DlColor::kBlack().withAlpha(0x7f), // half transparent black
4537 DlColor::kWhite().withAlpha(0x7f), // half transparent white
4538 DlColor::kBlack(), // opaque black
4539 DlColor::kWhite(), // opaque white
4540 DlColor::kRed(), // opaque red
4541 DlColor::kGreen(), // opaque green
4542 DlColor::kBlue(), // opaque blue
4543 DlColor::kDarkGrey(), // dark grey
4544 DlColor::kLightGrey(), // light grey
4545 };
4546
4547 // We test against a color cube of 3x3x3 colors [55,aa,ff]
4548 // plus transparency as the first color/pixel
4550 const int step = 0x55;
4551 static_assert(step * 3 == 255);
4552 for (int a = step; a < 256; a += step) {
4553 for (int r = step; r < 256; r += step) {
4554 for (int g = step; g < 256; g += step) {
4555 for (int b = step; b < 256; b += step) {
4556 test_dst_colors.push_back(DlColor(a << 24 | r << 16 | g << 8 | b));
4557 }
4558 }
4559 }
4560 }
4561
4562 static constexpr float color_filter_matrix_nomtb[] = {
4563 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4564 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4565 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4566 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4567 };
4568 static constexpr float color_filter_matrix_mtb[] = {
4569 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4570 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4571 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
4572 0.0001, 0.0001, 0.0001, 0.9997, 0.1, //
4573 };
4574 color_filter_nomtb = DlColorFilter::MakeMatrix(color_filter_matrix_nomtb);
4575 color_filter_mtb = DlColorFilter::MakeMatrix(color_filter_matrix_mtb);
4576 EXPECT_FALSE(color_filter_nomtb->modifies_transparent_black());
4577 EXPECT_TRUE(color_filter_mtb->modifies_transparent_black());
4578
4579 test_data =
4580 get_output(test_dst_colors.size(), 1, true, [this](SkCanvas* canvas) {
4581 int x = 0;
4582 for (DlColor color : test_dst_colors) {
4583 SkPaint paint;
4584 paint.setColor(ToSkColor4f(color));
4585 paint.setBlendMode(SkBlendMode::kSrc);
4586 canvas->drawRect(SkRect::MakeXYWH(x, 0, 1, 1), paint);
4587 x++;
4588 }
4589 });
4590
4591 // For image-on-image tests, the src and dest images will have repeated
4592 // rows/columns that have every color, but laid out at right angles to
4593 // each other so we see an interaction with every test color against
4594 // every other test color.
4595 int data_count = test_data->image()->width();
4597 data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
4598 ASSERT_EQ(test_data->width(), data_count);
4599 ASSERT_EQ(test_data->height(), 1);
4600 for (int y = 0; y < data_count; y++) {
4601 canvas->drawImage(test_data->image().get(), 0, y);
4602 }
4603 });
4605 data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
4606 ASSERT_EQ(test_data->width(), data_count);
4607 ASSERT_EQ(test_data->height(), 1);
4608 canvas->translate(data_count, 0);
4609 canvas->rotate(90);
4610 for (int y = 0; y < data_count; y++) {
4611 canvas->drawImage(test_data->image().get(), 0, y);
4612 }
4613 });
4614 // Double check that the pixel data is laid out in orthogonal stripes
4615 for (int y = 0; y < data_count; y++) {
4616 for (int x = 0; x < data_count; x++) {
4617 EXPECT_EQ(*test_image_dst_data->addr32(x, y), *test_data->addr32(x, 0));
4618 EXPECT_EQ(*test_image_src_data->addr32(x, y), *test_data->addr32(y, 0));
4619 }
4620 }
4621 }
4622
4623 // These flags are 0 by default until they encounter a counter-example
4624 // result and get set.
4625 static constexpr int kWasNotNop = 0x1; // Some tested pixel was modified
4626 static constexpr int kWasMTB = 0x2; // A transparent pixel was modified
4627
4628 std::vector<DlColor> test_src_colors;
4629 std::vector<DlColor> test_dst_colors;
4630
4631 std::shared_ptr<const DlColorFilter> color_filter_nomtb;
4632 std::shared_ptr<const DlColorFilter> color_filter_mtb;
4633
4634 // A 1-row image containing every color in test_dst_colors
4635 std::unique_ptr<RenderResult> test_data;
4636
4637 // A square image containing test_data duplicated in each row
4638 std::unique_ptr<RenderResult> test_image_dst_data;
4639
4640 // A square image containing test_data duplicated in each column
4641 std::unique_ptr<RenderResult> test_image_src_data;
4642
4643 std::unique_ptr<RenderResult> get_output(
4644 int w,
4645 int h,
4646 bool snapshot,
4647 const std::function<void(SkCanvas*)>& renderer) {
4648 auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(w, h));
4649 SkCanvas* canvas = surface->getCanvas();
4650 renderer(canvas);
4651 return std::make_unique<SkRenderResult>(surface, snapshot);
4652 }
4653
4655 DlColor result_color,
4656 const sk_sp<DisplayList>& dl,
4657 const std::string& desc) {
4658 int ret = 0;
4659 bool is_error = false;
4660 if (dst_color.isTransparent() && !result_color.isTransparent()) {
4661 ret |= kWasMTB;
4662 is_error = !dl->modifies_transparent_black();
4663 }
4664 if (result_color != dst_color) {
4665 ret |= kWasNotNop;
4666 is_error = (dl->op_count() == 0u);
4667 }
4668 if (is_error) {
4669 FML_LOG(ERROR) << std::hex << dst_color << " filters to " << result_color
4670 << desc;
4671 }
4672 return ret;
4673 }
4674
4675 int check_image_result(const std::unique_ptr<RenderResult>& dst_data,
4676 const std::unique_ptr<RenderResult>& result_data,
4677 const sk_sp<DisplayList>& dl,
4678 const std::string& desc) {
4679 EXPECT_EQ(dst_data->width(), result_data->width());
4680 EXPECT_EQ(dst_data->height(), result_data->height());
4681 int all_flags = 0;
4682 for (int y = 0; y < dst_data->height(); y++) {
4683 const uint32_t* dst_pixels = dst_data->addr32(0, y);
4684 const uint32_t* result_pixels = result_data->addr32(0, y);
4685 for (int x = 0; x < dst_data->width(); x++) {
4686 all_flags |= check_color_result(DlColor(dst_pixels[x]),
4687 DlColor(result_pixels[x]), dl, desc);
4688 }
4689 }
4690 return all_flags;
4691 }
4692
4693 void report_results(int all_flags,
4694 const sk_sp<DisplayList>& dl,
4695 const std::string& desc) {
4696 if (!dl->modifies_transparent_black()) {
4697 EXPECT_TRUE((all_flags & kWasMTB) == 0);
4698 } else if ((all_flags & kWasMTB) == 0) {
4699 FML_LOG(INFO) << "combination does not affect transparency: " << desc;
4700 }
4701 if (dl->op_count() == 0u) {
4702 EXPECT_TRUE((all_flags & kWasNotNop) == 0);
4703 } else if ((all_flags & kWasNotNop) == 0) {
4704 FML_LOG(INFO) << "combination could be classified as a nop: " << desc;
4705 }
4706 };
4707
4709 std::stringstream desc_stream;
4710 desc_stream << " using SkColorFilter::filterColor() with: ";
4711 desc_stream << BlendModeToString(mode);
4712 desc_stream << "/" << color;
4713 std::string desc = desc_stream.str();
4714 DisplayListBuilder builder(DlRect::MakeWH(100.0f, 100.0f));
4715 DlPaint paint = DlPaint(color).setBlendMode(mode);
4716 builder.DrawRect(DlRect::MakeLTRB(0.0f, 0.0f, 10.0f, 10.0f), paint);
4717 auto dl = builder.Build();
4718 if (dl->modifies_transparent_black()) {
4719 ASSERT_TRUE(dl->op_count() != 0u);
4720 }
4721
4722 auto sk_mode = static_cast<SkBlendMode>(mode);
4723 auto sk_color_filter =
4724 SkColorFilters::Blend(ToSkColor4f(color), nullptr, sk_mode);
4725 auto srgb = SkColorSpace::MakeSRGB();
4726 int all_flags = 0;
4727 if (sk_color_filter) {
4728 for (DlColor dst_color : test_dst_colors) {
4729 SkColor4f dst_color_f = ToSkColor4f(dst_color);
4730 DlColor result = DlColor(
4731 sk_color_filter->filterColor4f(dst_color_f, srgb.get(), srgb.get())
4732 .toSkColor());
4733 all_flags |= check_color_result(dst_color, result, dl, desc);
4734 }
4735 if ((all_flags & kWasMTB) != 0) {
4736 EXPECT_FALSE(sk_color_filter->isAlphaUnchanged());
4737 }
4738 }
4739 report_results(all_flags, dl, desc);
4740 };
4741
4743 std::stringstream desc_stream;
4744 desc_stream << " rendering with: ";
4745 desc_stream << BlendModeToString(mode);
4746 desc_stream << "/" << color;
4747 std::string desc = desc_stream.str();
4748 auto test_image = test_data->image();
4749 DlRect test_bounds =
4750 DlRect::MakeWH(test_image->width(), test_image->height());
4751 DisplayListBuilder builder(test_bounds);
4752 DlPaint dl_paint = DlPaint(color).setBlendMode(mode);
4753 builder.DrawRect(test_bounds, dl_paint);
4754 auto dl = builder.Build();
4755 bool dl_is_elided = dl->op_count() == 0u;
4756 bool dl_affects_transparent_pixels = dl->modifies_transparent_black();
4757 ASSERT_TRUE(!dl_is_elided || !dl_affects_transparent_pixels);
4758
4759 auto sk_mode = static_cast<SkBlendMode>(mode);
4760 SkPaint sk_paint;
4761 sk_paint.setBlendMode(sk_mode);
4762 sk_paint.setColor(ToSkColor4f(color));
4763 for (auto& back_end : CanvasCompareTester::TestBackends) {
4764 auto provider = CanvasCompareTester::GetProvider(back_end);
4765 auto result_surface = provider->MakeOffscreenSurface(
4766 test_image->width(), test_image->height(),
4768 SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
4769 result_canvas->clear(SK_ColorTRANSPARENT);
4770 result_canvas->drawImage(test_image.get(), 0, 0);
4771 result_canvas->drawRect(ToSkRect(test_bounds), sk_paint);
4772 if (GrDirectContext* direct_context = GrAsDirectContext(
4773 result_surface->sk_surface()->recordingContext())) {
4774 direct_context->flushAndSubmit();
4775 direct_context->flushAndSubmit(result_surface->sk_surface().get(),
4776 GrSyncCpu::kYes);
4777 }
4778 const std::unique_ptr<RenderResult> result_pixels =
4779 std::make_unique<SkRenderResult>(result_surface->sk_surface());
4780
4781 int all_flags = check_image_result(test_data, result_pixels, dl, desc);
4782 report_results(all_flags, dl, desc);
4783 }
4784 };
4785
4787 DlColor color,
4788 const DlColorFilter* color_filter,
4789 DlImageFilter* image_filter) {
4790 // if (true) { return; }
4791 std::stringstream desc_stream;
4792 desc_stream << " rendering with: ";
4793 desc_stream << BlendModeToString(mode);
4794 desc_stream << "/" << color;
4795 std::string cf_mtb = color_filter
4796 ? color_filter->modifies_transparent_black()
4797 ? "modifies transparency"
4798 : "preserves transparency"
4799 : "no filter";
4800 desc_stream << ", CF: " << cf_mtb;
4801 std::string if_mtb = image_filter
4802 ? image_filter->modifies_transparent_black()
4803 ? "modifies transparency"
4804 : "preserves transparency"
4805 : "no filter";
4806 desc_stream << ", IF: " << if_mtb;
4807 std::string desc = desc_stream.str();
4808
4809 DisplayListBuilder builder(DlRect::MakeWH(100.0f, 100.0f));
4810 DlPaint paint = DlPaint(color) //
4811 .setBlendMode(mode) //
4812 .setColorFilter(color_filter) //
4813 .setImageFilter(image_filter);
4814 builder.DrawImage(DlImage::Make(test_image_src_data->image()),
4816 auto dl = builder.Build();
4817
4818 int w = test_image_src_data->width();
4819 int h = test_image_src_data->height();
4820 auto sk_mode = static_cast<SkBlendMode>(mode);
4821 SkPaint sk_paint;
4822 sk_paint.setBlendMode(sk_mode);
4823 sk_paint.setColor(ToSkColor4f(color));
4824 sk_paint.setColorFilter(ToSk(color_filter));
4825 sk_paint.setImageFilter(ToSk(image_filter));
4826 for (auto& back_end : CanvasCompareTester::TestBackends) {
4827 auto provider = CanvasCompareTester::GetProvider(back_end);
4828 auto result_surface = provider->MakeOffscreenSurface(
4830 SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
4831 result_canvas->clear(SK_ColorTRANSPARENT);
4832 result_canvas->drawImage(test_image_dst_data->image(), 0, 0);
4833 result_canvas->drawImage(test_image_src_data->image(), 0, 0,
4834 SkSamplingOptions(), &sk_paint);
4835 if (GrDirectContext* direct_context = GrAsDirectContext(
4836 result_surface->sk_surface()->recordingContext())) {
4837 direct_context->flushAndSubmit();
4838 direct_context->flushAndSubmit(result_surface->sk_surface().get(),
4839 GrSyncCpu::kYes);
4840 }
4841 std::unique_ptr<RenderResult> result_pixels =
4842 std::make_unique<SkRenderResult>(result_surface->sk_surface());
4843
4844 int all_flags =
4845 check_image_result(test_image_dst_data, result_pixels, dl, desc);
4846 report_results(all_flags, dl, desc);
4847 }
4848 };
4849};
4850
4851TEST_F(DisplayListNopTest, BlendModeAndColorViaColorFilter) {
4852 auto test_mode_filter = [this](DlBlendMode mode) {
4853 for (DlColor color : test_src_colors) {
4854 test_mode_color_via_filter(mode, color);
4855 }
4856 };
4857
4858#define TEST_MODE(V) test_mode_filter(DlBlendMode::V);
4860#undef TEST_MODE
4861}
4862
4863TEST_F(DisplayListNopTest, BlendModeAndColorByRendering) {
4864 auto test_mode_render = [this](DlBlendMode mode) {
4865 // First check rendering a variety of colors onto image
4866 for (DlColor color : test_src_colors) {
4867 test_mode_color_via_rendering(mode, color);
4868 }
4869 };
4870
4871#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4873#undef TEST_MODE
4874}
4875
4876TEST_F(DisplayListNopTest, BlendModeAndColorAndFiltersByRendering) {
4877 auto test_mode_render = [this](DlBlendMode mode) {
4878 auto image_filter_nomtb = DlColorFilterImageFilter(color_filter_nomtb);
4879 auto image_filter_mtb = DlColorFilterImageFilter(color_filter_mtb);
4880 for (DlColor color : test_src_colors) {
4881 test_attributes_image(mode, color, nullptr, nullptr);
4882 test_attributes_image(mode, color, color_filter_nomtb.get(), nullptr);
4883 test_attributes_image(mode, color, color_filter_mtb.get(), nullptr);
4884 test_attributes_image(mode, color, nullptr, &image_filter_nomtb);
4885 test_attributes_image(mode, color, nullptr, &image_filter_mtb);
4886 }
4887 };
4888
4889#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
4891#undef TEST_MODE
4892}
4893
4894#undef FOR_EACH_BLEND_MODE_ENUM
4895
4896} // namespace testing
4897} // namespace flutter
GLenum type
constexpr bool applies_color() const
constexpr bool is_stroked(DlDrawStyle style=DlDrawStyle::kStroke) const
constexpr bool applies_color_filter() const
constexpr bool ignores_paint() const
constexpr bool applies_image_filter() const
constexpr bool applies_shader() const
const DisplayListSpecialGeometryFlags GeometryFlags(bool is_stroked) const
constexpr bool applies_anti_alias() const
constexpr bool applies_mask_filter() const
constexpr bool applies_blend() const
The primitive honors the DlBlendMode.
constexpr bool is_flood() const
void DrawImage(const sk_sp< DlImage > &image, const DlPoint &point, DlImageSampling sampling, const DlPaint *paint=nullptr) override
void SaveLayer(const std::optional< DlRect > &bounds, const DlPaint *paint=nullptr, const DlImageFilter *backdrop=nullptr, std::optional< int64_t > backdrop_id=std::nullopt) override
void Rotate(DlScalar degrees) override
void DrawDisplayList(const sk_sp< DisplayList > display_list, DlScalar opacity=SK_Scalar1) override
void Translate(DlScalar tx, DlScalar ty) override
sk_sp< DisplayList > Build()
Definition dl_builder.cc:66
void DrawRect(const DlRect &rect, const DlPaint &paint) override
constexpr bool may_have_joins() const
The geometry may have segments connect non-continuously.
constexpr bool may_have_end_caps() const
The geometry may have segments that end without closing the path.
constexpr bool may_have_acute_joins() const
constexpr bool butt_cap_becomes_square() const
Mainly for drawPoints(PointMode) where Butt caps are rendered as squares.
bool can_commute_with_opacity() const override
bool modifies_transparent_black() const override
Developer-facing API for rendering anything within the engine.
Definition dl_canvas.h:32
virtual DlRect GetDestinationClipCoverage() const =0
virtual DlISize GetBaseLayerDimensions() const =0
virtual void DrawRect(const DlRect &rect, const DlPaint &paint)=0
virtual DlMatrix GetMatrix() const =0
void Clear(DlColor color)
Definition dl_canvas.h:104
static std::shared_ptr< const DlColorFilter > MakeBlend(DlColor color, DlBlendMode mode)
static std::shared_ptr< const DlColorFilter > MakeLinearToSrgbGamma()
virtual bool modifies_transparent_black() const =0
static std::shared_ptr< const DlColorFilter > MakeMatrix(const float matrix[20])
static std::shared_ptr< const DlColorFilter > MakeSrgbToLinearGamma()
static std::shared_ptr< DlColorSource > MakeLinear(const DlPoint start_point, const DlPoint end_point, uint32_t stop_count, const DlColor *colors, const float *stops, DlTileMode tile_mode, const DlMatrix *matrix=nullptr)
static std::shared_ptr< DlImageFilter > MakeDilate(DlScalar radius_x, DlScalar radius_y)
static std::shared_ptr< DlImageFilter > MakeBlur(DlScalar sigma_x, DlScalar sigma_y, DlTileMode tile_mode)
static std::shared_ptr< DlImageFilter > MakeColorFilter(const std::shared_ptr< const DlColorFilter > &filter)
virtual bool modifies_transparent_black() const =0
static std::shared_ptr< DlImageFilter > MakeMatrix(const DlMatrix &matrix, DlImageSampling sampling)
static std::shared_ptr< DlImageFilter > MakeErode(DlScalar radius_x, DlScalar radius_y)
static sk_sp< DlImage > Make(const SkImage *image)
Definition dl_image.cc:11
bool modifies_transparent_black() const override
bool isAntiAlias() const
Definition dl_paint.h:57
DlStrokeCap getStrokeCap() const
Definition dl_paint.h:98
DlColor getColor() const
Definition dl_paint.h:69
DlPaint & setColor(DlColor color)
Definition dl_paint.h:70
DlBlendMode getBlendMode() const
Definition dl_paint.h:82
float getStrokeMiter() const
Definition dl_paint.h:120
DlStrokeJoin getStrokeJoin() const
Definition dl_paint.h:106
const DlColorSource * getColorSourcePtr() const
Definition dl_paint.h:129
DlPaint & setStrokeWidth(float width)
Definition dl_paint.h:115
DlPaint & setAlpha(uint8_t alpha)
Definition dl_paint.h:76
DlPaint & setBlendMode(DlBlendMode mode)
Definition dl_paint.h:85
const std::shared_ptr< const DlMaskFilter > & getMaskFilter() const
Definition dl_paint.h:180
DlDrawStyle getDrawStyle() const
Definition dl_paint.h:90
DlPaint & setImageFilter(std::nullptr_t filter)
Definition dl_paint.h:167
const std::shared_ptr< const DlColorSource > & getColorSource() const
Definition dl_paint.h:126
const std::shared_ptr< DlImageFilter > & getImageFilter() const
Definition dl_paint.h:162
float getStrokeWidth() const
Definition dl_paint.h:114
const std::shared_ptr< const DlColorFilter > & getColorFilter() const
Definition dl_paint.h:144
DlPaint & setDrawStyle(DlDrawStyle style)
Definition dl_paint.h:93
DlPaint & setOpacity(DlScalar opacity)
Definition dl_paint.h:78
DlPaint & setColorFilter(std::nullptr_t filter)
Definition dl_paint.h:149
DlPaint & setColorSource(std::nullptr_t source)
Definition dl_paint.h:131
bool isInvertColors() const
Definition dl_paint.h:63
DlPathBuilder & LineTo(DlPoint p2)
Draw a line from the current point to the indicated point p2.
DlPathBuilder & MoveTo(DlPoint p2)
Start a new contour that will originate at the indicated point p2.
DlPathBuilder & SetFillType(DlPathFillType fill_type)
Set the fill type that should be used to determine the interior of this path to the indicated |fill_t...
const DlPath TakePath()
Returns the path constructed by this path builder and resets its internal state to the default state ...
DlPathBuilder & AddCircle(DlPoint center, DlScalar radius)
Append a closed circular contour to the path centered on the provided point at the provided radius.
DlPathBuilder & AddRect(const DlRect &rect)
Append a closed rectangular contour to the path.
DlPathBuilder & AddRoundRect(const DlRoundRect &round_rect)
Append a closed rounded rect contour to the path.
DlPathBuilder & Close()
The path is closed back to the location of the most recent MoveTo call. Contours that are filled are ...
const SkPath & GetSkPath() const
Definition dl_path.cc:116
Backend implementation of |DlCanvas| for |SkCanvas|.
void DrawDisplayList(const sk_sp< DisplayList > display_list, DlScalar opacity=SK_Scalar1) override
static void DrawShadow(SkCanvas *canvas, const SkPath &path, DlColor color, float elevation, bool transparentOccluder, DlScalar dpr)
static std::shared_ptr< DlTextImpeller > Make(const std::shared_ptr< impeller::TextFrame > &frame)
static std::shared_ptr< DlTextSkia > Make(const sk_sp< SkTextBlob > &blob)
static std::shared_ptr< DlVertices > Make(DlVertexMode mode, int vertex_count, const DlPoint vertices[], const DlPoint texture_coordinates[], const DlColor colors[], int index_count=0, const uint16_t indices[]=nullptr, const DlRect *bounds=nullptr)
Constructs a DlVector with compact inline storage for all of its required and optional lists of data.
bool overflows(DlIRect pix_bounds, int worst_bounds_pad_x, int worst_bounds_pad_y) const
BoundsTolerance addBoundsPadding(DlScalar bounds_pad_x, DlScalar bounds_pad_y) const
static DlRect Scale(const DlRect &rect, const DlPoint &scales)
BoundsTolerance addPostClipPadding(DlScalar absolute_pad_x, DlScalar absolute_pad_y) const
BoundsTolerance mulScale(DlScalar scale_x, DlScalar scale_y) const
BoundsTolerance(const BoundsTolerance &)=default
BoundsTolerance clip(DlRect clip) const
BoundsTolerance addAbsolutePadding(DlScalar absolute_pad_x, DlScalar absolute_pad_y) const
bool operator==(BoundsTolerance const &other) const
BoundsTolerance addDiscreteOffset(DlScalar discrete_offset) const
static std::vector< BackendType > TestBackends
static bool checkPixels(const RenderResult *ref_result, const DlRect ref_bounds, const std::string &info, const DlColor bg=DlColor::kTransparent())
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 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 void compareToReference(const RenderResult *test_result, const RenderResult *ref_result, const std::string &info, const DlRect *bounds, const BoundsTolerance *tolerance, const DlColor bg, bool fuzzyCompares=false, int width=kTestWidth, int height=kTestHeight, bool printMismatches=false)
static void RenderWithClips(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &diff_tolerance)
static void RenderWith(const TestParameters &testP, const RenderEnvironment &env, const BoundsTolerance &tolerance_in, const CaseParameters &caseP)
static bool quickCompareToReference(const RenderResult *ref_result, const RenderResult *test_result, bool should_match, const std::string &info)
static void checkGroupOpacity(const RenderEnvironment &env, const sk_sp< DisplayList > &display_list, const RenderResult *ref_result, const std::string &info, DlColor bg)
static void save_to_png(const RenderResult *result, const std::string &op_desc, const std::string &reason)
static void showBoundsOverflow(const std::string &info, DlIRect &bounds, const BoundsTolerance *tolerance, int pixLeft, int pixTop, int pixRight, int pixBottom)
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 sk_sp< SkTextBlob > MakeTextBlob(const std::string &string, DlScalar font_height)
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)
void report_results(int all_flags, const sk_sp< DisplayList > &dl, const std::string &desc)
std::unique_ptr< RenderResult > test_data
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::shared_ptr< const DlColorFilter > color_filter_nomtb
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)
std::shared_ptr< const DlColorFilter > color_filter_mtb
void test_mode_color_via_filter(DlBlendMode mode, DlColor color)
void test_attributes_image(DlBlendMode mode, DlColor color, const DlColorFilter *color_filter, DlImageFilter *image_filter)
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 BackendType backend_type() const =0
virtual const std::string backend_name() const =0
void write(const std::string &path) const override
const uint32_t * addr32(int x, int y) const override
sk_sp< SkImage > image() const override
ImpellerRenderResult(sk_sp< DlPixelData > screenshot, DlRect render_bounds)
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
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 lineAdjust(const BoundsTolerance &tolerance, const DlPaint &paint, const DlMatrix &matrix) const
const BoundsTolerance adjust(const BoundsTolerance &tolerance, const DlPaint &paint, const DlMatrix &matrix) const
bool impeller_compatible(const DlPaint &paint) const
bool should_match(const RenderEnvironment &env, const CaseParameters &caseP, const DlPaint &attr, const MatrixClipJobRenderer &renderer) const
DlStrokeCap getCap(const DlPaint &attr, DisplayListSpecialGeometryFlags geo_flags) const
int32_t value
int32_t x
#define FOR_EACH_BLEND_MODE_ENUM(FUNC)
#define TEST_MODE(V)
const EmbeddedViewParams * params
FlutterVulkanImage * image
VkSurfaceKHR surface
Definition main.cc:65
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_LOG(severity)
Definition logging.h:101
#define FML_CHECK(condition)
Definition logging.h:104
#define FML_DCHECK(condition)
Definition logging.h:122
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
double y
const std::function< void(const SkRenderContext &)> SkRenderer
TEST_F(DisplayListTest, Defaults)
constexpr DlScalar kYOffsetU3
constexpr DlRect kRenderBounds
const std::function< void(const DlSetupContext &)> DlSetup
static void DrawCheckerboard(DlCanvas *canvas)
RenderContext< DlCanvas *, DlPaint &, sk_sp< DlImage > > DlSetupContext
constexpr DlScalar kMiter4DiamondOffsetX
const DlPoint kHorizontalMiterDiamondPoints[]
static auto kTestSkImage
RenderContext< SkCanvas *, SkPaint &, sk_sp< SkImage > > SkSetupContext
constexpr DlRect kTestBounds2
constexpr DlScalar kYOffsetD3
static const DlSetup kEmptyDlSetup
static const SkRenderer kEmptySkRenderer
SkFont CreateTestFontOfSize(DlScalar scalar)
constexpr DlScalar kYOffsetU2
sk_sp< DisplayList > makeTestDisplayList()
const int kHorizontalMiterDiamondPointCount
static std::shared_ptr< DlImageColorSource > MakeColorSource(const sk_sp< DlImage > &image)
constexpr DlScalar kYOffsetU1
constexpr DlScalar kMiter4DiamondOffsetY
constexpr DlScalar kXOffsetR2
constexpr DlScalar kYOffset0
DlSurfaceProvider::PixelFormat PixelFormat
constexpr DlScalar kXOffset0
const std::function< void(const SkSetupContext &)> SkSetup
constexpr DlScalar kMiterExtremeDiamondOffsetY
constexpr DlScalar kXOffsetL1
constexpr DlScalar kYOffsetD1
constexpr DlScalar kMiter10DiamondOffsetY
constexpr DlScalar kXOffsetL2
constexpr DlScalar kXOffsetL3
constexpr DlScalar kRenderCornerRadius
constexpr DlScalar kXOffsetR3
const int kVerticalMiterDiamondPointCount
RenderContext< DlCanvas *, const DlPaint &, sk_sp< DlImage > > DlRenderContext
constexpr DlScalar kMiterExtremeDiamondOffsetX
static const DlRenderer kEmptyDlRenderer
static const SkSetup kEmptySkSetup
constexpr DlScalar kMiter10DiamondOffsetX
RenderContext< SkCanvas *, const SkPaint &, sk_sp< SkImage > > SkRenderContext
constexpr DlPoint kVerticalMiterDiamondPoints[]
const std::function< void(const DlRenderContext &)> DlRenderer
constexpr DlScalar kXOffsetR1
constexpr DlScalar kYOffsetD2
constexpr DlScalar kRenderRadius
impeller::Scalar DlScalar
SkPaint ToSk(const DlPaint &paint)
@ kMiter
extends to miter limit
@ kBevel
connects outside edges
const SkPoint & ToSkPoint(const DlPoint &point)
DlStrokeCap
Definition dl_paint.h:28
@ kRound
adds circle
@ kButt
no stroke extension
@ kSquare
adds square
impeller::Matrix DlMatrix
impeller::Rect DlRect
impeller::Degrees DlDegrees
const SkIRect & ToSkIRect(const DlIRect &rect)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
@ kLines
draw each separate pair of points as a line segment
@ kPolygon
draw each pair of overlapping points as a line segment
@ kPoints
draw each point separately
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
SkColor4f ToSkColor4f(DlColor color)
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
@ kTriangles
The vertices are taken 3 at a time to form a triangle.
@ kStroke
strokes boundary of shapes
@ kFill
fills interior of shapes
SkMatrix ToSkMatrix(const DlMatrix &matrix)
impeller::IRect32 DlIRect
const DlIRect & ToDlIRect(const SkIRect &rect)
const SkPoint * ToSkPoints(const DlPoint *points)
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
@ kNormal
fuzzy inside and outside
bool NotEquals(const T *a, const U *b)
const SkRRect ToSkRRect(const DlRoundRect &round_rect)
TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder h
Definition switch_defs.h:54
DlMatrix ToDlMatrix(const SkMatrix &matrix)
impeller::Point DlPoint
SkM44 ToSkM44(const DlMatrix &matrix)
const DlRect & ToDlRect(const SkRect &rect)
const SkRect & ToSkRect(const DlRect &rect)
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition file_posix.cc:97
const char * BlendModeToString(BlendMode blend_mode)
Definition color.cc:47
BlendMode
Definition color.h:58
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition ref_ptr.h:261
flutter::DlPaint DlPaint
int32_t height
int32_t width
static constexpr DlColor kMagenta()
Definition dl_color.h:75
static constexpr DlColor kWhite()
Definition dl_color.h:70
static constexpr DlColor kBlue()
Definition dl_color.h:73
static constexpr DlColor kBlack()
Definition dl_color.h:69
static constexpr DlColor kYellow()
Definition dl_color.h:76
static constexpr DlColor kLightGrey()
Definition dl_color.h:79
constexpr DlScalar getRedF() const
Definition dl_color.h:114
constexpr DlScalar getAlphaF() const
Definition dl_color.h:113
constexpr DlScalar getBlueF() const
Definition dl_color.h:116
constexpr DlScalar getGreenF() const
Definition dl_color.h:115
static constexpr DlColor kTransparent()
Definition dl_color.h:68
static constexpr DlColor kRed()
Definition dl_color.h:71
static constexpr DlColor kGreen()
Definition dl_color.h:72
constexpr bool isTransparent() const
Definition dl_color.h:98
static constexpr DlColor kDarkGrey()
Definition dl_color.h:77
static constexpr DlColor kCyan()
Definition dl_color.h:74
uint32_t argb() const
Definition dl_color.h:158
constexpr bool isOpaque() const
Definition dl_color.h:97
DlColor withAlpha(uint8_t alpha) const
Definition dl_color.h:120
void Render(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)
A 4x4 matrix using column-major storage.
Definition matrix.h:37
constexpr Matrix Translate(const Vector3 &t) const
Definition matrix.h:263
static Matrix MakeRotationY(Radians r)
Definition matrix.h:208
static constexpr Matrix MakeRow(Scalar m0, Scalar m1, Scalar m2, Scalar m3, Scalar m4, Scalar m5, Scalar m6, Scalar m7, Scalar m8, Scalar m9, Scalar m10, Scalar m11, Scalar m12, Scalar m13, Scalar m14, Scalar m15)
Definition matrix.h:83
constexpr Matrix Scale(const Vector3 &s) const
Definition matrix.h:275
static Matrix MakeRotationX(Radians r)
Definition matrix.h:193
static RoundRect MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
Definition round_rect.h:31
constexpr auto GetBottom() const
Definition rect.h:357
static constexpr TRect MakeWH(Type width, Type height)
Definition rect.h:140
constexpr auto GetTop() const
Definition rect.h:353
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition rect.h:327
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
Definition rect.h:347
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition rect.h:297
constexpr T Area() const
Get the area of the rectangle, equivalent to |GetSize().Area()|.
Definition rect.h:376
constexpr bool Contains(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the half-open interior of this rectangle.
Definition rect.h:231
static constexpr std::enable_if_t< std::is_floating_point_v< FT >, TRect > Make(const TRect< U > &rect)
Definition rect.h:157
constexpr auto GetLeft() const
Definition rect.h:351
constexpr TPoint< T > GetLeftTop() const
Definition rect.h:359
RoundOut(const TRect< U > &r)
Definition rect.h:679
constexpr auto GetRight() const
Definition rect.h:355
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
constexpr TPoint< T > GetRightBottom() const
Definition rect.h:371
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.
Definition rect.h:341
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition rect.h:618
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition rect.h:382
constexpr TRect IntersectionOrEmpty(const TRect &o) const
Definition rect.h:542
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition rect.h:602
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
constexpr Type Area() const
Definition size.h:120
Type height
Definition size.h:29
Type width
Definition size.h:28
std::vector< Point > points