Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit fc0858b

Browse files
authored
fix the bounds of nested save layers with a mix of clips and transforms (#31440)
1 parent ab46186 commit fc0858b

File tree

3 files changed

+211
-121
lines changed

3 files changed

+211
-121
lines changed

display_list/display_list_unittests.cc

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ static constexpr SkIRect TestLatticeSrcRect = {1, 1, 39, 39};
184184

185185
static sk_sp<SkPicture> MakeTestPicture(int w, int h, SkColor color) {
186186
SkPictureRecorder recorder;
187-
SkCanvas* cv = recorder.beginRecording(TestBounds);
187+
SkRTreeFactory rtree_factory;
188+
SkCanvas* cv = recorder.beginRecording(TestBounds, &rtree_factory);
188189
SkPaint paint;
189190
paint.setColor(color);
190191
paint.setStyle(SkPaint::kFill_Style);
@@ -998,7 +999,8 @@ TEST(DisplayList, DisplayListSaveLayerBoundsWithAlphaFilter) {
998999
// that modifies alpha channels (save layer bounds are meaningless
9991000
// under those circumstances)
10001001
SkPictureRecorder recorder;
1001-
SkCanvas* canvas = recorder.beginRecording(build_bounds);
1002+
SkRTreeFactory rtree_factory;
1003+
SkCanvas* canvas = recorder.beginRecording(build_bounds, &rtree_factory);
10021004
SkPaint p1;
10031005
p1.setColorFilter(alpha_color_filter);
10041006
canvas->saveLayer(save_bounds, &p1);
@@ -1586,5 +1588,128 @@ TEST(DisplayList, SaveLayerBoundsSnapshotsImageFilter) {
15861588
EXPECT_EQ(bounds, SkRect::MakeLTRB(50, 50, 100, 100));
15871589
}
15881590

1591+
TEST(DisplayList, FlutterSvgIssue661BoundsWereEmpty) {
1592+
// See https://github.com/dnfield/flutter_svg/issues/661
1593+
1594+
SkPath path1;
1595+
path1.setFillType(SkPathFillType::kWinding);
1596+
path1.moveTo(25.54f, 37.52f);
1597+
path1.cubicTo(20.91f, 37.52f, 16.54f, 33.39f, 13.62f, 30.58f);
1598+
path1.lineTo(13, 30);
1599+
path1.lineTo(12.45f, 29.42f);
1600+
path1.cubicTo(8.39f, 25.15f, 1.61f, 18, 8.37f, 11.27f);
1601+
path1.cubicTo(10.18f, 9.46f, 12.37f, 9.58f, 14.49f, 11.58f);
1602+
path1.cubicTo(15.67f, 12.71f, 17.05f, 14.69f, 17.07f, 16.58f);
1603+
path1.cubicTo(17.0968f, 17.458f, 16.7603f, 18.3081f, 16.14f, 18.93f);
1604+
path1.cubicTo(15.8168f, 19.239f, 15.4653f, 19.5169f, 15.09f, 19.76f);
1605+
path1.cubicTo(14.27f, 20.33f, 14.21f, 20.44f, 14.27f, 20.62f);
1606+
path1.cubicTo(15.1672f, 22.3493f, 16.3239f, 23.9309f, 17.7f, 25.31f);
1607+
path1.cubicTo(19.0791f, 26.6861f, 20.6607f, 27.8428f, 22.39f, 28.74f);
1608+
path1.cubicTo(22.57f, 28.8f, 22.69f, 28.74f, 23.25f, 27.92f);
1609+
path1.cubicTo(23.5f, 27.566f, 23.778f, 27.231f, 24.08f, 26.92f);
1610+
path1.cubicTo(24.7045f, 26.3048f, 25.5538f, 25.9723f, 26.43f, 26);
1611+
path1.cubicTo(28.29f, 26, 30.27f, 27.4f, 31.43f, 28.58f);
1612+
path1.cubicTo(33.43f, 30.67f, 33.55f, 32.9f, 31.74f, 34.7f);
1613+
path1.cubicTo(30.1477f, 36.4508f, 27.906f, 37.4704f, 25.54f, 37.52f);
1614+
path1.close();
1615+
path1.moveTo(11.17f, 12.23f);
1616+
path1.cubicTo(10.6946f, 12.2571f, 10.2522f, 12.4819f, 9.95f, 12.85f);
1617+
path1.cubicTo(5.12f, 17.67f, 8.95f, 22.5f, 14.05f, 27.85f);
1618+
path1.lineTo(14.62f, 28.45f);
1619+
path1.lineTo(15.16f, 28.96f);
1620+
path1.cubicTo(20.52f, 34.06f, 25.35f, 37.89f, 30.16f, 33.06f);
1621+
path1.cubicTo(30.83f, 32.39f, 31.25f, 31.56f, 29.81f, 30.06f);
1622+
path1.cubicTo(28.9247f, 29.07f, 27.7359f, 28.4018f, 26.43f, 28.16f);
1623+
path1.cubicTo(26.1476f, 28.1284f, 25.8676f, 28.2367f, 25.68f, 28.45f);
1624+
path1.cubicTo(25.4633f, 28.6774f, 25.269f, 28.9252f, 25.1f, 29.19f);
1625+
path1.cubicTo(24.53f, 30.01f, 23.47f, 31.54f, 21.54f, 30.79f);
1626+
path1.lineTo(21.41f, 30.72f);
1627+
path1.cubicTo(19.4601f, 29.7156f, 17.6787f, 28.4133f, 16.13f, 26.86f);
1628+
path1.cubicTo(14.5748f, 25.3106f, 13.2693f, 23.5295f, 12.26f, 21.58f);
1629+
path1.lineTo(12.2f, 21.44f);
1630+
path1.cubicTo(11.45f, 19.51f, 12.97f, 18.44f, 13.8f, 17.88f);
1631+
path1.cubicTo(14.061f, 17.706f, 14.308f, 17.512f, 14.54f, 17.3f);
1632+
path1.cubicTo(14.7379f, 17.1067f, 14.8404f, 16.8359f, 14.82f, 16.56f);
1633+
path1.cubicTo(14.5978f, 15.268f, 13.9585f, 14.0843f, 13, 13.19f);
1634+
path1.cubicTo(12.5398f, 12.642f, 11.8824f, 12.2971f, 11.17f, 12.23f);
1635+
path1.lineTo(11.17f, 12.23f);
1636+
path1.close();
1637+
path1.moveTo(27, 19.34f);
1638+
path1.lineTo(24.74f, 19.34f);
1639+
path1.cubicTo(24.7319f, 18.758f, 24.262f, 18.2881f, 23.68f, 18.28f);
1640+
path1.lineTo(23.68f, 16.05f);
1641+
path1.lineTo(23.7f, 16.05f);
1642+
path1.cubicTo(25.5153f, 16.0582f, 26.9863f, 17.5248f, 27, 19.34f);
1643+
path1.lineTo(27, 19.34f);
1644+
path1.close();
1645+
path1.moveTo(32.3f, 19.34f);
1646+
path1.lineTo(30.07f, 19.34f);
1647+
path1.cubicTo(30.037f, 15.859f, 27.171f, 13.011f, 23.69f, 13);
1648+
path1.lineTo(23.69f, 10.72f);
1649+
path1.cubicTo(28.415f, 10.725f, 32.3f, 14.615f, 32.3f, 19.34f);
1650+
path1.close();
1651+
1652+
SkPath path2;
1653+
path2.setFillType(SkPathFillType::kWinding);
1654+
path2.moveTo(37.5f, 19.33f);
1655+
path2.lineTo(35.27f, 19.33f);
1656+
path2.cubicTo(35.265f, 12.979f, 30.041f, 7.755f, 23.69f, 7.75f);
1657+
path2.lineTo(23.69f, 5.52f);
1658+
path2.cubicTo(31.264f, 5.525f, 37.495f, 11.756f, 37.5f, 19.33f);
1659+
path2.close();
1660+
1661+
DisplayListBuilder builder;
1662+
{
1663+
builder.save();
1664+
builder.clipRect({0, 0, 100, 100}, SkClipOp::kIntersect, true);
1665+
{
1666+
builder.save();
1667+
builder.transform2DAffine(2.17391, 0, -2547.83, //
1668+
0, 2.04082, -500);
1669+
{
1670+
builder.save();
1671+
builder.clipRect({1172, 245, 1218, 294}, SkClipOp::kIntersect, true);
1672+
{
1673+
builder.saveLayer(nullptr, true);
1674+
{
1675+
builder.save();
1676+
builder.transform2DAffine(1.4375, 0, 1164.09, //
1677+
0, 1.53125, 236.548);
1678+
builder.setAntiAlias(1);
1679+
builder.setColor(0xffffffff);
1680+
builder.drawPath(path1);
1681+
builder.restore();
1682+
}
1683+
{
1684+
builder.save();
1685+
builder.transform2DAffine(1.4375, 0, 1164.09, //
1686+
0, 1.53125, 236.548);
1687+
builder.drawPath(path2);
1688+
builder.restore();
1689+
}
1690+
builder.restore();
1691+
}
1692+
builder.restore();
1693+
}
1694+
builder.restore();
1695+
}
1696+
builder.restore();
1697+
}
1698+
sk_sp<DisplayList> display_list = builder.Build();
1699+
// Prior to the fix, the bounds were empty.
1700+
EXPECT_FALSE(display_list->bounds().isEmpty());
1701+
// These are the expected bounds, but testing float values can be
1702+
// flaky wrt minor changes in the bounds calculations. If this
1703+
// line has to be revised too often as the DL implementation is
1704+
// improved and maintained, then we can eliminate this test and
1705+
// just rely on the "rounded out" bounds test that follows.
1706+
EXPECT_EQ(display_list->bounds(),
1707+
SkRect::MakeLTRB(0, 0.00189208984375, 99.9839630127, 100));
1708+
// This is the more practical result. The bounds are "almost" 0,0,100x100
1709+
EXPECT_EQ(display_list->bounds().roundOut(), SkIRect::MakeWH(100, 100));
1710+
EXPECT_EQ(display_list->op_count(), 19);
1711+
EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 304u);
1712+
}
1713+
15891714
} // namespace testing
15901715
} // namespace flutter

display_list/display_list_utils.cc

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ void ClipBoundsDispatchHelper::reset(const SkRect* cull_rect) {
247247
DisplayListBoundsCalculator::DisplayListBoundsCalculator(
248248
const SkRect* cull_rect)
249249
: ClipBoundsDispatchHelper(cull_rect) {
250-
layer_infos_.emplace_back(std::make_unique<RootLayerData>());
250+
layer_infos_.emplace_back(std::make_unique<LayerData>(nullptr));
251251
accumulator_ = layer_infos_.back()->layer_accumulator();
252252
}
253253
void DisplayListBoundsCalculator::setStrokeCap(SkPaint::Cap cap) {
@@ -294,24 +294,36 @@ void DisplayListBoundsCalculator::setMaskBlurFilter(SkBlurStyle style,
294294
void DisplayListBoundsCalculator::save() {
295295
SkMatrixDispatchHelper::save();
296296
ClipBoundsDispatchHelper::save();
297-
layer_infos_.emplace_back(std::make_unique<SaveData>(accumulator_));
297+
layer_infos_.emplace_back(std::make_unique<LayerData>(accumulator_));
298298
accumulator_ = layer_infos_.back()->layer_accumulator();
299299
}
300300
void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds,
301301
bool with_paint) {
302302
SkMatrixDispatchHelper::save();
303303
ClipBoundsDispatchHelper::save();
304+
304305
if (with_paint) {
305-
layer_infos_.emplace_back(std::make_unique<SaveLayerData>(
306-
accumulator_, image_filter_, paint_nops_on_transparency()));
306+
// The actual flood of the outer layer clip will occur after the
307+
// (eventual) corresponding restore is called, but rather than
308+
// remember this information in the LayerInfo until the restore
309+
// method is processed, we just mark the unbounded state up front.
310+
if (!paint_nops_on_transparency()) {
311+
// We will fill the clip of the outer layer when we restore
312+
AccumulateUnbounded();
313+
}
314+
315+
layer_infos_.emplace_back(
316+
std::make_unique<LayerData>(accumulator_, image_filter_));
307317
} else {
308318
layer_infos_.emplace_back(
309-
std::make_unique<SaveLayerData>(accumulator_, nullptr, true));
319+
std::make_unique<LayerData>(accumulator_, nullptr));
310320
}
321+
311322
accumulator_ = layer_infos_.back()->layer_accumulator();
312-
// Accumulate the layer in its own coordinate system and then
313-
// filter and transform its bounds on restore.
314-
SkMatrixDispatchHelper::reset();
323+
324+
// Even though Skia claims that the bounds are only a hint, they actually
325+
// use them as the temporary layer bounds during rendering the layer, so
326+
// we set them as if a clip operation were performed.
315327
if (bounds) {
316328
clipRect(*bounds, SkClipOp::kIntersect, false);
317329
}
@@ -320,24 +332,53 @@ void DisplayListBoundsCalculator::restore() {
320332
if (layer_infos_.size() > 1) {
321333
SkMatrixDispatchHelper::restore();
322334
ClipBoundsDispatchHelper::restore();
323-
accumulator_ = layer_infos_.back()->restore_accumulator();
324-
SkRect layer_bounds = layer_infos_.back()->layer_bounds();
325-
// Must read unbounded state after layer_bounds
326-
bool layer_unbounded = layer_infos_.back()->is_unbounded();
327-
layer_infos_.pop_back();
328335

329-
// We accumulate the bounds even if the layer was unbounded because
330-
// the unbounded state may be contained at a higher level, so we at
331-
// least accumulate our best estimate about what we have.
332-
if (!layer_bounds.isEmpty()) {
333-
// We do not use AccumulateOpBounds because the layer info already
334-
// applied all bounds modifications based on the attributes that
335-
// were in place when it was created. Modifying the bounds further
336-
// based on the current attributes would mix attribute states.
337-
// The bounds are still transformed and clipped by this method.
338-
AccumulateBounds(layer_bounds);
336+
// Remember a few pieces of information from the current layer info
337+
// for later processing.
338+
LayerData* layer_info = layer_infos_.back().get();
339+
BoundsAccumulator* outer_accumulator = layer_info->restore_accumulator();
340+
bool is_unbounded = layer_info->is_unbounded();
341+
342+
// Before we pop_back we will get the current layer bounds from the
343+
// current accumulator and adjust ot as required based on the filter.
344+
SkRect layer_bounds = accumulator_->bounds();
345+
sk_sp<SkImageFilter> filter = layer_info->filter();
346+
if (filter) {
347+
if (filter->canComputeFastBounds()) {
348+
SkIRect filter_bounds =
349+
filter->filterBounds(layer_bounds.roundOut(), matrix(),
350+
SkImageFilter::kForward_MapDirection);
351+
layer_bounds.set(filter_bounds);
352+
353+
// We could leave the clipping to the code below that will
354+
// finally accumulate the layer bounds, but the bounds do
355+
// not normally need clipping unless they were modified by
356+
// entering this filtering code path.
357+
if (has_clip() && !layer_bounds.intersect(clip_bounds())) {
358+
layer_bounds.setEmpty();
359+
}
360+
} else {
361+
// If the filter cannot compute bounds then it might take an
362+
// unbounded amount of space. This can sometimes happen if it
363+
// modifies transparent black which means its affect will not
364+
// be bounded by the transparent pixels outside of the layer
365+
// drawable.
366+
is_unbounded = true;
367+
}
339368
}
340-
if (layer_unbounded) {
369+
370+
// Restore the accumulator before popping the LayerInfo so that
371+
// it nevers points to an out of scope instance.
372+
accumulator_ = outer_accumulator;
373+
layer_infos_.pop_back();
374+
375+
// Finally accumulate the impact of the layer into the new scope.
376+
// Note that the bounds were already accumulated in device pixels
377+
// and clipped to any clips involved so we do not need to go
378+
// through any transforms or clips to accuulate them into this
379+
// layer.
380+
accumulator_->accumulate(layer_bounds);
381+
if (is_unbounded) {
341382
AccumulateUnbounded();
342383
}
343384
}

0 commit comments

Comments
 (0)