Remove physical model layer (flutter/engine#41593)
Removes the physical model layer and associated engine code. This was already deprecated and removed in the framework. By removing it in the engine, we can also remove the need for layer tree diff/paint/preroll to have the device pixel ratio. This will simplify some of the multi-view work Fixes https://github.com/flutter/flutter/issues/125720
This commit is contained in:
@@ -67,7 +67,6 @@
|
||||
../../../flutter/flow/layers/offscreen_surface_unittests.cc
|
||||
../../../flutter/flow/layers/opacity_layer_unittests.cc
|
||||
../../../flutter/flow/layers/performance_overlay_layer_unittests.cc
|
||||
../../../flutter/flow/layers/physical_shape_layer_unittests.cc
|
||||
../../../flutter/flow/layers/platform_view_layer_unittests.cc
|
||||
../../../flutter/flow/layers/shader_mask_layer_unittests.cc
|
||||
../../../flutter/flow/layers/texture_layer_unittests.cc
|
||||
|
||||
@@ -819,8 +819,6 @@ ORIGIN: ../../../flutter/flow/layers/opacity_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/opacity_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/performance_overlay_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/performance_overlay_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/physical_shape_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/physical_shape_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/platform_view_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/platform_view_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/shader_mask_layer.cc + ../../../flutter/LICENSE
|
||||
@@ -3415,8 +3413,6 @@ FILE: ../../../flutter/flow/layers/opacity_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/opacity_layer.h
|
||||
FILE: ../../../flutter/flow/layers/performance_overlay_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/performance_overlay_layer.h
|
||||
FILE: ../../../flutter/flow/layers/physical_shape_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/physical_shape_layer.h
|
||||
FILE: ../../../flutter/flow/layers/platform_view_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/platform_view_layer.h
|
||||
FILE: ../../../flutter/flow/layers/shader_mask_layer.cc
|
||||
|
||||
@@ -56,8 +56,6 @@ source_set("flow") {
|
||||
"layers/opacity_layer.h",
|
||||
"layers/performance_overlay_layer.cc",
|
||||
"layers/performance_overlay_layer.h",
|
||||
"layers/physical_shape_layer.cc",
|
||||
"layers/physical_shape_layer.h",
|
||||
"layers/platform_view_layer.cc",
|
||||
"layers/platform_view_layer.h",
|
||||
"layers/shader_mask_layer.cc",
|
||||
@@ -162,7 +160,6 @@ if (enable_unittests) {
|
||||
"layers/offscreen_surface_unittests.cc",
|
||||
"layers/opacity_layer_unittests.cc",
|
||||
"layers/performance_overlay_layer_unittests.cc",
|
||||
"layers/physical_shape_layer_unittests.cc",
|
||||
"layers/platform_view_layer_unittests.cc",
|
||||
"layers/shader_mask_layer_unittests.cc",
|
||||
"layers/texture_layer_unittests.cc",
|
||||
|
||||
@@ -16,9 +16,7 @@ std::optional<SkRect> FrameDamage::ComputeClipRect(
|
||||
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(),
|
||||
DiffContext context(layer_tree.frame_size(), layer_tree.paint_region_map(),
|
||||
prev_layer_tree_ ? prev_layer_tree_->paint_region_map()
|
||||
: empty_paint_region_map,
|
||||
has_raster_cache);
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
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,
|
||||
bool has_raster_cache)
|
||||
: 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),
|
||||
last_frame_paint_region_map_(last_frame_paint_region_map),
|
||||
has_raster_cache_(has_raster_cache) {}
|
||||
|
||||
@@ -43,7 +43,6 @@ using PaintRegionMap = std::map<uint64_t, PaintRegion>;
|
||||
class DiffContext {
|
||||
public:
|
||||
explicit DiffContext(SkISize frame_size,
|
||||
double device_pixel_aspect_ratio,
|
||||
PaintRegionMap& this_frame_paint_region_map,
|
||||
const PaintRegionMap& last_frame_paint_region_map,
|
||||
bool has_raster_cache);
|
||||
@@ -141,8 +140,6 @@ class DiffContext {
|
||||
int horizontal_clip_alignment = 0,
|
||||
int vertical_clip_alignment = 0) const;
|
||||
|
||||
double frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; };
|
||||
|
||||
// Adds the region to current damage. Used for removed layers, where instead
|
||||
// of diffing the layer its paint region is direcly added to damage.
|
||||
void AddDamage(const PaintRegion& damage);
|
||||
@@ -234,7 +231,6 @@ class DiffContext {
|
||||
std::shared_ptr<std::vector<SkRect>> rects_;
|
||||
State state_;
|
||||
SkISize frame_size_;
|
||||
double frame_device_pixel_ratio_;
|
||||
std::vector<State> state_stack_;
|
||||
std::vector<FilterBoundsAdjustment> filter_bounds_adjustment_stack_;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "flutter/flow/layers/clip_path_layer.h"
|
||||
#include "flutter/flow/layers/clip_rect_layer.h"
|
||||
#include "flutter/flow/layers/clip_rrect_layer.h"
|
||||
#include "flutter/flow/layers/physical_shape_layer.h"
|
||||
#include "flutter/flow/testing/layer_test.h"
|
||||
#include "flutter/flow/testing/mock_layer.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
@@ -221,72 +220,6 @@ TEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerCheckBoard) {
|
||||
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
|
||||
}
|
||||
|
||||
TEST_F(CheckerBoardLayerTest, PhysicalSaveLayerCheckBoard) {
|
||||
constexpr float initial_elevation = 20.0f;
|
||||
const SkRect paint_bounds = SkRect::MakeXYWH(0, 0, 8, 8);
|
||||
SkPath layer_path =
|
||||
SkPath().addRect(paint_bounds).addOval(paint_bounds.makeInset(0.1, 0.1));
|
||||
auto layer = std::make_shared<PhysicalShapeLayer>(
|
||||
SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path,
|
||||
Clip::antiAliasWithSaveLayer);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
|
||||
// their shadows , so we do not do any painting there.
|
||||
EXPECT_EQ(layer->paint_bounds(),
|
||||
DlCanvas::ComputeShadowBounds(layer_path, initial_elevation, 1.0f,
|
||||
SkMatrix()));
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
EXPECT_EQ(layer->elevation(), initial_elevation);
|
||||
|
||||
const DlPaint clip_paint;
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(SK_ColorGREEN);
|
||||
layer_paint.setAntiAlias(true);
|
||||
layer->Paint(paint_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector(
|
||||
{MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevation, false, 1}},
|
||||
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::ClipPathData{layer_path, ClipOp::kIntersect,
|
||||
MockCanvas::kSoft_ClipEdgeStyle}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::SaveLayerData{layer->paint_bounds(), clip_paint,
|
||||
nullptr, 2}},
|
||||
MockCanvas::DrawCall{2, MockCanvas::DrawPaintData{layer_paint}},
|
||||
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
|
||||
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
|
||||
|
||||
mock_canvas().reset_draw_calls();
|
||||
|
||||
layer->Paint(checkerboard_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector(
|
||||
{MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevation, false, 1}},
|
||||
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::ClipPathData{layer_path, ClipOp::kIntersect,
|
||||
MockCanvas::kSoft_ClipEdgeStyle}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::SaveLayerData{layer->paint_bounds(), clip_paint,
|
||||
nullptr, 2}},
|
||||
MockCanvas::DrawCall{2, MockCanvas::DrawPaintData{layer_paint}},
|
||||
// start DrawCheckerboard calls
|
||||
MockCanvas::DrawCall{2,
|
||||
MockCanvas::DrawRectData{layer->paint_bounds(),
|
||||
checkerboard_paint()}},
|
||||
// end DrawCheckerboard calls
|
||||
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
|
||||
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@@ -63,7 +63,6 @@ struct PrerollContext {
|
||||
const Stopwatch& raster_time;
|
||||
const Stopwatch& ui_time;
|
||||
std::shared_ptr<TextureRegistry> texture_registry;
|
||||
const float frame_device_pixel_ratio = 1.0f;
|
||||
|
||||
// These allow us to track properties like elevation, opacity, and the
|
||||
// presence of a platform view during Preroll.
|
||||
@@ -114,7 +113,6 @@ struct PaintContext {
|
||||
const Stopwatch& ui_time;
|
||||
std::shared_ptr<TextureRegistry> texture_registry;
|
||||
const RasterCache* raster_cache;
|
||||
const float frame_device_pixel_ratio = 1.0f;
|
||||
|
||||
// Snapshot store to collect leaf layer snapshots. The store is non-null
|
||||
// only when leaf layer tracing is enabled.
|
||||
|
||||
@@ -118,7 +118,6 @@ bool Rasterize(RasterCacheItem::CacheState cache_state,
|
||||
.ui_time = paint_context.ui_time,
|
||||
.texture_registry = paint_context.texture_registry,
|
||||
.raster_cache = paint_context.raster_cache,
|
||||
.frame_device_pixel_ratio = paint_context.frame_device_pixel_ratio,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
|
||||
.raster_time = frame.context().raster_time(),
|
||||
.ui_time = frame.context().ui_time(),
|
||||
.texture_registry = frame.context().texture_registry(),
|
||||
.frame_device_pixel_ratio = device_pixel_ratio_,
|
||||
.raster_cached_entries = &raster_cache_items_,
|
||||
.display_list_enabled = frame.display_list_builder() != nullptr,
|
||||
// clang-format on
|
||||
@@ -141,7 +140,6 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame,
|
||||
.ui_time = frame.context().ui_time(),
|
||||
.texture_registry = frame.context().texture_registry(),
|
||||
.raster_cache = cache,
|
||||
.frame_device_pixel_ratio = device_pixel_ratio_,
|
||||
.layer_snapshot_store = snapshot_store,
|
||||
.enable_leaf_layer_tracing = enable_leaf_layer_tracing_,
|
||||
.aiks_context = frame.aiks_context(),
|
||||
@@ -182,7 +180,6 @@ sk_sp<DisplayList> LayerTree::Flatten(
|
||||
.raster_time = unused_stopwatch,
|
||||
.ui_time = unused_stopwatch,
|
||||
.texture_registry = texture_registry,
|
||||
.frame_device_pixel_ratio = device_pixel_ratio_
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -199,7 +196,6 @@ sk_sp<DisplayList> LayerTree::Flatten(
|
||||
.ui_time = unused_stopwatch,
|
||||
.texture_registry = texture_registry,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = device_pixel_ratio_,
|
||||
.layer_snapshot_store = nullptr,
|
||||
.enable_leaf_layer_tracing = false,
|
||||
// clang-format on
|
||||
|
||||
@@ -216,7 +216,6 @@ TEST_F(LayerTreeTest, PrerollContextInitialization) {
|
||||
EXPECT_EQ(&context.raster_time, &mock_raster_time);
|
||||
EXPECT_EQ(&context.ui_time, &mock_ui_time);
|
||||
EXPECT_EQ(context.texture_registry.get(), mock_registry.get());
|
||||
EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f);
|
||||
|
||||
EXPECT_EQ(context.has_platform_view, false);
|
||||
EXPECT_EQ(context.has_texture_layer, false);
|
||||
@@ -252,7 +251,6 @@ TEST_F(LayerTreeTest, PaintContextInitialization) {
|
||||
EXPECT_EQ(context.texture_registry.get(), mock_registry.get());
|
||||
EXPECT_EQ(context.raster_cache, nullptr);
|
||||
EXPECT_EQ(context.state_stack.checkerboard_func(), nullptr);
|
||||
EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f);
|
||||
|
||||
EXPECT_EQ(context.enable_leaf_layer_tracing, false);
|
||||
EXPECT_EQ(context.layer_snapshot_store, nullptr);
|
||||
|
||||
@@ -74,7 +74,6 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) {
|
||||
.ui_time = mock_stopwatch,
|
||||
.texture_registry = nullptr,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/flow/layers/physical_shape_layer.h"
|
||||
|
||||
#include "flutter/flow/paint_utils.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
PhysicalShapeLayer::PhysicalShapeLayer(DlColor color,
|
||||
DlColor shadow_color,
|
||||
float elevation,
|
||||
const SkPath& path,
|
||||
Clip clip_behavior)
|
||||
: color_(color),
|
||||
shadow_color_(shadow_color),
|
||||
elevation_(elevation),
|
||||
path_(path),
|
||||
clip_behavior_(clip_behavior) {}
|
||||
|
||||
void PhysicalShapeLayer::Diff(DiffContext* context, const Layer* old_layer) {
|
||||
DiffContext::AutoSubtreeRestore subtree(context);
|
||||
auto* prev = static_cast<const PhysicalShapeLayer*>(old_layer);
|
||||
if (!context->IsSubtreeDirty()) {
|
||||
FML_DCHECK(prev);
|
||||
if (color_ != prev->color_ || shadow_color_ != prev->shadow_color_ ||
|
||||
elevation_ != prev->elevation() || path_ != prev->path_ ||
|
||||
clip_behavior_ != prev->clip_behavior_) {
|
||||
context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));
|
||||
}
|
||||
}
|
||||
|
||||
SkRect bounds;
|
||||
if (elevation_ == 0) {
|
||||
bounds = path_.getBounds();
|
||||
} else {
|
||||
bounds = DlCanvas::ComputeShadowBounds(path_, elevation_,
|
||||
context->frame_device_pixel_ratio(),
|
||||
context->GetTransform3x3());
|
||||
}
|
||||
|
||||
context->AddLayerBounds(bounds);
|
||||
|
||||
// Only push cull rect if there is clip.
|
||||
if (clip_behavior_ == Clip::none || context->PushCullRect(bounds)) {
|
||||
DiffChildren(context, prev);
|
||||
}
|
||||
context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());
|
||||
}
|
||||
|
||||
void PhysicalShapeLayer::Preroll(PrerollContext* context) {
|
||||
Layer::AutoPrerollSaveLayerState save =
|
||||
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
|
||||
|
||||
SkRect child_paint_bounds = SkRect::MakeEmpty();
|
||||
PrerollChildren(context, &child_paint_bounds);
|
||||
context->renderable_state_flags =
|
||||
UsesSaveLayer() ? Layer::kSaveLayerRenderFlags : 0;
|
||||
|
||||
SkRect paint_bounds;
|
||||
if (elevation_ == 0) {
|
||||
paint_bounds = path_.getBounds();
|
||||
} else {
|
||||
// We will draw the shadow in Paint(), so add some margin to the paint
|
||||
// bounds to leave space for the shadow.
|
||||
paint_bounds = DlCanvas::ComputeShadowBounds(
|
||||
path_, elevation_, context->frame_device_pixel_ratio,
|
||||
context->state_stack.transform_3x3());
|
||||
}
|
||||
|
||||
if (clip_behavior_ == Clip::none) {
|
||||
paint_bounds.join(child_paint_bounds);
|
||||
}
|
||||
|
||||
set_paint_bounds(paint_bounds);
|
||||
}
|
||||
|
||||
void PhysicalShapeLayer::Paint(PaintContext& context) const {
|
||||
FML_DCHECK(needs_painting(context));
|
||||
|
||||
if (elevation_ != 0) {
|
||||
context.canvas->DrawShadow(path_, shadow_color_, elevation_,
|
||||
SkColorGetA(color_) != 0xff,
|
||||
context.frame_device_pixel_ratio);
|
||||
}
|
||||
|
||||
// Call drawPath without clip if possible for better performance.
|
||||
DlPaint paint;
|
||||
paint.setColor(color_);
|
||||
paint.setAntiAlias(true);
|
||||
if (clip_behavior_ != Clip::antiAliasWithSaveLayer) {
|
||||
context.canvas->DrawPath(path_, paint);
|
||||
}
|
||||
|
||||
auto mutator = context.state_stack.save();
|
||||
switch (clip_behavior_) {
|
||||
case Clip::hardEdge:
|
||||
mutator.clipPath(path_, false);
|
||||
break;
|
||||
case Clip::antiAlias:
|
||||
mutator.clipPath(path_, true);
|
||||
break;
|
||||
case Clip::antiAliasWithSaveLayer: {
|
||||
TRACE_EVENT0("flutter", "Canvas::saveLayer");
|
||||
mutator.clipPath(path_, true);
|
||||
mutator.saveLayer(paint_bounds());
|
||||
} break;
|
||||
case Clip::none:
|
||||
break;
|
||||
}
|
||||
|
||||
if (UsesSaveLayer()) {
|
||||
// If we want to avoid the bleeding edge artifact
|
||||
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
|
||||
// using saveLayer, we have to call drawPaint instead of drawPath as
|
||||
// anti-aliased drawPath will always have such artifacts.
|
||||
context.canvas->DrawPaint(paint);
|
||||
}
|
||||
|
||||
PaintChildren(context);
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_
|
||||
#define FLUTTER_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_
|
||||
|
||||
#include "flutter/flow/layers/container_layer.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class PhysicalShapeLayer : public ContainerLayer {
|
||||
public:
|
||||
PhysicalShapeLayer(DlColor color,
|
||||
DlColor shadow_color,
|
||||
float elevation,
|
||||
const SkPath& path,
|
||||
Clip clip_behavior);
|
||||
|
||||
void Diff(DiffContext* context, const Layer* old_layer) override;
|
||||
|
||||
void Preroll(PrerollContext* context) override;
|
||||
|
||||
void Paint(PaintContext& context) const override;
|
||||
|
||||
bool UsesSaveLayer() const {
|
||||
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
|
||||
}
|
||||
|
||||
float elevation() const { return elevation_; }
|
||||
|
||||
private:
|
||||
DlColor color_;
|
||||
DlColor shadow_color_;
|
||||
float elevation_ = 0.0f;
|
||||
SkPath path_;
|
||||
Clip clip_behavior_;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_
|
||||
@@ -1,509 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/flow/layers/physical_shape_layer.h"
|
||||
|
||||
#include "flutter/flow/testing/diff_context_test.h"
|
||||
#include "flutter/flow/testing/layer_test.h"
|
||||
#include "flutter/flow/testing/mock_layer.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/testing/mock_canvas.h"
|
||||
|
||||
#include "third_party/skia/include/core/SkSurface.h"
|
||||
|
||||
namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
using PhysicalShapeLayerTest = LayerTest;
|
||||
|
||||
using ClipOp = DlCanvas::ClipOp;
|
||||
|
||||
#ifndef NDEBUG
|
||||
TEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) {
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kBlack(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
SkPath(), Clip::none);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty());
|
||||
EXPECT_FALSE(layer->needs_painting(paint_context()));
|
||||
|
||||
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
|
||||
"needs_painting\\(context\\)");
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, PaintBeforePrerollDies) {
|
||||
SkPath child_path;
|
||||
child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
|
||||
auto mock_layer = std::make_shared<MockLayer>(child_path, DlPaint());
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kBlack(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
SkPath(), Clip::none);
|
||||
layer->Add(mock_layer);
|
||||
|
||||
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
|
||||
"needs_painting\\(context\\)");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, NonEmptyLayer) {
|
||||
SkPath layer_path;
|
||||
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::none);
|
||||
layer->Preroll(preroll_context());
|
||||
EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), SkRect::MakeEmpty());
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(DlColor::kGreen());
|
||||
layer_paint.setAntiAlias(true);
|
||||
layer->Paint(paint_context());
|
||||
EXPECT_EQ(mock_canvas().draw_calls(),
|
||||
std::vector({MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathClip) {
|
||||
const SkRect layer_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
|
||||
const SkRect child_bounds1 = SkRect::MakeLTRB(4, 0, 12, 12);
|
||||
const SkRect child_bounds2 = SkRect::MakeLTRB(3, 2, 5, 15);
|
||||
SkPath layer_path =
|
||||
SkPath().addRect(layer_bounds).addOval(layer_bounds.makeInset(0.1, 0.1));
|
||||
SkPath child1_path = SkPath()
|
||||
.addRect(child_bounds1)
|
||||
.addOval(child_bounds1.makeInset(0.1, 0.1));
|
||||
SkPath child2_path = SkPath()
|
||||
.addRect(child_bounds2)
|
||||
.addOval(child_bounds2.makeInset(0.1, 0.1));
|
||||
auto child1 =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kRed(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
child1_path, Clip::none);
|
||||
auto child2 =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kBlue(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
child2_path, Clip::none);
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::hardEdge);
|
||||
layer->Add(child1);
|
||||
layer->Add(child2);
|
||||
|
||||
SkRect child_paint_bounds = SkRect::MakeEmpty();
|
||||
layer->Preroll(preroll_context());
|
||||
child_paint_bounds.join(child1->paint_bounds());
|
||||
child_paint_bounds.join(child2->paint_bounds());
|
||||
EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds());
|
||||
EXPECT_NE(layer->paint_bounds(), child_paint_bounds);
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_paint_bounds);
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(DlColor::kGreen());
|
||||
layer_paint.setAntiAlias(true);
|
||||
DlPaint child1_paint;
|
||||
child1_paint.setColor(DlColor::kRed());
|
||||
child1_paint.setAntiAlias(true);
|
||||
DlPaint child2_paint;
|
||||
child2_paint.setColor(DlColor::kBlue());
|
||||
child2_paint.setAntiAlias(true);
|
||||
layer->Paint(paint_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector({
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
|
||||
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::ClipPathData{layer_path, ClipOp::kIntersect}},
|
||||
MockCanvas::DrawCall{
|
||||
1, MockCanvas::DrawPathData{child1_path, child1_paint}},
|
||||
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},
|
||||
}));
|
||||
DisplayListBuilder expected_builder;
|
||||
{ // layer::Paint()
|
||||
expected_builder.DrawPath(layer_path,
|
||||
DlPaint(DlColor::kGreen()).setAntiAlias(true));
|
||||
expected_builder.Save();
|
||||
{
|
||||
expected_builder.ClipPath(layer_path, ClipOp::kIntersect, false);
|
||||
{ // child1::Paint()
|
||||
expected_builder.DrawPath(child1_path,
|
||||
DlPaint(DlColor::kRed()).setAntiAlias(true));
|
||||
}
|
||||
// child2::Paint() is not called due to layer cullling
|
||||
// This is the expected and intended behavior.
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
layer->Paint(display_list_paint_context());
|
||||
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathNoClip) {
|
||||
SkPath layer_path;
|
||||
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
|
||||
SkPath child1_path;
|
||||
child1_path.addRect(4, 0, 12, 12).close();
|
||||
SkPath child2_path;
|
||||
child2_path.addRect(3, 2, 5, 15).close();
|
||||
auto child1 =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kRed(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
child1_path, Clip::none);
|
||||
auto child2 =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kBlue(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
child2_path, Clip::none);
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::none);
|
||||
layer->Add(child1);
|
||||
layer->Add(child2);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
SkRect child_bounds = child1->paint_bounds();
|
||||
child_bounds.join(child2->paint_bounds());
|
||||
SkRect total_bounds = child_bounds;
|
||||
total_bounds.join(layer_path.getBounds());
|
||||
EXPECT_NE(layer->paint_bounds(), layer_path.getBounds());
|
||||
EXPECT_EQ(layer->paint_bounds(), total_bounds);
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(DlColor::kGreen());
|
||||
layer_paint.setAntiAlias(true);
|
||||
DlPaint child1_paint;
|
||||
child1_paint.setColor(DlColor::kRed());
|
||||
child1_paint.setAntiAlias(true);
|
||||
DlPaint child2_paint;
|
||||
child2_paint.setColor(DlColor::kBlue());
|
||||
child2_paint.setAntiAlias(true);
|
||||
layer->Paint(paint_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector({MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{child1_path, child1_paint}},
|
||||
MockCanvas::DrawCall{0, MockCanvas::DrawPathData{
|
||||
child2_path, child2_paint}}}));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ElevationSimple) {
|
||||
constexpr float initial_elevation = 20.0f;
|
||||
SkPath layer_path;
|
||||
layer_path.addRect(0, 0, 8, 8).close();
|
||||
auto layer = std::make_shared<PhysicalShapeLayer>(
|
||||
DlColor::kGreen(), DlColor::kBlack(), initial_elevation, layer_path,
|
||||
Clip::none);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
|
||||
// their shadows , so we do not do any painting there.
|
||||
EXPECT_EQ(layer->paint_bounds(),
|
||||
DlCanvas::ComputeShadowBounds(layer_path, initial_elevation, 1.0f,
|
||||
SkMatrix()));
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
EXPECT_EQ(layer->elevation(), initial_elevation);
|
||||
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(DlColor::kGreen());
|
||||
layer_paint.setAntiAlias(true);
|
||||
layer->Paint(paint_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector(
|
||||
{MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevation, false, 1}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ElevationComplex) {
|
||||
// The layer tree should look like this:
|
||||
// layers[0] +1.0f = 1.0f
|
||||
// | \
|
||||
// | \
|
||||
// | \
|
||||
// | layers[2] +3.0f = 4.0f
|
||||
// | |
|
||||
// | layers[3] +4.0f = 8.0f
|
||||
// |
|
||||
// |
|
||||
// layers[1] + 2.0f = 3.0f
|
||||
constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
SkPath layer_path;
|
||||
layer_path.addRect(0, 0, 80, 80).close();
|
||||
|
||||
std::shared_ptr<PhysicalShapeLayer> layers[4];
|
||||
for (int i = 0; i < 4; i += 1) {
|
||||
layers[i] = std::make_shared<PhysicalShapeLayer>(
|
||||
DlColor::kBlack(), DlColor::kBlack(), initial_elevations[i], layer_path,
|
||||
Clip::none);
|
||||
}
|
||||
layers[0]->Add(layers[1]);
|
||||
layers[0]->Add(layers[2]);
|
||||
layers[2]->Add(layers[3]);
|
||||
|
||||
layers[0]->Preroll(preroll_context());
|
||||
for (int i = 0; i < 4; i += 1) {
|
||||
// On Fuchsia, the system compositor handles all elevated
|
||||
// PhysicalShapeLayers and their shadows , so we do not do any painting
|
||||
// there.
|
||||
SkRect paint_bounds = DlCanvas::ComputeShadowBounds(
|
||||
layer_path, initial_elevations[i], 1.0f /* pixel_ratio */, SkMatrix());
|
||||
|
||||
// Without clipping the children will be painted as well
|
||||
for (auto layer : layers[i]->layers()) {
|
||||
paint_bounds.join(layer->paint_bounds());
|
||||
}
|
||||
EXPECT_EQ(layers[i]->paint_bounds(), paint_bounds);
|
||||
EXPECT_TRUE(layers[i]->needs_painting(paint_context()));
|
||||
}
|
||||
|
||||
DlPaint layer_paint;
|
||||
layer_paint.setColor(DlColor::kBlack());
|
||||
layer_paint.setAntiAlias(true);
|
||||
layers[0]->Paint(paint_context());
|
||||
EXPECT_EQ(
|
||||
mock_canvas().draw_calls(),
|
||||
std::vector(
|
||||
{MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevations[0], false, 1}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevations[1], false, 1}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevations[2], false, 1}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawShadowData{layer_path, DlColor::kBlack(),
|
||||
initial_elevations[3], false, 1}},
|
||||
MockCanvas::DrawCall{
|
||||
0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ShadowNotDependsCtm) {
|
||||
constexpr SkScalar elevations[] = {1, 2, 3, 4, 5, 10};
|
||||
constexpr SkScalar scales[] = {0.5, 1, 1.5, 2, 3, 5};
|
||||
constexpr SkScalar translates[] = {0, 1, -1, 0.5, 2, 10};
|
||||
|
||||
SkPath path;
|
||||
path.addRect(0, 0, 8, 8).close();
|
||||
|
||||
for (SkScalar elevation : elevations) {
|
||||
SkRect baseline_bounds =
|
||||
DlCanvas::ComputeShadowBounds(path, elevation, 1.0f, SkMatrix());
|
||||
for (SkScalar scale : scales) {
|
||||
for (SkScalar translate_x : translates) {
|
||||
for (SkScalar translate_y : translates) {
|
||||
SkMatrix ctm;
|
||||
ctm.setScaleTranslate(scale, scale, translate_x, translate_y);
|
||||
SkRect bounds =
|
||||
DlCanvas::ComputeShadowBounds(path, elevation, scale, ctm);
|
||||
EXPECT_FLOAT_EQ(bounds.fLeft, baseline_bounds.fLeft);
|
||||
EXPECT_FLOAT_EQ(bounds.fTop, baseline_bounds.fTop);
|
||||
EXPECT_FLOAT_EQ(bounds.fRight, baseline_bounds.fRight);
|
||||
EXPECT_FLOAT_EQ(bounds.fBottom, baseline_bounds.fBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int RasterizedDifferenceInPixels(
|
||||
const std::function<void(SkCanvas*)>& actual_draw_function,
|
||||
const std::function<void(SkCanvas*)>& expected_draw_function,
|
||||
const SkSize& canvas_size) {
|
||||
sk_sp<SkSurface> actual_surface =
|
||||
SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());
|
||||
sk_sp<SkSurface> expected_surface =
|
||||
SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());
|
||||
|
||||
actual_surface->getCanvas()->drawColor(SK_ColorWHITE);
|
||||
expected_surface->getCanvas()->drawColor(SK_ColorWHITE);
|
||||
|
||||
actual_draw_function(actual_surface->getCanvas());
|
||||
expected_draw_function(expected_surface->getCanvas());
|
||||
|
||||
SkPixmap actual_pixels;
|
||||
EXPECT_TRUE(actual_surface->peekPixels(&actual_pixels));
|
||||
|
||||
SkPixmap expected_pixels;
|
||||
EXPECT_TRUE(expected_surface->peekPixels(&expected_pixels));
|
||||
|
||||
int different_pixels = 0;
|
||||
for (int y = 0; y < canvas_size.height(); y++) {
|
||||
const uint32_t* actual_row = actual_pixels.addr32(0, y);
|
||||
const uint32_t* expected_row = expected_pixels.addr32(0, y);
|
||||
for (int x = 0; x < canvas_size.width(); x++) {
|
||||
if (actual_row[x] != expected_row[x]) {
|
||||
different_pixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return different_pixels;
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, ShadowNotDependsPathSize) {
|
||||
constexpr SkRect test_cases[][2] = {
|
||||
{{20, -100, 80, 80}, {20, -1000, 80, 80}},
|
||||
{{20, 20, 80, 200}, {20, 20, 80, 2000}},
|
||||
};
|
||||
|
||||
for (const SkRect* test_case : test_cases) {
|
||||
EXPECT_EQ(RasterizedDifferenceInPixels(
|
||||
[=](SkCanvas* canvas) {
|
||||
SkPath path;
|
||||
path.addRect(test_case[0]).close();
|
||||
DlSkCanvasAdapter(canvas).DrawShadow(
|
||||
path, DlColor::kBlack(), 1.0f, false, 1.0f);
|
||||
},
|
||||
[=](SkCanvas* canvas) {
|
||||
SkPath path;
|
||||
path.addRect(test_case[1]).close();
|
||||
DlSkCanvasAdapter(canvas).DrawShadow(
|
||||
path, DlColor::kBlack(), 1.0f, false, 1.0f);
|
||||
},
|
||||
SkSize::Make(100, 100)),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ReadbackResult(PrerollContext* context,
|
||||
Clip clip_behavior,
|
||||
const std::shared_ptr<Layer>& child,
|
||||
bool before) {
|
||||
const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const SkPath layer_path = SkPath().addRect(layer_bounds);
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, clip_behavior);
|
||||
if (child != nullptr) {
|
||||
layer->Add(child);
|
||||
}
|
||||
context->surface_needs_readback = before;
|
||||
layer->Preroll(context);
|
||||
return context->surface_needs_readback;
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, Readback) {
|
||||
PrerollContext* context = preroll_context();
|
||||
SkPath path;
|
||||
DlPaint paint;
|
||||
|
||||
const Clip hard = Clip::hardEdge;
|
||||
const Clip soft = Clip::antiAlias;
|
||||
const Clip save_layer = Clip::antiAliasWithSaveLayer;
|
||||
|
||||
std::shared_ptr<MockLayer> nochild;
|
||||
auto reader = std::make_shared<MockLayer>(path, paint);
|
||||
reader->set_fake_reads_surface(true);
|
||||
auto nonreader = std::make_shared<MockLayer>(path, paint);
|
||||
|
||||
// No children, no prior readback -> no readback after
|
||||
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
|
||||
|
||||
// No children, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
|
||||
|
||||
// Non readback child, no prior readback -> no readback after
|
||||
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
|
||||
|
||||
// Non readback child, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
|
||||
|
||||
// Readback child, no prior readback -> readback after unless SaveLayer
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
|
||||
|
||||
// Readback child, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerTest, OpacityInheritance) {
|
||||
SkPath layer_path;
|
||||
layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::none);
|
||||
|
||||
PrerollContext* context = preroll_context();
|
||||
context->renderable_state_flags = 0;
|
||||
layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, 0);
|
||||
}
|
||||
|
||||
using PhysicalShapeLayerDiffTest = DiffContextTest;
|
||||
|
||||
TEST_F(PhysicalShapeLayerDiffTest, NoClipPaintRegion) {
|
||||
MockLayerTree tree1;
|
||||
const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::none);
|
||||
|
||||
const SkPath layer_path2 =
|
||||
SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));
|
||||
auto layer2 = std::make_shared<MockLayer>(layer_path2);
|
||||
layer->Add(layer2);
|
||||
tree1.root()->Add(layer);
|
||||
|
||||
auto damage = DiffLayerTree(tree1, MockLayerTree());
|
||||
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 400, 400));
|
||||
}
|
||||
|
||||
TEST_F(PhysicalShapeLayerDiffTest, ClipPaintRegion) {
|
||||
MockLayerTree tree1;
|
||||
const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));
|
||||
auto layer =
|
||||
std::make_shared<PhysicalShapeLayer>(DlColor::kGreen(), DlColor::kBlack(),
|
||||
0.0f, // elevation
|
||||
layer_path, Clip::hardEdge);
|
||||
|
||||
const SkPath layer_path2 =
|
||||
SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));
|
||||
auto layer2 = std::make_shared<MockLayer>(layer_path2);
|
||||
layer->Add(layer2);
|
||||
tree1.root()->Add(layer);
|
||||
|
||||
auto damage = DiffLayerTree(tree1, MockLayerTree());
|
||||
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 100, 100));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
@@ -20,7 +20,7 @@ Damage DiffContextTest::DiffLayerTree(MockLayerTree& layer_tree,
|
||||
bool use_raster_cache) {
|
||||
FML_CHECK(layer_tree.size() == old_layer_tree.size());
|
||||
|
||||
DiffContext dc(layer_tree.size(), 1, layer_tree.paint_region_map(),
|
||||
DiffContext dc(layer_tree.size(), 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()));
|
||||
|
||||
@@ -56,7 +56,6 @@ class LayerTestBase : public CanvasTestBase<BaseT> {
|
||||
.raster_time = raster_time_,
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
.has_platform_view = false,
|
||||
.raster_cached_entries = &cacheable_items_,
|
||||
// clang-format on
|
||||
@@ -71,7 +70,6 @@ class LayerTestBase : public CanvasTestBase<BaseT> {
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
},
|
||||
display_list_builder_(kDlBounds),
|
||||
@@ -85,7 +83,6 @@ class LayerTestBase : public CanvasTestBase<BaseT> {
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
},
|
||||
checkerboard_context_{
|
||||
@@ -98,7 +95,6 @@ class LayerTestBase : public CanvasTestBase<BaseT> {
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
} {
|
||||
use_null_raster_cache();
|
||||
|
||||
@@ -100,7 +100,6 @@ PrerollContextHolder GetSamplePrerollContextHolder(
|
||||
.raster_time = *raster_time,
|
||||
.ui_time = *ui_time,
|
||||
.texture_registry = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
.has_platform_view = false,
|
||||
.has_texture_layer = false,
|
||||
.raster_cached_entries = &raster_cache_items_,
|
||||
@@ -128,7 +127,6 @@ PaintContextHolder GetSamplePaintContextHolder(
|
||||
.ui_time = *ui_time,
|
||||
.texture_registry = nullptr,
|
||||
.raster_cache = raster_cache,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
},
|
||||
// clang-format on
|
||||
srgb};
|
||||
|
||||
@@ -85,7 +85,6 @@ class MockRasterCache : public RasterCache {
|
||||
.raster_time = raster_time_,
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
.has_platform_view = false,
|
||||
.has_texture_layer = false,
|
||||
.raster_cached_entries = &raster_cache_items_
|
||||
@@ -103,7 +102,6 @@ class MockRasterCache : public RasterCache {
|
||||
.ui_time = ui_time_,
|
||||
.texture_registry = texture_registry_,
|
||||
.raster_cache = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
};
|
||||
};
|
||||
|
||||
@@ -212,15 +212,6 @@ class ShaderMaskEngineLayer extends _EngineLayerWrapper {
|
||||
ShaderMaskEngineLayer._(super.nativeLayer) : super._();
|
||||
}
|
||||
|
||||
/// An opaque handle to a physical shape engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushPhysicalShape].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class PhysicalShapeEngineLayer extends _EngineLayerWrapper {
|
||||
PhysicalShapeEngineLayer._(super.nativeLayer) : super._();
|
||||
}
|
||||
|
||||
/// Builds a [Scene] containing the given visuals.
|
||||
///
|
||||
/// A [Scene] can then be rendered using [FlutterView.render].
|
||||
@@ -604,53 +595,6 @@ class SceneBuilder extends NativeFieldWrapperClass1 {
|
||||
int filterQualityIndex,
|
||||
EngineLayer? oldLayer);
|
||||
|
||||
/// Pushes a physical layer operation for an arbitrary shape onto the
|
||||
/// operation stack.
|
||||
///
|
||||
/// By default, the layer's content will not be clipped (clip = [Clip.none]).
|
||||
/// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer],
|
||||
/// then the content is clipped to the given shape defined by [path].
|
||||
///
|
||||
/// If [elevation] is greater than 0.0, then a shadow is drawn around the layer.
|
||||
/// [shadowColor] defines the color of the shadow if present and [color] defines the
|
||||
/// color of the layer background.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
@Deprecated(
|
||||
'Use a clip and canvas operations directly (See RenderPhysicalModel). '
|
||||
'This feature was deprecated after v3.1.0-0.0.pre.',
|
||||
)
|
||||
PhysicalShapeEngineLayer pushPhysicalShape({
|
||||
required Path path,
|
||||
required double elevation,
|
||||
required Color color,
|
||||
Color? shadowColor,
|
||||
Clip clipBehavior = Clip.none,
|
||||
PhysicalShapeEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushPhysicalShape'));
|
||||
final EngineLayer engineLayer = EngineLayer._();
|
||||
_pushPhysicalShape(engineLayer, path, elevation, color.value, shadowColor?.value ?? 0xFF000000,
|
||||
clipBehavior.index, oldLayer?._nativeLayer);
|
||||
final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(engineLayer);
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
|
||||
@Native<Void Function(Pointer<Void>, Handle, Pointer<Void>, Double, Int32, Int32, Int32, Handle)>(symbol: 'SceneBuilder::pushPhysicalShape')
|
||||
external void _pushPhysicalShape(
|
||||
EngineLayer outEngineLayer,
|
||||
Path path,
|
||||
double elevation,
|
||||
int color,
|
||||
int shadowColor,
|
||||
int clipBehavior,
|
||||
EngineLayer? oldLayer);
|
||||
|
||||
/// Ends the effect of the most recently pushed operation.
|
||||
///
|
||||
/// Internally the scene builder maintains a stack of operations. Each of the
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "flutter/flow/layers/layer_tree.h"
|
||||
#include "flutter/flow/layers/opacity_layer.h"
|
||||
#include "flutter/flow/layers/performance_overlay_layer.h"
|
||||
#include "flutter/flow/layers/physical_shape_layer.h"
|
||||
#include "flutter/flow/layers/platform_view_layer.h"
|
||||
#include "flutter/flow/layers/shader_mask_layer.h"
|
||||
#include "flutter/flow/layers/texture_layer.h"
|
||||
@@ -203,25 +202,6 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle,
|
||||
}
|
||||
}
|
||||
|
||||
void SceneBuilder::pushPhysicalShape(Dart_Handle layer_handle,
|
||||
const CanvasPath* path,
|
||||
double elevation,
|
||||
int color,
|
||||
int shadow_color,
|
||||
int clipBehavior,
|
||||
const fml::RefPtr<EngineLayer>& oldLayer) {
|
||||
auto layer = std::make_shared<flutter::PhysicalShapeLayer>(
|
||||
static_cast<DlColor>(color), static_cast<DlColor>(shadow_color),
|
||||
static_cast<float>(elevation), path->path(),
|
||||
static_cast<flutter::Clip>(clipBehavior));
|
||||
PushLayer(layer);
|
||||
EngineLayer::MakeRetained(layer_handle, layer);
|
||||
|
||||
if (oldLayer && oldLayer->Layer()) {
|
||||
layer->AssignOldLayer(oldLayer->Layer().get());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneBuilder::addRetained(const fml::RefPtr<EngineLayer>& retainedLayer) {
|
||||
AddLayer(retainedLayer->Layer());
|
||||
}
|
||||
|
||||
@@ -90,13 +90,6 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
|
||||
int blendMode,
|
||||
int filterQualityIndex,
|
||||
const fml::RefPtr<EngineLayer>& oldLayer);
|
||||
void pushPhysicalShape(Dart_Handle layer_handle,
|
||||
const CanvasPath* path,
|
||||
double elevation,
|
||||
int color,
|
||||
int shadowColor,
|
||||
int clipBehavior,
|
||||
const fml::RefPtr<EngineLayer>& oldLayer);
|
||||
|
||||
void addRetained(const fml::RefPtr<EngineLayer>& retainedLayer);
|
||||
|
||||
|
||||
@@ -286,7 +286,6 @@ typedef CanvasPath Path;
|
||||
V(SceneBuilder, pushImageFilter, 4) \
|
||||
V(SceneBuilder, pushOffset, 5) \
|
||||
V(SceneBuilder, pushOpacity, 6) \
|
||||
V(SceneBuilder, pushPhysicalShape, 8) \
|
||||
V(SceneBuilder, pushShaderMask, 10) \
|
||||
V(SceneBuilder, pushTransformHandle, 4) \
|
||||
V(SceneBuilder, setCheckerboardOffscreenLayers, 2) \
|
||||
|
||||
@@ -30,8 +30,6 @@ abstract class BackdropFilterEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class ShaderMaskEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class PhysicalShapeEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class SceneBuilder {
|
||||
factory SceneBuilder() =>
|
||||
engine.renderer.createSceneBuilder();
|
||||
@@ -86,18 +84,6 @@ abstract class SceneBuilder {
|
||||
ShaderMaskEngineLayer? oldLayer,
|
||||
FilterQuality filterQuality = FilterQuality.low,
|
||||
});
|
||||
@Deprecated(
|
||||
'Use a clip and canvas operations directly (See RenderPhysicalModel). '
|
||||
'This feature was deprecated after v3.1.0-0.0.pre.',
|
||||
)
|
||||
PhysicalShapeEngineLayer pushPhysicalShape({
|
||||
required Path path,
|
||||
required double elevation,
|
||||
required Color color,
|
||||
Color? shadowColor,
|
||||
Clip clipBehavior = Clip.none,
|
||||
PhysicalShapeEngineLayer? oldLayer,
|
||||
});
|
||||
void addRetained(EngineLayer retainedLayer);
|
||||
void pop();
|
||||
void addPerformanceOverlay(int enabledOptions, Rect bounds);
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'painting.dart';
|
||||
import 'path.dart';
|
||||
import 'picture.dart';
|
||||
import 'raster_cache.dart';
|
||||
import 'util.dart';
|
||||
|
||||
/// A layer to be composed into a scene.
|
||||
///
|
||||
@@ -482,84 +481,6 @@ class PictureLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer representing a physical shape.
|
||||
///
|
||||
/// The shape clips its children to a given [Path], and casts a shadow based
|
||||
/// on the given elevation.
|
||||
class PhysicalShapeEngineLayer extends ContainerLayer
|
||||
implements ui.PhysicalShapeEngineLayer {
|
||||
PhysicalShapeEngineLayer(
|
||||
this._elevation,
|
||||
this._color,
|
||||
this._shadowColor,
|
||||
this._path,
|
||||
this._clipBehavior,
|
||||
);
|
||||
|
||||
final double _elevation;
|
||||
final ui.Color _color;
|
||||
final ui.Color? _shadowColor; // ignore: use_late_for_private_fields_and_variables
|
||||
final CkPath _path;
|
||||
final ui.Clip _clipBehavior;
|
||||
|
||||
@override
|
||||
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
|
||||
prerollChildren(prerollContext, matrix);
|
||||
paintBounds = computeSkShadowBounds(
|
||||
_path, _elevation, ui.window.devicePixelRatio, matrix);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintContext paintContext) {
|
||||
assert(needsPainting);
|
||||
|
||||
if (_elevation != 0) {
|
||||
drawShadow(paintContext.leafNodesCanvas!, _path, _shadowColor!,
|
||||
_elevation, _color.alpha != 0xff);
|
||||
}
|
||||
|
||||
final CkPaint paint = CkPaint()..color = _color;
|
||||
if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) {
|
||||
paintContext.leafNodesCanvas!.drawPath(_path, paint);
|
||||
}
|
||||
|
||||
final int saveCount = paintContext.internalNodesCanvas.save();
|
||||
switch (_clipBehavior) {
|
||||
case ui.Clip.hardEdge:
|
||||
paintContext.internalNodesCanvas.clipPath(_path, false);
|
||||
case ui.Clip.antiAlias:
|
||||
paintContext.internalNodesCanvas.clipPath(_path, true);
|
||||
case ui.Clip.antiAliasWithSaveLayer:
|
||||
paintContext.internalNodesCanvas.clipPath(_path, true);
|
||||
paintContext.internalNodesCanvas.saveLayer(paintBounds, null);
|
||||
case ui.Clip.none:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
|
||||
// If we want to avoid the bleeding edge artifact
|
||||
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
|
||||
// using saveLayer, we have to call drawPaint instead of drawPath as
|
||||
// anti-aliased drawPath will always have such artifacts.
|
||||
paintContext.leafNodesCanvas!.drawPaint(paint);
|
||||
}
|
||||
paint.dispose();
|
||||
|
||||
paintChildren(paintContext);
|
||||
|
||||
paintContext.internalNodesCanvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
/// Draws a shadow on the given [canvas] for the given [path].
|
||||
///
|
||||
/// The blur of the shadow is decided by the [elevation], and the
|
||||
/// shadow is painted with the given [color].
|
||||
static void drawShadow(CkCanvas canvas, CkPath path, ui.Color color,
|
||||
double elevation, bool transparentOccluder) {
|
||||
canvas.drawShadow(path, color, elevation, transparentOccluder);
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer which contains a [ui.ColorFilter].
|
||||
class ColorFilterEngineLayer extends ContainerLayer
|
||||
implements ui.ColorFilterEngineLayer {
|
||||
|
||||
@@ -177,24 +177,6 @@ class LayerSceneBuilder implements ui.SceneBuilder {
|
||||
return pushLayer<OpacityEngineLayer>(OpacityEngineLayer(alpha, offset));
|
||||
}
|
||||
|
||||
@override
|
||||
PhysicalShapeEngineLayer pushPhysicalShape({
|
||||
required ui.Path path,
|
||||
required double elevation,
|
||||
required ui.Color color,
|
||||
ui.Color? shadowColor,
|
||||
ui.Clip clipBehavior = ui.Clip.none,
|
||||
ui.EngineLayer? oldLayer,
|
||||
}) {
|
||||
return pushLayer<PhysicalShapeEngineLayer>(PhysicalShapeEngineLayer(
|
||||
elevation,
|
||||
color,
|
||||
shadowColor,
|
||||
path as CkPath,
|
||||
clipBehavior,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
ShaderMaskEngineLayer pushShaderMask(
|
||||
ui.Shader shader,
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../dom.dart';
|
||||
import '../shadow.dart';
|
||||
import '../svg.dart';
|
||||
import '../util.dart';
|
||||
import 'dom_canvas.dart';
|
||||
import 'painting.dart';
|
||||
import 'path/path.dart';
|
||||
import 'path_to_svg_clip.dart';
|
||||
import 'surface.dart';
|
||||
import 'surface_stats.dart';
|
||||
@@ -190,264 +186,6 @@ class PersistedClipRRect extends PersistedContainerSurface
|
||||
bool get isClipping => true;
|
||||
}
|
||||
|
||||
class PersistedPhysicalShape extends PersistedContainerSurface
|
||||
with _DomClip
|
||||
implements ui.PhysicalShapeEngineLayer {
|
||||
PersistedPhysicalShape(PersistedPhysicalShape? super.oldLayer, this.path,
|
||||
this.elevation, int color, int shadowColor, this.clipBehavior)
|
||||
: color = ui.Color(color),
|
||||
shadowColor = ui.Color(shadowColor),
|
||||
pathBounds = path.getBounds();
|
||||
|
||||
final SurfacePath path;
|
||||
final ui.Rect pathBounds;
|
||||
final double elevation;
|
||||
final ui.Color color;
|
||||
final ui.Color shadowColor;
|
||||
final ui.Clip clipBehavior;
|
||||
DomElement? _clipElement;
|
||||
DomElement? _svgElement;
|
||||
|
||||
@override
|
||||
void recomputeTransformAndClip() {
|
||||
transform = parent!.transform;
|
||||
|
||||
if (clipBehavior != ui.Clip.none) {
|
||||
final ui.RRect? roundRect = path.toRoundedRect();
|
||||
if (roundRect != null) {
|
||||
localClipBounds = roundRect.outerRect;
|
||||
} else {
|
||||
final ui.Rect? rect = path.toRect();
|
||||
if (rect != null) {
|
||||
localClipBounds = rect;
|
||||
} else {
|
||||
localClipBounds = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
localClipBounds = null;
|
||||
}
|
||||
projectedClip = null;
|
||||
}
|
||||
|
||||
void _applyColor() {
|
||||
rootElement!.style.backgroundColor = color.toCssString();
|
||||
}
|
||||
|
||||
@override
|
||||
DomElement createElement() {
|
||||
return super.createElement()..setAttribute('clip-type', 'physical-shape');
|
||||
}
|
||||
|
||||
@override
|
||||
void discard() {
|
||||
super.discard();
|
||||
_clipElement?.remove();
|
||||
_clipElement = null;
|
||||
_svgElement?.remove();
|
||||
_svgElement = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
_applyShape();
|
||||
}
|
||||
|
||||
void _applyShape() {
|
||||
_applyColor();
|
||||
// Handle special case of round rect physical shape mapping to
|
||||
// rounded div.
|
||||
final ui.RRect? roundRect = path.toRoundedRect();
|
||||
if (roundRect != null) {
|
||||
final String borderRadius =
|
||||
'${roundRect.tlRadiusX}px ${roundRect.trRadiusX}px '
|
||||
'${roundRect.brRadiusX}px ${roundRect.blRadiusX}px';
|
||||
final DomCSSStyleDeclaration style = rootElement!.style;
|
||||
style
|
||||
..left = '${roundRect.left}px'
|
||||
..top = '${roundRect.top}px'
|
||||
..width = '${roundRect.width}px'
|
||||
..height = '${roundRect.height}px'
|
||||
..borderRadius = borderRadius;
|
||||
childContainer!.style
|
||||
..left = '${-roundRect.left}px'
|
||||
..top = '${-roundRect.top}px';
|
||||
if (clipBehavior != ui.Clip.none) {
|
||||
style.overflow = 'hidden';
|
||||
}
|
||||
applyCssShadow(rootElement, pathBounds, elevation, shadowColor);
|
||||
return;
|
||||
} else {
|
||||
final ui.Rect? rect = path.toRect();
|
||||
if (rect != null) {
|
||||
final DomCSSStyleDeclaration style = rootElement!.style;
|
||||
style
|
||||
..left = '${rect.left}px'
|
||||
..top = '${rect.top}px'
|
||||
..width = '${rect.width}px'
|
||||
..height = '${rect.height}px'
|
||||
..borderRadius = '';
|
||||
childContainer!.style
|
||||
..left = '${-rect.left}px'
|
||||
..top = '${-rect.top}px';
|
||||
if (clipBehavior != ui.Clip.none) {
|
||||
style.overflow = 'hidden';
|
||||
}
|
||||
applyCssShadow(rootElement, pathBounds, elevation, shadowColor);
|
||||
return;
|
||||
} else {
|
||||
final ui.Rect? ovalRect = path.toCircle();
|
||||
if (ovalRect != null) {
|
||||
final double rx = ovalRect.width / 2.0;
|
||||
final double ry = ovalRect.height / 2.0;
|
||||
final String borderRadius =
|
||||
rx == ry ? '${rx}px ' : '${rx}px ${ry}px ';
|
||||
final DomCSSStyleDeclaration style = rootElement!.style;
|
||||
final double left = ovalRect.left;
|
||||
final double top = ovalRect.top;
|
||||
style
|
||||
..left = '${left}px'
|
||||
..top = '${top}px'
|
||||
..width = '${rx * 2}px'
|
||||
..height = '${ry * 2}px'
|
||||
..borderRadius = borderRadius;
|
||||
childContainer!.style
|
||||
..left = '${-left}px'
|
||||
..top = '${-top}px';
|
||||
if (clipBehavior != ui.Clip.none) {
|
||||
style.overflow = 'hidden';
|
||||
}
|
||||
applyCssShadow(rootElement, pathBounds, elevation, shadowColor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If code reaches this point, we have a path we want to clip against and
|
||||
/// potentially have a shadow due to material surface elevation.
|
||||
///
|
||||
/// When there is no shadow we can simply clip a div with a background
|
||||
/// color using a svg clip path.
|
||||
///
|
||||
/// Otherwise we need to paint svg element for the path and clip
|
||||
/// contents against same path for shadow to work since box-shadow doesn't
|
||||
/// take clip-path into account.
|
||||
///
|
||||
/// Webkit has a bug when applying clip-path on an element that has
|
||||
/// position: absolute and transform
|
||||
/// (https://bugs.webkit.org/show_bug.cgi?id=141731).
|
||||
/// To place clipping rectangle correctly
|
||||
/// we size the inner container to cover full pathBounds instead of sizing
|
||||
/// to clipping rect bounds (which is the case for elevation == 0.0 where
|
||||
/// we shift outer/inner clip area instead to position clip-path).
|
||||
final SVGSVGElement svgClipPath = elevation == 0.0
|
||||
? pathToSvgClipPath(path,
|
||||
offsetX: -pathBounds.left,
|
||||
offsetY: -pathBounds.top,
|
||||
scaleX: 1.0 / pathBounds.width,
|
||||
scaleY: 1.0 / pathBounds.height)
|
||||
: pathToSvgClipPath(path,
|
||||
scaleX: 1.0 / pathBounds.right,
|
||||
scaleY: 1.0 / pathBounds.bottom);
|
||||
|
||||
/// If apply is called multiple times (without update), remove prior
|
||||
/// svg clip and render elements.
|
||||
_clipElement?.remove();
|
||||
_svgElement?.remove();
|
||||
_clipElement = svgClipPath;
|
||||
rootElement!.append(_clipElement!);
|
||||
if (elevation == 0.0) {
|
||||
setClipPath(rootElement!, createSvgClipUrl());
|
||||
final DomCSSStyleDeclaration rootElementStyle = rootElement!.style;
|
||||
rootElementStyle
|
||||
..overflow = ''
|
||||
..left = '${pathBounds.left}px'
|
||||
..top = '${pathBounds.top}px'
|
||||
..width = '${pathBounds.width}px'
|
||||
..height = '${pathBounds.height}px'
|
||||
..borderRadius = '';
|
||||
childContainer!.style
|
||||
..left = '-${pathBounds.left}px'
|
||||
..top = '-${pathBounds.top}px';
|
||||
return;
|
||||
}
|
||||
|
||||
setClipPath(childContainer!, createSvgClipUrl());
|
||||
final DomCSSStyleDeclaration rootElementStyle = rootElement!.style;
|
||||
rootElementStyle
|
||||
..overflow = ''
|
||||
..left = '${pathBounds.left}px'
|
||||
..top = '${pathBounds.top}px'
|
||||
..width = '${pathBounds.width}px'
|
||||
..height = '${pathBounds.height}px'
|
||||
..borderRadius = '';
|
||||
childContainer!.style
|
||||
..left = '-${pathBounds.left}px'
|
||||
..top = '-${pathBounds.top}px'
|
||||
..width = '${pathBounds.right}px'
|
||||
..height = '${pathBounds.bottom}px';
|
||||
|
||||
final ui.Rect pathBounds2 = path.getBounds();
|
||||
_svgElement = pathToSvgElement(
|
||||
path,
|
||||
SurfacePaintData()
|
||||
..style = ui.PaintingStyle.fill
|
||||
..color = color.value);
|
||||
|
||||
/// Render element behind the clipped content.
|
||||
rootElement!.insertBefore(_svgElement!, childContainer);
|
||||
|
||||
final SurfaceShadowData shadow = computeShadow(pathBounds, elevation)!;
|
||||
final ui.Color boxShadowColor = toShadowColor(shadowColor);
|
||||
_svgElement!.style
|
||||
..filter = 'drop-shadow(${shadow.offset.dx}px ${shadow.offset.dy}px '
|
||||
'${shadow.blurWidth}px '
|
||||
'rgba(${boxShadowColor.red}, ${boxShadowColor.green}, '
|
||||
'${boxShadowColor.blue}, ${boxShadowColor.alpha / 255}))'
|
||||
..transform = 'translate(-${pathBounds2.left}px, -${pathBounds2.top}px)';
|
||||
|
||||
rootElement!.style.backgroundColor = '';
|
||||
}
|
||||
|
||||
@override
|
||||
void update(PersistedPhysicalShape oldSurface) {
|
||||
super.update(oldSurface);
|
||||
final bool pathChanged = oldSurface.path != path;
|
||||
if (pathChanged) {
|
||||
localClipBounds = null;
|
||||
}
|
||||
if (pathChanged ||
|
||||
oldSurface.elevation != elevation ||
|
||||
oldSurface.shadowColor != shadowColor ||
|
||||
oldSurface.color != color) {
|
||||
oldSurface._clipElement?.remove();
|
||||
oldSurface._clipElement = null;
|
||||
oldSurface._svgElement?.remove();
|
||||
oldSurface._svgElement = null;
|
||||
_clipElement?.remove();
|
||||
_clipElement = null;
|
||||
_svgElement?.remove();
|
||||
_svgElement = null;
|
||||
// Reset style on prior element since we may have switched between
|
||||
// rect/rrect and arbitrary path.
|
||||
setClipPath(rootElement!, '');
|
||||
_applyShape();
|
||||
} else {
|
||||
// Reuse clipElement from prior surface.
|
||||
_clipElement = oldSurface._clipElement;
|
||||
if (_clipElement != null) {
|
||||
rootElement!.append(_clipElement!);
|
||||
}
|
||||
oldSurface._clipElement = null;
|
||||
_svgElement = oldSurface._svgElement;
|
||||
if (_svgElement != null) {
|
||||
rootElement!.insertBefore(_svgElement!, childContainer);
|
||||
}
|
||||
oldSurface._svgElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A surface that clips it's children.
|
||||
class PersistedClipPath extends PersistedContainerSurface
|
||||
implements ui.ClipPathEngineLayer {
|
||||
|
||||
@@ -19,7 +19,6 @@ import 'color_filter.dart';
|
||||
import 'image_filter.dart';
|
||||
import 'offset.dart';
|
||||
import 'opacity.dart';
|
||||
import 'path/path.dart';
|
||||
import 'path_to_svg_clip.dart';
|
||||
import 'picture.dart';
|
||||
import 'platform_view.dart';
|
||||
@@ -264,37 +263,6 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
shader, maskRect, blendMode, filterQuality));
|
||||
}
|
||||
|
||||
/// Pushes a physical layer operation for an arbitrary shape onto the
|
||||
/// operation stack.
|
||||
///
|
||||
/// By default, the layer's content will not be clipped (clip = [Clip.none]).
|
||||
/// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer],
|
||||
/// then the content is clipped to the given shape defined by [path].
|
||||
///
|
||||
/// If [elevation] is greater than 0.0, then a shadow is drawn around the layer.
|
||||
/// [shadowColor] defines the color of the shadow if present and [color] defines the
|
||||
/// color of the layer background.
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
@override
|
||||
ui.PhysicalShapeEngineLayer pushPhysicalShape({
|
||||
required ui.Path path,
|
||||
required double elevation,
|
||||
required ui.Color color,
|
||||
ui.Color? shadowColor,
|
||||
ui.Clip clipBehavior = ui.Clip.none,
|
||||
ui.PhysicalShapeEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedPhysicalShape>(PersistedPhysicalShape(
|
||||
oldLayer as PersistedPhysicalShape?,
|
||||
path as SurfacePath,
|
||||
elevation,
|
||||
color.value,
|
||||
shadowColor?.value ?? 0xFF000000,
|
||||
clipBehavior,
|
||||
));
|
||||
}
|
||||
|
||||
/// Add a retained engine layer subtree from previous frames.
|
||||
///
|
||||
/// All the engine layers that are in the subtree of the retained layer will
|
||||
|
||||
@@ -11,13 +11,13 @@ import 'dom.dart';
|
||||
|
||||
/// How far is the light source from the surface of the UI.
|
||||
///
|
||||
/// Must be kept in sync with `flow/layers/physical_shape_layer.cc`.
|
||||
/// Originally based on the constant in `flow/layers/physical_shape_layer.cc`.
|
||||
const double kLightHeight = 600.0;
|
||||
|
||||
/// The radius of the light source. The positive radius creates a penumbra in
|
||||
/// the shadow, which we express using a blur effect.
|
||||
///
|
||||
/// Must be kept in sync with `flow/layers/physical_shape_layer.cc`.
|
||||
/// Originally based on the constant in `flow/layers/physical_shape_layer.cc`.
|
||||
const double kLightRadius = 800.0;
|
||||
|
||||
/// The X offset of the list source relative to the center of the shape.
|
||||
|
||||
@@ -164,20 +164,6 @@ class SkwasmSceneBuilder implements ui.SceneBuilder {
|
||||
OpacityLayer(),
|
||||
OpacityOperation(alpha, offset),
|
||||
);
|
||||
|
||||
@override
|
||||
ui.PhysicalShapeEngineLayer pushPhysicalShape({
|
||||
required ui.Path path,
|
||||
required double elevation,
|
||||
required ui.Color color,
|
||||
ui.Color? shadowColor,
|
||||
ui.Clip clipBehavior = ui.Clip.none,
|
||||
ui.PhysicalShapeEngineLayer? oldLayer
|
||||
}) {
|
||||
// TODO(jacksongardner): implement pushPhysicalShape
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ShaderMaskEngineLayer pushShaderMask(
|
||||
ui.Shader shader,
|
||||
|
||||
@@ -54,141 +54,6 @@ void testMain() {
|
||||
);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/51237
|
||||
// Draws a grid of shadows at different offsets. Prior to directional
|
||||
// light the shadows would shift depending on the offset. With directional
|
||||
// light the cells in the grid must look identical.
|
||||
test('uses directional shadows', () async {
|
||||
const ui.Rect region = ui.Rect.fromLTRB(0, 0, 820, 420);
|
||||
final CkPicture picture = paintPicture(region, (CkCanvas canvas) {
|
||||
final CkPath shape = CkPath()
|
||||
..addRect(const ui.Rect.fromLTRB(0, 0, 40, 40));
|
||||
final CkPaint shapePaint = CkPaint()
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const ui.Color(0xFF009900);
|
||||
final CkPaint shadowBoundsPaint = CkPaint()
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const ui.Color(0xFF000099);
|
||||
canvas.translate(20, 20);
|
||||
|
||||
for (int row = 0; row < 5; row += 1) {
|
||||
canvas.save();
|
||||
for (int col = 0; col < 10; col += 1) {
|
||||
final double elevation = 2 * (col % 5).toDouble();
|
||||
canvas.drawShadow(shape, const ui.Color(0xFFFF0000), elevation, true);
|
||||
canvas.drawPath(shape, shapePaint);
|
||||
|
||||
final PhysicalShapeEngineLayer psl = PhysicalShapeEngineLayer(
|
||||
elevation,
|
||||
const ui.Color(0xFF000000),
|
||||
const ui.Color(0xFF000000),
|
||||
shape,
|
||||
ui.Clip.antiAlias,
|
||||
);
|
||||
psl.preroll(
|
||||
PrerollContext(
|
||||
RasterCache(),
|
||||
HtmlViewEmbedder.instance,
|
||||
),
|
||||
Matrix4.identity(),
|
||||
);
|
||||
canvas.drawRect(psl.paintBounds, shadowBoundsPaint);
|
||||
|
||||
final CkParagraph p = makeSimpleText('$elevation');
|
||||
p.layout(const ui.ParagraphConstraints(width: 1000));
|
||||
canvas.drawParagraph(
|
||||
p, ui.Offset(20 - p.maxIntrinsicWidth / 2, 20 - p.height / 2));
|
||||
canvas.translate(80, 0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0, 80);
|
||||
}
|
||||
});
|
||||
await matchPictureGolden('canvaskit_directional_shadows.png', picture,
|
||||
region: region);
|
||||
});
|
||||
|
||||
test('computes shadow bounds correctly with parent transforms', () async {
|
||||
const double rectSize = 50;
|
||||
const double halfSize = rectSize / 2;
|
||||
const double padding = 110;
|
||||
const ui.Rect region = ui.Rect.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
(rectSize + padding) * 3 + padding,
|
||||
(rectSize + padding) * 2 + padding,
|
||||
);
|
||||
late List<PhysicalShapeEngineLayer> physicalShapeLayers;
|
||||
|
||||
LayerTree buildTestScene({required bool paintShadowBounds}) {
|
||||
final Iterator<PhysicalShapeEngineLayer>? shadowBounds =
|
||||
paintShadowBounds ? physicalShapeLayers.iterator : null;
|
||||
physicalShapeLayers = <PhysicalShapeEngineLayer>[];
|
||||
|
||||
final LayerSceneBuilder builder = LayerSceneBuilder();
|
||||
builder.pushOffset(padding + halfSize, padding + halfSize);
|
||||
|
||||
final CkPath shape = CkPath()
|
||||
..addRect(
|
||||
const ui.Rect.fromLTRB(-halfSize, -halfSize, halfSize, halfSize));
|
||||
final CkPaint shadowBoundsPaint = CkPaint()
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = const ui.Color(0xFF000099);
|
||||
|
||||
for (int row = 0; row < 2; row += 1) {
|
||||
for (int col = 0; col < 3; col += 1) {
|
||||
builder.pushOffset(
|
||||
col * (rectSize + padding), row * (rectSize + padding));
|
||||
builder.pushTransform(Float64List.fromList(
|
||||
Matrix4.rotationZ(row * math.pi / 4).storage));
|
||||
final double scale = 1 / (1 + col);
|
||||
builder.pushTransform(Float64List.fromList(
|
||||
Matrix4.diagonal3Values(scale, scale, 1).storage));
|
||||
physicalShapeLayers.add(builder.pushPhysicalShape(
|
||||
path: shape,
|
||||
elevation: 6,
|
||||
color: const ui.Color(0xFF009900),
|
||||
shadowColor: const ui.Color(0xFF000000),
|
||||
));
|
||||
if (shadowBounds != null) {
|
||||
shadowBounds.moveNext();
|
||||
final ui.Rect bounds = shadowBounds.current.paintBounds;
|
||||
builder.addPicture(
|
||||
ui.Offset.zero,
|
||||
paintPicture(region, (CkCanvas canvas) {
|
||||
canvas.drawRect(bounds, shadowBoundsPaint);
|
||||
}));
|
||||
}
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
builder.pop();
|
||||
return builder.build().layerTree;
|
||||
}
|
||||
|
||||
// Render the scene once without painting the shadow bounds just to
|
||||
// preroll the scene to compute the shadow bounds.
|
||||
buildTestScene(paintShadowBounds: false).rootLayer.preroll(
|
||||
PrerollContext(
|
||||
RasterCache(),
|
||||
HtmlViewEmbedder.instance,
|
||||
),
|
||||
Matrix4.identity(),
|
||||
);
|
||||
|
||||
// Render again, this time with the shadow bounds.
|
||||
final LayerTree layerTree = buildTestScene(paintShadowBounds: true);
|
||||
|
||||
CanvasKitRenderer.instance.rasterizer.draw(layerTree);
|
||||
await matchGoldenFile('canvaskit_shadow_bounds.png', region: region);
|
||||
});
|
||||
|
||||
test('text styles - default', () async {
|
||||
await testTextStyle('default');
|
||||
});
|
||||
|
||||
@@ -93,22 +93,6 @@ void testMain() {
|
||||
return '''<s><o></o></s>''';
|
||||
});
|
||||
});
|
||||
|
||||
test('pushPhysicalShape implements surface lifecycle', () {
|
||||
testLayerLifeCycle((ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
final ui.Path path = ui.Path()..addRect(const ui.Rect.fromLTRB(10, 20, 30, 40));
|
||||
return sceneBuilder.pushPhysicalShape(
|
||||
path: path,
|
||||
elevation: 2,
|
||||
color: const ui.Color.fromRGBO(0, 0, 0, 1),
|
||||
shadowColor: const ui.Color.fromRGBO(0, 0, 0, 1),
|
||||
oldLayer: oldLayer as ui.PhysicalShapeEngineLayer?,
|
||||
);
|
||||
}, () {
|
||||
return '''<s><pshape><clip-i></clip-i></pshape></s>''';
|
||||
});
|
||||
});
|
||||
|
||||
test('pushBackdropFilter implements surface lifecycle', () {
|
||||
testLayerLifeCycle((ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
|
||||
return sceneBuilder.pushBackdropFilter(
|
||||
|
||||
@@ -362,22 +362,6 @@ void testMain() {
|
||||
expect(
|
||||
opacityLayer2.rootElement, element); // adopts old surface's element
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/60461
|
||||
//
|
||||
// During retained match many to many, build can be called on existing
|
||||
// PersistedPhysicalShape multiple times when not matched.
|
||||
test('Can call apply multiple times on existing PersistedPhysicalShape'
|
||||
'when using arbitrary path',
|
||||
() {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final Path path = Path();
|
||||
path.addPolygon(const <Offset>[Offset(50, 0), Offset(100, 80), Offset(20, 40)], true);
|
||||
final PersistedPhysicalShape shape = builder1.pushPhysicalShape(path: path,
|
||||
color: const Color(0xFF00FF00), elevation: 1) as PersistedPhysicalShape;
|
||||
builder1.build();
|
||||
expect(() => shape.apply(), returnsNormally);
|
||||
});
|
||||
});
|
||||
|
||||
final Map<String, TestEngineLayerFactory> layerFactories = <String, TestEngineLayerFactory>{
|
||||
@@ -406,11 +390,6 @@ void testMain() {
|
||||
);
|
||||
return builder.pushShaderMask(shader, shaderBounds, BlendMode.srcOver);
|
||||
},
|
||||
'PhysicalShapeEngineLayer': (SurfaceSceneBuilder builder) => builder.pushPhysicalShape(
|
||||
path: Path()..addRect(const Rect.fromLTRB(0, 0, 10, 10)),
|
||||
elevation: 3,
|
||||
color: const Color(0xAABBCCDD),
|
||||
),
|
||||
};
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/104305
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'dart:js_util' as js_util;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart' hide PhysicalShapeEngineLayer;
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
@@ -83,35 +83,6 @@ Future<void> testMain() async {
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
await matchGoldenFile('color_filter_mode.png', region: region);
|
||||
});
|
||||
|
||||
/// Regression test for https://github.com/flutter/flutter/issues/59451.
|
||||
///
|
||||
/// Picture with overlay blend inside a physical shape. Should show image
|
||||
/// at 0,0. In the filed issue it was leaving a gap on top.
|
||||
test('Should render image with color filter without gap', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Path path = Path();
|
||||
path.addRRect(RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(0, 0, 400, 400), const Radius.circular(2)));
|
||||
final PhysicalShapeEngineLayer oldLayer = builder.pushPhysicalShape(
|
||||
path: path, color: const Color(0xFFFFFFFF), elevation: 0);
|
||||
final Picture circles1 = _drawTestPictureWithImage(
|
||||
const ColorFilter.mode(Color(0x3C4043), BlendMode.overlay));
|
||||
builder.addPicture(const Offset(10, 0), circles1);
|
||||
builder.pop();
|
||||
builder.build();
|
||||
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
builder2.pushPhysicalShape(
|
||||
path: path, color: const Color(0xFFFFFFFF), elevation: 0, oldLayer: oldLayer);
|
||||
builder2.addPicture(const Offset(10, 0), circles1);
|
||||
builder2.pop();
|
||||
|
||||
domDocument.body!.append(builder2.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('color_filter_blendMode_overlay.png',
|
||||
region: region);
|
||||
});
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithCircles(double offsetX, double offsetY) {
|
||||
@@ -142,23 +113,6 @@ Picture _drawTestPictureWithCircles(double offsetX, double offsetY) {
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithImage(ColorFilter filter) {
|
||||
final EnginePictureRecorder recorder =
|
||||
PictureRecorder() as EnginePictureRecorder;
|
||||
final RecordingCanvas canvas =
|
||||
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
final Image testImage = createTestImage();
|
||||
canvas.drawImageRect(
|
||||
testImage,
|
||||
const Rect.fromLTWH(0, 0, 200, 150),
|
||||
const Rect.fromLTWH(0, 0, 300, 300),
|
||||
(Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..colorFilter = filter
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1)) as SurfacePaint);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
Picture _drawBackground() {
|
||||
final EnginePictureRecorder recorder =
|
||||
PictureRecorder() as EnginePictureRecorder;
|
||||
|
||||
@@ -128,237 +128,6 @@ Future<void> testMain() async {
|
||||
await matchGoldenFile('compositing_shifted_clip_rrect.png', region: region);
|
||||
});
|
||||
|
||||
test('pushPhysicalShape', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()..addRect(const ui.Rect.fromLTRB(10, 10, 60, 60)),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color.fromRGBO(0, 0, 0, 0.3),
|
||||
elevation: 0,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
builder.pushOffset(70, 0);
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()
|
||||
..addRRect(ui.RRect.fromLTRBR(10, 10, 60, 60, const ui.Radius.circular(5))),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color.fromRGBO(0, 0, 0, 0.3),
|
||||
elevation: 0,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_shifted_physical_shape_clip.png',
|
||||
region: region);
|
||||
});
|
||||
|
||||
test('pushPhysicalShape clipOp.none', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()..addRect(const ui.Rect.fromLTRB(10, 10, 60, 60)),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color.fromRGBO(0, 0, 0, 0.3),
|
||||
elevation: 0,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
builder.pushOffset(70, 0);
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()
|
||||
..addRRect(ui.RRect.fromLTRBR(10, 10, 60, 60, const ui.Radius.circular(5))),
|
||||
color: const ui.Color.fromRGBO(0, 0, 0, 0.3),
|
||||
elevation: 0,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_shifted_physical_shape_clipnone.png',
|
||||
region: region);
|
||||
});
|
||||
|
||||
test('pushPhysicalShape with path and elevation', () async {
|
||||
final ui.Path cutCornersButton = ui.Path()
|
||||
..moveTo(15, 10)
|
||||
..lineTo(60, 10)
|
||||
..lineTo(60, 60)
|
||||
..lineTo(15, 60)
|
||||
..lineTo(10, 55)
|
||||
..lineTo(10, 15);
|
||||
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFFA0FFFF),
|
||||
elevation: 2,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
builder.pushOffset(70, 0);
|
||||
builder.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFFA0FFFF),
|
||||
elevation: 8,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
builder.pushOffset(140, 0);
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()..addOval(const ui.Rect.fromLTRB(10, 10, 60, 60)),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFFA0FFFF),
|
||||
elevation: 4,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
builder.pushOffset(210, 0);
|
||||
builder.pushPhysicalShape(
|
||||
path: ui.Path()
|
||||
..addRRect(ui.RRect.fromRectAndRadius(
|
||||
const ui.Rect.fromLTRB(10, 10, 60, 60), const ui.Radius.circular(10.0))),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFFA0FFFF),
|
||||
elevation: 4,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
builder.pop();
|
||||
|
||||
domDocument.body!.append(builder.build().webOnlyRootElement!);
|
||||
|
||||
await matchGoldenFile('compositing_physical_shape_path.png',
|
||||
region: region);
|
||||
});
|
||||
|
||||
test('pushPhysicalShape should update across frames', () async {
|
||||
final ui.Path cutCornersButton = ui.Path()
|
||||
..moveTo(15, 10)
|
||||
..lineTo(60, 10)
|
||||
..lineTo(60, 60)
|
||||
..lineTo(15, 60)
|
||||
..lineTo(10, 55)
|
||||
..lineTo(10, 15);
|
||||
|
||||
/// Start with shape that has elevation and red color.
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final ui.PhysicalShapeEngineLayer oldShapeLayer = builder.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFFFF0000),
|
||||
elevation: 2,
|
||||
);
|
||||
_drawTestPicture(builder);
|
||||
builder.pop();
|
||||
|
||||
final DomElement viewElement = builder.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement);
|
||||
await matchGoldenFile('compositing_physical_update_1.png', region: region);
|
||||
viewElement.remove();
|
||||
|
||||
/// Update color to green.
|
||||
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
|
||||
final ui.PhysicalShapeEngineLayer oldShapeLayer2 = builder2.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFF00FF00),
|
||||
elevation: 2,
|
||||
oldLayer: oldShapeLayer,
|
||||
);
|
||||
_drawTestPicture(builder2);
|
||||
builder2.pop();
|
||||
|
||||
final DomElement viewElement2 = builder2.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement2);
|
||||
await matchGoldenFile('compositing_physical_update_2.png', region: region);
|
||||
viewElement2.remove();
|
||||
|
||||
/// Update elevation.
|
||||
final SurfaceSceneBuilder builder3 = SurfaceSceneBuilder();
|
||||
final ui.PhysicalShapeEngineLayer oldShapeLayer3 = builder3.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFF00FF00),
|
||||
elevation: 6,
|
||||
oldLayer: oldShapeLayer2,
|
||||
);
|
||||
_drawTestPicture(builder3);
|
||||
builder3.pop();
|
||||
|
||||
final DomElement viewElement3 = builder3.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement3);
|
||||
await matchGoldenFile('compositing_physical_update_3.png',
|
||||
region: region);
|
||||
viewElement3.remove();
|
||||
|
||||
/// Update shape from arbitrary path to rect.
|
||||
final SurfaceSceneBuilder builder4 = SurfaceSceneBuilder();
|
||||
final ui.PhysicalShapeEngineLayer oldShapeLayer4 = builder4.pushPhysicalShape(
|
||||
path: ui.Path()..addOval(const ui.Rect.fromLTRB(10, 10, 60, 60)),
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFF00FF00),
|
||||
elevation: 6,
|
||||
oldLayer: oldShapeLayer3,
|
||||
);
|
||||
_drawTestPicture(builder4);
|
||||
builder4.pop();
|
||||
|
||||
final DomElement viewElement4 = builder4.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement4);
|
||||
await matchGoldenFile('compositing_physical_update_4.png', region: region);
|
||||
viewElement4.remove();
|
||||
|
||||
/// Update shape back to arbitrary path.
|
||||
final SurfaceSceneBuilder builder5 = SurfaceSceneBuilder();
|
||||
final ui.PhysicalShapeEngineLayer oldShapeLayer5 = builder5.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFF00FF00),
|
||||
elevation: 6,
|
||||
oldLayer: oldShapeLayer4,
|
||||
);
|
||||
_drawTestPicture(builder5);
|
||||
builder5.pop();
|
||||
|
||||
final DomElement viewElement5 = builder5.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement5);
|
||||
await matchGoldenFile('compositing_physical_update_3.png',
|
||||
region: region);
|
||||
viewElement5.remove();
|
||||
|
||||
/// Update shadow color.
|
||||
final SurfaceSceneBuilder builder6 = SurfaceSceneBuilder();
|
||||
builder6.pushPhysicalShape(
|
||||
path: cutCornersButton,
|
||||
clipBehavior: ui.Clip.hardEdge,
|
||||
color: const ui.Color(0xFF00FF00),
|
||||
shadowColor: const ui.Color(0xFFFF0000),
|
||||
elevation: 6,
|
||||
oldLayer: oldShapeLayer5,
|
||||
);
|
||||
_drawTestPicture(builder6);
|
||||
builder6.pop();
|
||||
|
||||
final DomElement viewElement6 = builder6.build().webOnlyRootElement!;
|
||||
domDocument.body!.append(viewElement6);
|
||||
await matchGoldenFile('compositing_physical_update_5.png', region: region);
|
||||
viewElement6.remove();
|
||||
});
|
||||
|
||||
test('pushImageFilter blur', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
builder.pushImageFilter(
|
||||
|
||||
@@ -56,22 +56,6 @@ Future<void> testMain() async {
|
||||
builder.addPicture(Offset.zero, recorder.endRecording());
|
||||
}
|
||||
|
||||
void paintPhysicalShapeShadow(double elevation, Offset offset) {
|
||||
final SurfacePath path = SurfacePath()
|
||||
..addRect(const Rect.fromLTRB(0, 0, 20, 20));
|
||||
builder.pushOffset(offset.dx, offset.dy);
|
||||
builder.pushPhysicalShape(
|
||||
path: path,
|
||||
elevation: elevation,
|
||||
shadowColor: _kShadowColor,
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
);
|
||||
builder.pop(); // physical shape
|
||||
paintShapeOutline();
|
||||
paintShadowBounds(path, elevation);
|
||||
builder.pop(); // offset
|
||||
}
|
||||
|
||||
void paintBitmapCanvasShadow(
|
||||
double elevation, Offset offset, bool transparentOccluder) {
|
||||
final SurfacePath path = SurfacePath()
|
||||
@@ -135,10 +119,6 @@ Future<void> testMain() async {
|
||||
|
||||
builder.pushOffset(10, 20);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
paintPhysicalShapeShadow(i.toDouble(), Offset(50.0 * i, 0));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
paintBitmapCanvasShadow(i.toDouble(), Offset(50.0 * i, 60), false);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "flutter/assets/directory_asset_bundle.h"
|
||||
#include "flutter/flow/layers/container_layer.h"
|
||||
#include "flutter/flow/layers/layer.h"
|
||||
#include "flutter/flow/layers/physical_shape_layer.h"
|
||||
#include "flutter/fml/command_line.h"
|
||||
#include "flutter/fml/file.h"
|
||||
#include "flutter/fml/log_settings.h"
|
||||
@@ -25,167 +24,6 @@ namespace testing {
|
||||
|
||||
using PersistentCacheTest = ShellTest;
|
||||
|
||||
static void WaitForIO(Shell* shell) {
|
||||
std::promise<bool> io_task_finished;
|
||||
shell->GetTaskRunners().GetIOTaskRunner()->PostTask(
|
||||
[&io_task_finished]() { io_task_finished.set_value(true); });
|
||||
io_task_finished.get_future().wait();
|
||||
}
|
||||
|
||||
static void WaitForRaster(Shell* shell) {
|
||||
std::promise<bool> raster_task_finished;
|
||||
shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
|
||||
[&raster_task_finished]() { raster_task_finished.set_value(true); });
|
||||
raster_task_finished.get_future().wait();
|
||||
}
|
||||
|
||||
TEST_F(PersistentCacheTest, CacheSkSLWorks) {
|
||||
// Create a temp dir to store the persistent cache
|
||||
fml::ScopedTemporaryDirectory dir;
|
||||
PersistentCache::SetCacheDirectoryPath(dir.path());
|
||||
PersistentCache::ResetCacheForProcess();
|
||||
|
||||
auto settings = CreateSettingsForFixture();
|
||||
settings.cache_sksl = true;
|
||||
settings.dump_skp_on_shader_compilation = true;
|
||||
|
||||
fml::AutoResetWaitableEvent first_frame_latch;
|
||||
settings.frame_rasterized_callback =
|
||||
[&first_frame_latch](const FrameTiming& t) {
|
||||
first_frame_latch.Signal();
|
||||
};
|
||||
|
||||
auto sksl_config = RunConfiguration::InferFromSettings(settings);
|
||||
sksl_config.SetEntrypoint("emptyMain");
|
||||
std::unique_ptr<Shell> shell = CreateShell(settings);
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
RunEngine(shell.get(), std::move(sksl_config));
|
||||
|
||||
// Initially, we should have no SkSL cache
|
||||
auto cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
|
||||
ASSERT_EQ(cache.size(), 0u);
|
||||
|
||||
// Draw something to trigger shader compilations.
|
||||
LayerTreeBuilder builder = [](const std::shared_ptr<ContainerLayer>& root) {
|
||||
SkPath path;
|
||||
path.addCircle(50, 50, 20);
|
||||
auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
|
||||
SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
|
||||
root->Add(physical_shape_layer);
|
||||
};
|
||||
PumpOneFrame(shell.get(), 100, 100, builder);
|
||||
first_frame_latch.Wait();
|
||||
WaitForIO(shell.get());
|
||||
|
||||
// Some skp should be dumped due to shader compilations.
|
||||
int skp_count = 0;
|
||||
fml::FileVisitor skp_visitor = [&skp_count](const fml::UniqueFD& directory,
|
||||
const std::string& filename) {
|
||||
if (filename.size() >= 4 &&
|
||||
filename.substr(filename.size() - 4, 4) == ".skp") {
|
||||
skp_count += 1;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
fml::VisitFilesRecursively(dir.fd(), skp_visitor);
|
||||
ASSERT_GT(skp_count, 0);
|
||||
|
||||
// SkSL cache should be generated by the last run.
|
||||
cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
|
||||
ASSERT_GT(cache.size(), 0u);
|
||||
|
||||
// Run the engine again with cache_sksl = false and check that the previously
|
||||
// generated SkSL cache is used for precompile.
|
||||
PersistentCache::ResetCacheForProcess();
|
||||
settings.cache_sksl = false;
|
||||
settings.dump_skp_on_shader_compilation = true;
|
||||
auto normal_config = RunConfiguration::InferFromSettings(settings);
|
||||
normal_config.SetEntrypoint("emptyMain");
|
||||
DestroyShell(std::move(shell));
|
||||
shell = CreateShell(settings);
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
RunEngine(shell.get(), std::move(normal_config));
|
||||
first_frame_latch.Reset();
|
||||
PumpOneFrame(shell.get(), 100, 100, builder);
|
||||
first_frame_latch.Wait();
|
||||
WaitForIO(shell.get());
|
||||
|
||||
// Shader precompilation from SkSL is not implemented on the Skia Vulkan
|
||||
// backend so don't run the second half of this test on Vulkan. This can get
|
||||
// removed if SkSL precompilation is implemented in the Skia Vulkan backend.
|
||||
#if !defined(SHELL_ENABLE_VULKAN)
|
||||
// To check that all shaders are precompiled, verify that no new skp is dumped
|
||||
// due to shader compilations.
|
||||
int old_skp_count = skp_count;
|
||||
skp_count = 0;
|
||||
fml::VisitFilesRecursively(dir.fd(), skp_visitor);
|
||||
ASSERT_EQ(skp_count, old_skp_count);
|
||||
#endif // !defined(SHELL_ENABLE_VULKAN)
|
||||
|
||||
// Remove all files generated
|
||||
fml::RemoveFilesInDirectory(dir.fd());
|
||||
DestroyShell(std::move(shell));
|
||||
}
|
||||
|
||||
TEST_F(PersistentCacheTest, CanPrecompileMetalShaders) {
|
||||
#if !SHELL_ENABLE_METAL
|
||||
GTEST_SKIP();
|
||||
#endif // !SHELL_ENABLE_METAL
|
||||
fml::ScopedTemporaryDirectory dir;
|
||||
PersistentCache::SetCacheDirectoryPath(dir.path());
|
||||
PersistentCache::ResetCacheForProcess();
|
||||
|
||||
auto settings = CreateSettingsForFixture();
|
||||
settings.cache_sksl = true;
|
||||
settings.dump_skp_on_shader_compilation = true;
|
||||
|
||||
fml::AutoResetWaitableEvent first_frame_latch;
|
||||
settings.frame_rasterized_callback =
|
||||
[&first_frame_latch](const FrameTiming& t) {
|
||||
first_frame_latch.Signal();
|
||||
};
|
||||
|
||||
auto sksl_config = RunConfiguration::InferFromSettings(settings);
|
||||
sksl_config.SetEntrypoint("emptyMain");
|
||||
std::unique_ptr<Shell> shell =
|
||||
CreateShell(settings, //
|
||||
GetTaskRunnersForFixture(), //
|
||||
false, //
|
||||
nullptr, //
|
||||
false, //
|
||||
ShellTestPlatformView::BackendType::kMetalBackend //
|
||||
);
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
RunEngine(shell.get(), std::move(sksl_config));
|
||||
|
||||
// Initially, we should have no SkSL cache
|
||||
{
|
||||
auto empty_cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
|
||||
ASSERT_EQ(empty_cache.size(), 0u);
|
||||
}
|
||||
|
||||
// Draw something to trigger shader compilations.
|
||||
LayerTreeBuilder builder = [](const std::shared_ptr<ContainerLayer>& root) {
|
||||
SkPath path;
|
||||
path.addCircle(50, 50, 20);
|
||||
auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
|
||||
SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
|
||||
root->Add(physical_shape_layer);
|
||||
};
|
||||
PumpOneFrame(shell.get(), 100, 100, builder);
|
||||
first_frame_latch.Wait();
|
||||
WaitForRaster(shell.get());
|
||||
WaitForIO(shell.get());
|
||||
|
||||
// Assert that SkSLs have been generated.
|
||||
auto filled_cache = PersistentCache::GetCacheForProcess()->LoadSkSLs();
|
||||
ASSERT_GT(filled_cache.size(), 0u);
|
||||
|
||||
// Remove all files generated.
|
||||
fml::RemoveFilesInDirectory(dir.fd());
|
||||
DestroyShell(std::move(shell));
|
||||
}
|
||||
|
||||
static void CheckTextSkData(const sk_sp<SkData>& data,
|
||||
const std::string& expected) {
|
||||
std::string data_string(reinterpret_cast<const char*>(data->bytes()),
|
||||
|
||||
@@ -2418,7 +2418,6 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
|
||||
.ui_time = ui_time,
|
||||
.texture_registry = nullptr,
|
||||
.raster_cache = &raster_cache,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -2433,7 +2432,6 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
|
||||
.raster_time = raster_time,
|
||||
.ui_time = ui_time,
|
||||
.texture_registry = nullptr,
|
||||
.frame_device_pixel_ratio = 1.0f,
|
||||
.has_platform_view = false,
|
||||
.has_texture_layer = false,
|
||||
.raster_cached_entries = &raster_cache_items,
|
||||
|
||||
@@ -402,10 +402,6 @@ void main() {
|
||||
oldLayer: oldLayer as ShaderMaskEngineLayer?,
|
||||
);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) {
|
||||
// ignore: deprecated_member_use
|
||||
return builder.pushPhysicalShape(path: Path(), color: const Color.fromARGB(0, 0, 0, 0), oldLayer: oldLayer as PhysicalShapeEngineLayer?, elevation: 0.0);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) {
|
||||
return builder.pushColorFilter(
|
||||
const ColorFilter.mode(
|
||||
|
||||
Reference in New Issue
Block a user