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