Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion display_list/display_list_matrix_clip_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class DisplayListMatrixClipTracker {
DisplayListMatrixClipTracker(const SkRect& cull_rect, const SkMatrix& matrix);
DisplayListMatrixClipTracker(const SkRect& cull_rect, const SkM44& matrix);

bool using_4x4_matrix() { return current_->is_4x4(); }
bool using_4x4_matrix() const { return current_->is_4x4(); }

SkM44 matrix_4x4() const { return current_->matrix_4x4(); }
SkMatrix matrix_3x3() const { return current_->matrix_3x3(); }
Expand Down
84 changes: 52 additions & 32 deletions flow/diff_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ DiffContext::DiffContext(SkISize frame_size,
PaintRegionMap& this_frame_paint_region_map,
const PaintRegionMap& last_frame_paint_region_map,
bool has_raster_cache)
: rects_(std::make_shared<std::vector<SkRect>>()),
: clip_tracker_(DisplayListMatrixClipTracker(kGiantRect, SkMatrix::I())),
rects_(std::make_shared<std::vector<SkRect>>()),
frame_size_(frame_size),
frame_device_pixel_ratio_(frame_device_pixel_ratio),
this_frame_paint_region_map_(this_frame_paint_region_map),
Expand All @@ -21,12 +22,18 @@ DiffContext::DiffContext(SkISize frame_size,

void DiffContext::BeginSubtree() {
state_stack_.push_back(state_);
state_.rect_index_ = rects_->size();

bool had_integral_transform = state_.integral_transform;
state_.rect_index = rects_->size();
state_.has_filter_bounds_adjustment = false;
state_.has_texture = false;
if (state_.transform_override) {
state_.transform = *state_.transform_override;
state_.transform_override = std::nullopt;
state_.integral_transform = false;

state_.clip_tracker_save_count = clip_tracker_.getSaveCount();
clip_tracker_.save();

if (had_integral_transform) {
MakeCurrentTransformIntegral();
}
}

Expand All @@ -35,23 +42,33 @@ void DiffContext::EndSubtree() {
if (state_.has_filter_bounds_adjustment) {
filter_bounds_adjustment_stack_.pop_back();
}
clip_tracker_.restoreToCount(state_.clip_tracker_save_count);
state_ = state_stack_.back();
state_stack_.pop_back();
}

DiffContext::State::State()
: dirty(false),
cull_rect(kGiantRect),
rect_index_(0),
rect_index(0),
integral_transform(false),
clip_tracker_save_count(0),
has_filter_bounds_adjustment(false),
has_texture(false) {}

void DiffContext::PushTransform(const SkMatrix& transform) {
state_.transform.preConcat(transform);
clip_tracker_.transform(transform);
}

void DiffContext::SetTransform(const SkMatrix& transform) {
state_.transform_override = transform;
void DiffContext::MakeCurrentTransformIntegral() {
// TODO(knopp): This is duplicated from LayerStack. Maybe should be part of
// clip tracker?
if (clip_tracker_.using_4x4_matrix()) {
clip_tracker_.setTransform(
RasterCacheUtil::GetIntegralTransCTM(clip_tracker_.matrix_4x4()));
} else {
clip_tracker_.setTransform(
RasterCacheUtil::GetIntegralTransCTM(clip_tracker_.matrix_3x3()));
}
}

void DiffContext::PushFilterBoundsAdjustment(
Expand Down Expand Up @@ -128,21 +145,23 @@ Damage DiffContext::ComputeDamage(const SkIRect& accumulated_buffer_damage,
return res;
}

SkRect DiffContext::MapRect(const SkRect& rect) {
SkRect mapped_rect(rect);
clip_tracker_.mapRect(&mapped_rect);
return mapped_rect;
}

bool DiffContext::PushCullRect(const SkRect& clip) {
SkRect cull_rect = state_.transform.mapRect(clip);
return state_.cull_rect.intersect(cull_rect);
clip_tracker_.clipRect(clip, SkClipOp::kIntersect, false);
return !clip_tracker_.device_cull_rect().isEmpty();
}

SkMatrix DiffContext::GetTransform3x3() const {
return clip_tracker_.matrix_3x3();
}

SkRect DiffContext::GetCullRect() const {
SkMatrix inverse_transform;
// Perspective projections don't produce rectangles that are useful for
// culling for some reason.
if (!state_.transform.hasPerspective() &&
state_.transform.invert(&inverse_transform)) {
return inverse_transform.mapRect(state_.cull_rect);
} else {
return kGiantRect;
}
return clip_tracker_.local_cull_rect();
}

void DiffContext::MarkSubtreeDirty(const PaintRegion& previous_paint_region) {
Expand All @@ -163,16 +182,17 @@ void DiffContext::AddLayerBounds(const SkRect& rect) {
// During painting we cull based on non-overriden transform and then
// override the transform right before paint. Do the same thing here to get
// identical paint rect.
auto transformed_rect =
ApplyFilterBoundsAdjustment(state_.transform.mapRect(rect));
if (transformed_rect.intersects(state_.cull_rect)) {
auto paint_rect = state_.transform_override
? ApplyFilterBoundsAdjustment(
state_.transform_override->mapRect(rect))
: transformed_rect;
rects_->push_back(paint_rect);
auto transformed_rect = ApplyFilterBoundsAdjustment(MapRect(rect));
if (transformed_rect.intersects(clip_tracker_.device_cull_rect())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should perhaps add clip_tracker_.device_content_culled to avoid having to dup the Rect with the accessor.

if (state_.integral_transform) {
clip_tracker_.save();
MakeCurrentTransformIntegral();
transformed_rect = ApplyFilterBoundsAdjustment(MapRect(rect));
clip_tracker_.restore();
}
rects_->push_back(transformed_rect);
if (IsSubtreeDirty()) {
AddDamage(paint_rect);
AddDamage(transformed_rect);
}
}
}
Expand Down Expand Up @@ -208,8 +228,8 @@ void DiffContext::AddReadbackRegion(const SkIRect& rect) {
PaintRegion DiffContext::CurrentSubtreeRegion() const {
bool has_readback = std::any_of(
readbacks_.begin(), readbacks_.end(),
[&](const Readback& r) { return r.position >= state_.rect_index_; });
return PaintRegion(rects_, state_.rect_index_, rects_->size(), has_readback,
[&](const Readback& r) { return r.position >= state_.rect_index; });
return PaintRegion(rects_, state_.rect_index, rects_->size(), has_readback,
state_.has_texture);
}

Expand Down
48 changes: 28 additions & 20 deletions flow/diff_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#include <map>
#include <optional>
#include <vector>
#include "display_list/display_list_matrix_clip_tracker.h"
#include "flutter/flow/paint_region.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkRect.h"
Expand Down Expand Up @@ -83,16 +83,16 @@ class DiffContext {
// subtree will have bounds adjusted by this function.
void PushFilterBoundsAdjustment(const FilterBoundsAdjustment& filter);

// Returns transform matrix for current subtree
const SkMatrix& GetTransform() const { return state_.transform; }
// Instruct DiffContext that current layer will paint with integral transform.
void WillPaintWithIntegralTransform() { state_.integral_transform = true; }

// Overrides transform matrix for current subtree
void SetTransform(const SkMatrix& transform);
// Returns current transform as SkMatrix.
SkMatrix GetTransform3x3() const;

// Return cull rect for current subtree (in local coordinates)
// Return cull rect for current subtree (in local coordinates).
SkRect GetCullRect() const;

// Sets the dirty flag on current subtree;
// Sets the dirty flag on current subtree.
//
// previous_paint_region, which should represent region of previous subtree
// at this level will be added to damage area.
Expand Down Expand Up @@ -196,24 +196,29 @@ class DiffContext {

Statistics& statistics() { return statistics_; }

SkRect MapRect(const SkRect& rect);

private:
struct State {
State();

bool dirty;
SkRect cull_rect; // in screen coordinates

// In order to replicate paint process closely, we need both the original
// transform, and the overriden transform (set for layers that need to paint
// on integer coordinates). The reason for this is that during paint the
// transform matrix is overriden only after layer passes the cull check
// first (with original transform). So to cull layer we use transform, but
// to get paint coordinates we use transform_override. Child layers are
// painted after transform override, so if set we use transform_override as
// base when diffing child layers.
SkMatrix transform;
std::optional<SkMatrix> transform_override;
size_t rect_index_;

size_t rect_index;

// In order to replicate paint process closely, DiffContext needs to take
// into account that some layers are painted with transform translation
// snapped to integral coordinates.
//
// It's not possible to simply snap the transform itself, because culling
// needs to happen with original (unsnapped) transform, just like it does
// during paint. This means the integral coordinates must be applied after
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting ugly. I've added some comments to the Preroll pixel-snapping issue about how this has been working and potentially suggesting that Preroll might eventually want to move to device space bounds to make this more deterministic.

Barring that, though, I realized that the bounds culling in Paint is using unsnapped bounds and that could potentially be a bug except that it looks like the bounds culling done by SkCanvas actually has a default outset(1.0) on it. That code is attempting to adjust for AA effects, not pixel snapping, but the end result is the same.

Would it perhaps be easier to just do an outset(1.0) on the final damage bounds rather than to complicate the Diff code to deal with the "slop" of pixel snapped vs unsnapped bounds calculations in Layers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One complication is that a pixel snapped bounds may involve more than a single pixel of slop if it runs through an ImageFilter. That is potentially an issue for the accuracy of the bounds calculated in Preroll as well...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think outsetting final bounds is going to be always enough. You can have situation where multiple nested layers (transform -> integral ctm -> transform -> integral ctm) will move the final offset more than 1pixel compared to what it would be without integral ctm.

Operation Offset With Integral CTM Offset Without Integral CTM
Initial Value 0.5 0.5
MakeIntegral 1.0 0.5 (noop)
Translate by -0.5 0.5 0.0
MakeIntegral 1.0 0.0 (noop)
Translate by -0.5 0.5 -0.5
MakeIntegral 1.0 -0.5 (noop)
Translate by -0.5 0.5 -1.0
MakeIntegral 1.0 -1.0 (noop)

You can see that integral / non integral paint offset can diverge by more than one pixel.

Current solution is bit hairy, but correct. I'd love to remove the workaround but I think that might need to wait until we actually cull layers integral CTM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or an outset per time it happens, but it can happen on a number of layers leading to bounds bloat.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the outset+1 in the quickReject is enough for Paint because it is only ever "1 pixel snap behind", but for Diff, it can have N nested pixel snaps...

// culling before painting the layer content (either the layer itself, or
// when starting subtree to paint layer children).
bool integral_transform;

// Used to restoring clip tracker when popping state.
int clip_tracker_save_count;

// Whether this subtree has filter bounds adjustment function. If so,
// it will need to be removed from stack when subtree is closed.
Expand All @@ -223,6 +228,9 @@ class DiffContext {
bool has_texture;
};

void MakeCurrentTransformIntegral();

DisplayListMatrixClipTracker clip_tracker_;
std::shared_ptr<std::vector<SkRect>> rects_;
State state_;
SkISize frame_size_;
Expand Down
4 changes: 2 additions & 2 deletions flow/layers/backdrop_filter_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ void BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
context->AddLayerBounds(paint_bounds);

if (filter_) {
context->GetTransform().mapRect(&paint_bounds);
paint_bounds = context->MapRect(paint_bounds);
auto filter_target_bounds = paint_bounds.roundOut();
SkIRect filter_input_bounds; // in screen coordinates
filter_->get_input_device_bounds(
filter_target_bounds, context->GetTransform(), filter_input_bounds);
filter_target_bounds, context->GetTransform3x3(), filter_input_bounds);
context->AddReadbackRegion(filter_input_bounds);
}

Expand Down
1 change: 1 addition & 0 deletions flow/layers/backdrop_filter_layer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ TEST_F(BackdropLayerDiffTest, BackdropLayer) {
auto path1 = SkPath().addRect(SkRect::MakeLTRB(180, 180, 190, 190));
l4.root()->Add(std::make_shared<MockLayer>(path1));
damage = DiffLayerTree(l4, l3);

EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(180, 180, 190, 190));

MockLayerTree l5;
Expand Down
3 changes: 1 addition & 2 deletions flow/layers/clip_shape_layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class ClipShapeLayer : public CacheableContainerLayer {
}
}
if (UsesSaveLayer() && context->has_raster_cache()) {
context->SetTransform(
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
context->WillPaintWithIntegralTransform();
}
if (context->PushCullRect(clip_shape_bounds())) {
DiffChildren(context, prev);
Expand Down
3 changes: 1 addition & 2 deletions flow/layers/color_filter_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ void ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
}

if (context->has_raster_cache()) {
context->SetTransform(
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
context->WillPaintWithIntegralTransform();
}

DiffChildren(context, prev);
Expand Down
3 changes: 1 addition & 2 deletions flow/layers/display_list_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ void DisplayListLayer::Diff(DiffContext* context, const Layer* old_layer) {
}
context->PushTransform(SkMatrix::Translate(offset_.x(), offset_.y()));
if (context->has_raster_cache()) {
context->SetTransform(
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
context->WillPaintWithIntegralTransform();
}
context->AddLayerBounds(display_list()->bounds());
context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());
Expand Down
5 changes: 2 additions & 3 deletions flow/layers/image_filter_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ void ImageFilterLayer::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()));
context->WillPaintWithIntegralTransform();
}

if (filter_) {
auto filter = filter_->makeWithLocalMatrix(context->GetTransform());
auto filter = filter_->makeWithLocalMatrix(context->GetTransform3x3());
if (filter) {
// This transform will be applied to every child rect in the subtree
context->PushFilterBoundsAdjustment([filter](SkRect rect) {
Expand Down
3 changes: 1 addition & 2 deletions flow/layers/opacity_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ 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()));
context->WillPaintWithIntegralTransform();
}
DiffChildren(context, prev);
context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());
Expand Down
2 changes: 1 addition & 1 deletion flow/layers/physical_shape_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void PhysicalShapeLayer::Diff(DiffContext* context, const Layer* old_layer) {
} else {
bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
path_, elevation_, context->frame_device_pixel_ratio(),
context->GetTransform());
context->GetTransform3x3());
}

context->AddLayerBounds(bounds);
Expand Down
3 changes: 1 addition & 2 deletions flow/layers/shader_mask_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) {
}
}
if (context->has_raster_cache()) {
context->SetTransform(
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
context->WillPaintWithIntegralTransform();
}
DiffChildren(context, prev);

Expand Down