diff --git a/flow/compositor_context.cc b/flow/compositor_context.cc index 7013bc86ee374..e8fd35984bbdf 100644 --- a/flow/compositor_context.cc +++ b/flow/compositor_context.cc @@ -11,14 +11,16 @@ namespace flutter { std::optional FrameDamage::ComputeClipRect( - flutter::LayerTree& layer_tree) { + flutter::LayerTree& layer_tree, + bool has_raster_cache) { if (layer_tree.root_layer()) { PaintRegionMap empty_paint_region_map; DiffContext context(layer_tree.frame_size(), layer_tree.device_pixel_ratio(), layer_tree.paint_region_map(), prev_layer_tree_ ? prev_layer_tree_->paint_region_map() - : empty_paint_region_map); + : empty_paint_region_map, + has_raster_cache); context.PushCullRect(SkRect::MakeIWH(layer_tree.frame_size().width(), layer_tree.frame_size().height())); { @@ -119,7 +121,9 @@ RasterStatus CompositorContext::ScopedFrame::Raster( TRACE_EVENT0("flutter", "CompositorContext::ScopedFrame::Raster"); std::optional clip_rect = - frame_damage ? frame_damage->ComputeClipRect(layer_tree) : std::nullopt; + frame_damage + ? frame_damage->ComputeClipRect(layer_tree, !ignore_raster_cache) + : std::nullopt; bool root_needs_readback = layer_tree.Preroll( *this, ignore_raster_cache, clip_rect ? *clip_rect : kGiantRect); diff --git a/flow/compositor_context.h b/flow/compositor_context.h index f7667640caa71..a3f0478a84987 100644 --- a/flow/compositor_context.h +++ b/flow/compositor_context.h @@ -81,7 +81,8 @@ class FrameDamage { // If previous layer tree is not specified, clip rect will be nullopt, // but the paint region of layer_tree will be calculated so that it can be // used for diffing of subsequent frames. - std::optional ComputeClipRect(flutter::LayerTree& layer_tree); + std::optional ComputeClipRect(flutter::LayerTree& layer_tree, + bool has_raster_cache); // See Damage::frame_damage. std::optional GetFrameDamage() const { diff --git a/flow/diff_context.cc b/flow/diff_context.cc index 524ad88620dad..abf3af1325d3f 100644 --- a/flow/diff_context.cc +++ b/flow/diff_context.cc @@ -10,12 +10,14 @@ namespace flutter { DiffContext::DiffContext(SkISize frame_size, double frame_device_pixel_ratio, PaintRegionMap& this_frame_paint_region_map, - const PaintRegionMap& last_frame_paint_region_map) + const PaintRegionMap& last_frame_paint_region_map, + bool has_raster_cache) : rects_(std::make_shared>()), frame_size_(frame_size), frame_device_pixel_ratio_(frame_device_pixel_ratio), this_frame_paint_region_map_(this_frame_paint_region_map), - last_frame_paint_region_map_(last_frame_paint_region_map) {} + last_frame_paint_region_map_(last_frame_paint_region_map), + has_raster_cache_(has_raster_cache) {} void DiffContext::BeginSubtree() { state_stack_.push_back(state_); diff --git a/flow/diff_context.h b/flow/diff_context.h index 28f2a0c567179..9fcf227ddbfa7 100644 --- a/flow/diff_context.h +++ b/flow/diff_context.h @@ -45,7 +45,8 @@ class DiffContext { explicit DiffContext(SkISize frame_size, double device_pixel_aspect_ratio, PaintRegionMap& this_frame_paint_region_map, - const PaintRegionMap& last_frame_paint_region_map); + const PaintRegionMap& last_frame_paint_region_map, + bool has_raster_cache); // Starts a new subtree. void BeginSubtree(); @@ -156,6 +157,11 @@ class DiffContext { // frame layer tree. PaintRegion GetOldLayerPaintRegion(const Layer* layer) const; + // Whether or not a raster cache is being used. If so, we must snap + // all transformations to physical pixels if the layer may be raster + // cached. + bool has_raster_cache() const { return has_raster_cache_; } + class Statistics { public: // Picture replaced by different picture @@ -223,6 +229,7 @@ class DiffContext { PaintRegionMap& this_frame_paint_region_map_; const PaintRegionMap& last_frame_paint_region_map_; + bool has_raster_cache_; void AddDamage(const SkRect& rect); diff --git a/flow/layers/clip_shape_layer.h b/flow/layers/clip_shape_layer.h index faf4c26d31b6c..e4594f8e2eafc 100644 --- a/flow/layers/clip_shape_layer.h +++ b/flow/layers/clip_shape_layer.h @@ -8,6 +8,7 @@ #include "flutter/flow/layers/cacheable_layer.h" #include "flutter/flow/layers/container_layer.h" #include "flutter/flow/paint_utils.h" +#include "flutter/flow/raster_cache_util.h" namespace flutter { @@ -32,6 +33,10 @@ class ClipShapeLayer : public CacheableContainerLayer { context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); } } + if (UsesSaveLayer() && context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } if (context->PushCullRect(clip_shape_bounds())) { DiffChildren(context, prev); } @@ -45,12 +50,16 @@ class ClipShapeLayer : public CacheableContainerLayer { if (!context->cull_rect.intersect(clip_shape_bounds())) { context->cull_rect.setEmpty(); } + SkMatrix child_matrix = matrix; + if (context->raster_cache && uses_save_layer) { + child_matrix = RasterCacheUtil::GetIntegralTransCTM(child_matrix); + } // We can use the raster_cache for children only when the use_save_layer is // true so if use_save_layer is false we pass the layer_raster_item is // nullptr which mean we don't do raster cache logic. AutoCache cache = AutoCache(uses_save_layer ? layer_raster_cache_item_.get() : nullptr, - context, matrix); + context, child_matrix); Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); @@ -89,6 +98,9 @@ class ClipShapeLayer : public CacheableContainerLayer { AutoCachePaint cache_paint(context); if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { return; } diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index 02822bf68d612..deb4f09579cd5 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -27,6 +27,11 @@ void ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { } } + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } + DiffChildren(context, prev); context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); @@ -38,7 +43,12 @@ void ColorFilterLayer::Preroll(PrerollContext* context, Layer::AutoPrerollSaveLayerState::Create(context); AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context, matrix); - ContainerLayer::Preroll(context, matrix); + SkMatrix child_matrix = matrix; + if (context->raster_cache) { + child_matrix = RasterCacheUtil::GetIntegralTransCTM(child_matrix); + } + + ContainerLayer::Preroll(context, child_matrix); // We always use a saveLayer (or a cached rendering), so we // can always apply opacity in those cases. context->subtree_can_inherit_opacity = true; @@ -49,6 +59,9 @@ void ColorFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); AutoCachePaint cache_paint(context); if (layer_raster_cache_item_->IsCacheChildren()) { cache_paint.setColorFilter(filter_.get()); diff --git a/flow/layers/display_list_layer.cc b/flow/layers/display_list_layer.cc index 5039feae31c3b..b5fbe9ce1b521 100644 --- a/flow/layers/display_list_layer.cc +++ b/flow/layers/display_list_layer.cc @@ -52,6 +52,10 @@ void DisplayListLayer::Diff(DiffContext* context, const Layer* old_layer) { #endif } context->PushTransform(SkMatrix::Translate(offset_.x(), offset_.y())); + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } context->AddLayerBounds(display_list()->bounds()); context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } @@ -95,9 +99,13 @@ void DisplayListLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "DisplayListLayer::Preroll"); DisplayList* disp_list = display_list(); + SkMatrix child_matrix = matrix; + if (context->raster_cache) { + child_matrix = RasterCacheUtil::GetIntegralTransCTM(child_matrix); + } AutoCache cache = - AutoCache(display_list_raster_cache_item_.get(), context, matrix); + AutoCache(display_list_raster_cache_item_.get(), context, child_matrix); if (disp_list->can_apply_group_opacity()) { context->subtree_can_inherit_opacity = true; } @@ -111,6 +119,10 @@ void DisplayListLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); + if (context.raster_cache) { + context.leaf_nodes_canvas->setMatrix(RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } if (context.raster_cache && display_list_raster_cache_item_) { AutoCachePaint cache_paint(context); diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc index ca88583539144..4db87af8d0451 100644 --- a/flow/layers/display_list_layer_unittests.cc +++ b/flow/layers/display_list_layer_unittests.cc @@ -325,10 +325,25 @@ TEST_F(DisplayListLayerDiffTest, FractionalTranslation) { tree1.root()->Add( CreateDisplayListLayer(display_list, SkPoint::Make(0.5, 0.5))); - auto damage = DiffLayerTree(tree1, MockLayerTree()); + auto damage = + DiffLayerTree(tree1, MockLayerTree(), SkIRect::MakeEmpty(), 0, 0, + /*use_raster_cache=*/false); EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 61, 61)); } +TEST_F(DisplayListLayerDiffTest, FractionalTranslationWithRasterCache) { + auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); + + MockLayerTree tree1; + tree1.root()->Add( + CreateDisplayListLayer(display_list, SkPoint::Make(0.5, 0.5))); + + auto damage = + DiffLayerTree(tree1, MockLayerTree(), SkIRect::MakeEmpty(), 0, 0, + /*use_raster_cache=*/true); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(11, 11, 61, 61)); +} + TEST_F(DisplayListLayerDiffTest, DisplayListCompare) { MockLayerTree tree1; auto display_list1 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index addccef60d533..fce658b17b1ee 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -25,6 +25,11 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { } } + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } + if (filter_) { auto filter = filter_->makeWithLocalMatrix(context->GetTransform()); if (filter) { @@ -51,7 +56,11 @@ void ImageFilterLayer::Preroll(PrerollContext* context, AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context, matrix); SkRect child_bounds = SkRect::MakeEmpty(); - PrerollChildren(context, matrix, &child_bounds); + SkMatrix child_matrix = matrix; + if (context->raster_cache) { + child_matrix = RasterCacheUtil::GetIntegralTransCTM(child_matrix); + } + PrerollChildren(context, child_matrix, &child_bounds); // We always paint with a saveLayer (or a cached rendering), // so we can always apply opacity in any of those cases. @@ -74,7 +83,7 @@ void ImageFilterLayer::Preroll(PrerollContext* context, // So in here we reset the LayerRasterCacheItem cache state. layer_raster_cache_item_->MarkNotCacheChildren(); - transformed_filter_ = filter_->makeWithLocalMatrix(matrix); + transformed_filter_ = filter_->makeWithLocalMatrix(child_matrix); if (transformed_filter_) { layer_raster_cache_item_->MarkCacheChildren(); } @@ -86,6 +95,9 @@ void ImageFilterLayer::Paint(PaintContext& context) const { AutoCachePaint cache_paint(context); if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); if (layer_raster_cache_item_->IsCacheChildren()) { cache_paint.setImageFilter(transformed_filter_.get()); } diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index e9702629052ca..e1370300c75d7 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -5,6 +5,7 @@ #include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/layers/cacheable_layer.h" +#include "flutter/flow/raster_cache_util.h" #include "third_party/skia/include/core/SkPaint.h" namespace flutter { @@ -27,6 +28,10 @@ void OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) { } } context->PushTransform(SkMatrix::Translate(offset_.fX, offset_.fY)); + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } DiffChildren(context, prev); context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); } @@ -85,6 +90,11 @@ void OpacityLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->translate(offset_.fX, offset_.fY); + if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } SkScalar inherited_opacity = context.inherited_opacity; SkScalar subtree_opacity = opacity() * inherited_opacity; diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index 07da4e1717ab8..6f5bc833b4e92 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -644,9 +644,23 @@ TEST_F(OpacityLayerDiffTest, FractionalTranslation) { MockLayerTree tree1; tree1.root()->Add(layer); - auto damage = DiffLayerTree(tree1, MockLayerTree()); + auto damage = DiffLayerTree(tree1, MockLayerTree(), SkIRect::MakeEmpty(), 0, + 0, /*use_raster_cache=*/false); EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 61, 61)); } +TEST_F(OpacityLayerDiffTest, FractionalTranslationWithRasterCache) { + auto picture = CreateDisplayListLayer( + CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1)); + auto layer = CreateOpacityLater({picture}, 128, SkPoint::Make(0.5, 0.5)); + + MockLayerTree tree1; + tree1.root()->Add(layer); + + auto damage = DiffLayerTree(tree1, MockLayerTree(), SkIRect::MakeEmpty(), 0, + 0, /*use_raster_cache=*/true); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(11, 11, 61, 61)); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index d4a441417e966..44ef92fa1e907 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -26,6 +26,10 @@ void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) { context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); } } + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } DiffChildren(context, prev); context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); @@ -49,6 +53,12 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { AutoCachePaint cache_paint(context); + if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } + if (context.raster_cache) { if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { return; diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 9b6e72d43c2c8..d3487de6d1a41 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -32,38 +32,14 @@ void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const { TRACE_EVENT0("flutter", "RasterCacheResult::draw"); SkAutoCanvasRestore auto_restore(&canvas, true); - SkRect bounds = - RasterCacheUtil::GetDeviceBounds(logical_rect_, canvas.getTotalMatrix()); -#ifndef NDEBUG - // The image dimensions should always be larger than the device bounds and - // smaller than the device bounds plus one pixel, at the same time, we must - // introduce epsilon to solve the round-off error. The value of epsilon is - // 1/512, which represents half of an AA sample. - float epsilon = 1 / 512.0; - FML_DCHECK(image_->dimensions().width() - bounds.width() > -epsilon && - image_->dimensions().height() - bounds.height() > -epsilon && - image_->dimensions().width() - bounds.width() < 1 + epsilon && - image_->dimensions().height() - bounds.height() < 1 + epsilon); -#endif + SkRect bounds = RasterCacheUtil::GetRoundedOutDeviceBounds( + logical_rect_, canvas.getTotalMatrix()); + FML_DCHECK(std::abs(bounds.width() - image_->dimensions().width()) <= 1 && + std::abs(bounds.height() - image_->dimensions().height()) <= 1); canvas.resetMatrix(); flow_.Step(); - - bool exceeds_bounds = bounds.fLeft + image_->dimensions().width() > - SkScalarCeilToScalar(bounds.fRight) || - bounds.fTop + image_->dimensions().height() > - SkScalarCeilToScalar(bounds.fBottom); - - // Make sure raster cache doesn't bleed to physical pixels outside of - // original bounds. https://github.com/flutter/flutter/issues/110002 - if (exceeds_bounds) { - canvas.save(); - canvas.clipRect(SkRect::Make(bounds.roundOut())); - } canvas.drawImage(image_, bounds.fLeft, bounds.fTop, SkSamplingOptions(), paint); - if (exceeds_bounds) { - canvas.restore(); - } } RasterCache::RasterCache(size_t access_threshold, @@ -80,14 +56,12 @@ std::unique_ptr RasterCache::Rasterize( const { TRACE_EVENT0("flutter", "RasterCachePopulate"); - SkRect dest_rect = - RasterCacheUtil::GetDeviceBounds(context.logical_rect, context.matrix); - // we always round out here so that the texture is integer sized. - int width = SkScalarCeilToInt(dest_rect.width()); - int height = SkScalarCeilToInt(dest_rect.height()); + SkRect dest_rect = RasterCacheUtil::GetRoundedOutDeviceBounds( + context.logical_rect, context.matrix); - const SkImageInfo image_info = SkImageInfo::MakeN32Premul( - width, height, sk_ref_sp(context.dst_color_space)); + const SkImageInfo image_info = + SkImageInfo::MakeN32Premul(dest_rect.width(), dest_rect.height(), + sk_ref_sp(context.dst_color_space)); sk_sp surface = context.gr_context ? SkSurface::MakeRenderTarget( @@ -139,7 +113,8 @@ bool RasterCache::UpdateCacheEntry( int RasterCache::MarkSeen(const RasterCacheKeyID& id, const SkMatrix& matrix, bool visible) const { - RasterCacheKey key = RasterCacheKey(id, matrix); + RasterCacheKey key = + RasterCacheKey(id, RasterCacheUtil::GetIntegralTransCTM(matrix)); Entry& entry = cache_[key]; entry.encountered_this_frame = true; entry.visible_this_frame = visible; @@ -151,7 +126,8 @@ int RasterCache::MarkSeen(const RasterCacheKeyID& id, int RasterCache::GetAccessCount(const RasterCacheKeyID& id, const SkMatrix& matrix) const { - RasterCacheKey key = RasterCacheKey(id, matrix); + RasterCacheKey key = + RasterCacheKey(id, RasterCacheUtil::GetIntegralTransCTM(matrix)); auto entry = cache_.find(key); if (entry != cache_.cend()) { return entry->second.accesses_since_visible; @@ -161,7 +137,8 @@ int RasterCache::GetAccessCount(const RasterCacheKeyID& id, bool RasterCache::HasEntry(const RasterCacheKeyID& id, const SkMatrix& matrix) const { - RasterCacheKey key = RasterCacheKey(id, matrix); + RasterCacheKey key = + RasterCacheKey(id, RasterCacheUtil::GetIntegralTransCTM(matrix)); if (cache_.find(key) != cache_.cend()) { return true; } @@ -171,7 +148,8 @@ bool RasterCache::HasEntry(const RasterCacheKeyID& id, bool RasterCache::Draw(const RasterCacheKeyID& id, SkCanvas& canvas, const SkPaint* paint) const { - auto it = cache_.find(RasterCacheKey(id, canvas.getTotalMatrix())); + auto it = cache_.find(RasterCacheKey( + id, RasterCacheUtil::GetIntegralTransCTM(canvas.getTotalMatrix()))); if (it == cache_.end()) { return false; } diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index 9a72da8e698ff..afd00392afa1d 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -787,67 +787,5 @@ TEST_F(RasterCacheTest, RasterCacheKeyID_LayerChildrenIds) { ASSERT_EQ(ids, expected_ids); } -TEST_F(RasterCacheTest, RasterCacheBleedingNoClipNeeded) { - SkImageInfo info = - SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); - - auto image = SkImage::MakeRasterData( - info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); - auto canvas = MockCanvas(); - canvas.setMatrix(SkMatrix::Scale(2, 2)); - // Drawing cached image does not exceeds physical pixels of the original - // bounds and does not need to be clipped. - auto cache_result = - RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 20, 20), ""); - auto paint = SkPaint(); - cache_result.draw(canvas, &paint); - - EXPECT_EQ(canvas.draw_calls(), - std::vector({ - MockCanvas::DrawCall{ - 0, MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, - MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, - MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, - MockCanvas::DrawCall{ - 1, MockCanvas::DrawImageData{image, 200.6, 200.6, - SkSamplingOptions(), paint}}, - MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, - })); -} - -TEST_F(RasterCacheTest, RasterCacheBleedingClipNeeded) { - SkImageInfo info = - SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); - - auto image = SkImage::MakeRasterData( - info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); - auto canvas = MockCanvas(); - canvas.setMatrix(SkMatrix::Scale(2, 2)); - - auto cache_result = - RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 19.6, 19.6), ""); - auto paint = SkPaint(); - cache_result.draw(canvas, &paint); - - EXPECT_EQ( - canvas.draw_calls(), - std::vector({ - MockCanvas::DrawCall{0, - MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, - MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, - MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, - MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, - MockCanvas::DrawCall{ - 2, MockCanvas::ClipRectData{SkRect::MakeLTRB(200, 200, 240, 240), - SkClipOp::kIntersect, - MockCanvas::kHard_ClipEdgeStyle}}, - MockCanvas::DrawCall{ - 2, MockCanvas::DrawImageData{image, 200.6, 200.6, - SkSamplingOptions(), paint}}, - MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, - MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, - })); -} - } // namespace testing } // namespace flutter diff --git a/flow/raster_cache_util.h b/flow/raster_cache_util.h index 35abaf2d5f56c..71836fdaf502d 100644 --- a/flow/raster_cache_util.h +++ b/flow/raster_cache_util.h @@ -54,6 +54,39 @@ struct RasterCacheUtil { ctm.mapRect(&device_rect, rect); return device_rect; } + + static SkRect GetRoundedOutDeviceBounds(const SkRect& rect, + const SkMatrix& ctm) { + SkRect device_rect; + ctm.mapRect(&device_rect, rect); + device_rect.roundOut(&device_rect); + return device_rect; + } + + /** + * @brief Snap the translation components of the matrix to integers. + * + * The snapping will only happen if the matrix only has scale and translation + * transformations. This is used, along with GetRoundedOutDeviceBounds, to + * ensure that the textures drawn by the raster cache are exactly aligned to + * physical pixels. Any layers that participate in raster caching must align + * themselves to physical pixels even when not cached to prevent a change in + * apparent location if caching is later applied. + * + * @param ctm the current transformation matrix. + * @return SkMatrix the snapped transformation matrix. + */ + static SkMatrix GetIntegralTransCTM(const SkMatrix& ctm) { + // Avoid integral snapping if the matrix has complex transformation to avoid + // the artifact observed in https://github.com/flutter/flutter/issues/41654. + if (!ctm.isScaleTranslate()) { + return ctm; + } + SkMatrix result = ctm; + result[SkMatrix::kMTransX] = SkScalarRoundToScalar(ctm.getTranslateX()); + result[SkMatrix::kMTransY] = SkScalarRoundToScalar(ctm.getTranslateY()); + return result; + } }; } // namespace flutter diff --git a/flow/testing/diff_context_test.cc b/flow/testing/diff_context_test.cc index e9d5a4b05d9e6..47b35f5b80258 100644 --- a/flow/testing/diff_context_test.cc +++ b/flow/testing/diff_context_test.cc @@ -17,11 +17,12 @@ Damage DiffContextTest::DiffLayerTree(MockLayerTree& layer_tree, const MockLayerTree& old_layer_tree, const SkIRect& additional_damage, int horizontal_clip_alignment, - int vertical_clip_alignment) { + int vertical_clip_alignment, + bool use_raster_cache) { FML_CHECK(layer_tree.size() == old_layer_tree.size()); DiffContext dc(layer_tree.size(), 1, layer_tree.paint_region_map(), - old_layer_tree.paint_region_map()); + old_layer_tree.paint_region_map(), use_raster_cache); dc.PushCullRect( SkRect::MakeIWH(layer_tree.size().width(), layer_tree.size().height())); layer_tree.root()->Diff(&dc, old_layer_tree.root()); diff --git a/flow/testing/diff_context_test.h b/flow/testing/diff_context_test.h index 246e54817e251..0401b3232028d 100644 --- a/flow/testing/diff_context_test.h +++ b/flow/testing/diff_context_test.h @@ -39,7 +39,8 @@ class DiffContextTest : public ThreadTest { const MockLayerTree& old_layer_tree, const SkIRect& additional_damage = SkIRect::MakeEmpty(), int horizontal_clip_alignment = 0, - int vertical_alignment = 0); + int vertical_alignment = 0, + bool use_raster_cache = true); // Create display list consisting of filled rect with given color; Being able // to specify different color is useful to test deep comparison of pictures