1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/flow/layers/physical_shape_layer.h"
6
7 #include "flutter/flow/paint_utils.h"
8 #include "third_party/skia/include/utils/SkShadowUtils.h"
9
10 namespace flutter {
11
12 const SkScalar kLightHeight = 600;
13 const SkScalar kLightRadius = 800;
14
PhysicalShapeLayer(SkColor color,SkColor shadow_color,SkScalar device_pixel_ratio,float viewport_depth,float elevation,const SkPath & path,Clip clip_behavior)15 PhysicalShapeLayer::PhysicalShapeLayer(SkColor color,
16 SkColor shadow_color,
17 SkScalar device_pixel_ratio,
18 float viewport_depth,
19 float elevation,
20 const SkPath& path,
21 Clip clip_behavior)
22 : color_(color),
23 shadow_color_(shadow_color),
24 device_pixel_ratio_(device_pixel_ratio),
25 viewport_depth_(viewport_depth),
26 elevation_(elevation),
27 path_(path),
28 isRect_(false),
29 clip_behavior_(clip_behavior) {
30 SkRect rect;
31 if (path.isRect(&rect)) {
32 isRect_ = true;
33 frameRRect_ = SkRRect::MakeRect(rect);
34 } else if (path.isRRect(&frameRRect_)) {
35 isRect_ = frameRRect_.isRect();
36 } else if (path.isOval(&rect)) {
37 // isRRect returns false for ovals, so we need to explicitly check isOval
38 // as well.
39 frameRRect_ = SkRRect::MakeOval(rect);
40 } else {
41 // Scenic currently doesn't provide an easy way to create shapes from
42 // arbitrary paths.
43 // For shapes that cannot be represented as a rounded rectangle we
44 // default to use the bounding rectangle.
45 // TODO(amirh): fix this once we have a way to create a Scenic shape from
46 // an SkPath.
47 frameRRect_ = SkRRect::MakeRect(path.getBounds());
48 }
49 }
50
51 PhysicalShapeLayer::~PhysicalShapeLayer() = default;
52
Preroll(PrerollContext * context,const SkMatrix & matrix)53 void PhysicalShapeLayer::Preroll(PrerollContext* context,
54 const SkMatrix& matrix) {
55 context->total_elevation += elevation_;
56 total_elevation_ = context->total_elevation;
57 SkRect child_paint_bounds;
58 PrerollChildren(context, matrix, &child_paint_bounds);
59 context->total_elevation -= elevation_;
60
61 if (elevation_ == 0) {
62 set_paint_bounds(path_.getBounds());
63 } else {
64 #if defined(OS_FUCHSIA)
65 // Let the system compositor draw all shadows for us.
66 set_needs_system_composite(true);
67 #else
68 // Add some margin to the paint bounds to leave space for the shadow.
69 // We fill this whole region and clip children to it so we don't need to
70 // join the child paint bounds.
71 // The offset is calculated as follows:
72
73 // .--- (kLightRadius)
74 // -------/ (light)
75 // | /
76 // | /
77 // |/
78 // |O
79 // /| (kLightHeight)
80 // / |
81 // / |
82 // / |
83 // / |
84 // ------------- (layer)
85 // /| |
86 // / | | (elevation)
87 // A / | |B
88 // ------------------------------------------------ (canvas)
89 // --- (extent of shadow)
90 //
91 // E = lt } t = (r + w/2)/h
92 // } =>
93 // r + w/2 = ht } E = (l/h)(r + w/2)
94 //
95 // Where: E = extent of shadow
96 // l = elevation of layer
97 // r = radius of the light source
98 // w = width of the layer
99 // h = light height
100 // t = tangent of AOB, i.e., multiplier for elevation to extent
101 SkRect bounds(path_.getBounds());
102 // tangent for x
103 double tx = (kLightRadius * device_pixel_ratio_ + bounds.width() * 0.5) /
104 kLightHeight;
105 // tangent for y
106 double ty = (kLightRadius * device_pixel_ratio_ + bounds.height() * 0.5) /
107 kLightHeight;
108 bounds.outset(elevation_ * tx, elevation_ * ty);
109 set_paint_bounds(bounds);
110 #endif // defined(OS_FUCHSIA)
111 }
112 }
113
114 #if defined(OS_FUCHSIA)
115
UpdateScene(SceneUpdateContext & context)116 void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) {
117 FML_DCHECK(needs_system_composite());
118
119 // Retained rendering: speedup by reusing a retained entity node if possible.
120 // When an entity node is reused, no paint layer is added to the frame so we
121 // won't call PhysicalShapeLayer::Paint.
122 LayerRasterCacheKey key(unique_id(), context.Matrix());
123 if (context.HasRetainedNode(key)) {
124 const scenic::EntityNode& retained_node = context.GetRetainedNode(key);
125 FML_DCHECK(context.top_entity());
126 FML_DCHECK(retained_node.session() == context.session());
127 context.top_entity()->entity_node().AddChild(retained_node);
128 return;
129 }
130
131 // If we can't find an existing retained surface, create one.
132 SceneUpdateContext::Frame frame(context, frameRRect_, color_, elevation_,
133 total_elevation_, viewport_depth_, this);
134 for (auto& layer : layers()) {
135 if (layer->needs_painting()) {
136 frame.AddPaintLayer(layer.get());
137 }
138 }
139
140 UpdateSceneChildren(context);
141 }
142
143 #endif // defined(OS_FUCHSIA)
144
Paint(PaintContext & context) const145 void PhysicalShapeLayer::Paint(PaintContext& context) const {
146 TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint");
147 FML_DCHECK(needs_painting());
148
149 if (elevation_ != 0) {
150 DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_,
151 SkColorGetA(color_) != 0xff, device_pixel_ratio_);
152 }
153
154 // Call drawPath without clip if possible for better performance.
155 SkPaint paint;
156 paint.setColor(color_);
157 paint.setAntiAlias(true);
158 if (clip_behavior_ != Clip::antiAliasWithSaveLayer) {
159 context.leaf_nodes_canvas->drawPath(path_, paint);
160 }
161
162 int saveCount = context.internal_nodes_canvas->save();
163 switch (clip_behavior_) {
164 case Clip::hardEdge:
165 context.internal_nodes_canvas->clipPath(path_, false);
166 break;
167 case Clip::antiAlias:
168 context.internal_nodes_canvas->clipPath(path_, true);
169 break;
170 case Clip::antiAliasWithSaveLayer:
171 context.internal_nodes_canvas->clipPath(path_, true);
172 context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
173 break;
174 case Clip::none:
175 break;
176 }
177
178 if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
179 // If we want to avoid the bleeding edge artifact
180 // (https://github.com/flutter/flutter/issues/18057#issue-328003931)
181 // using saveLayer, we have to call drawPaint instead of drawPath as
182 // anti-aliased drawPath will always have such artifacts.
183 context.leaf_nodes_canvas->drawPaint(paint);
184 }
185
186 PaintChildren(context);
187
188 context.internal_nodes_canvas->restoreToCount(saveCount);
189 }
190
DrawShadow(SkCanvas * canvas,const SkPath & path,SkColor color,float elevation,bool transparentOccluder,SkScalar dpr)191 void PhysicalShapeLayer::DrawShadow(SkCanvas* canvas,
192 const SkPath& path,
193 SkColor color,
194 float elevation,
195 bool transparentOccluder,
196 SkScalar dpr) {
197 const SkScalar kAmbientAlpha = 0.039f;
198 const SkScalar kSpotAlpha = 0.25f;
199
200 SkShadowFlags flags = transparentOccluder
201 ? SkShadowFlags::kTransparentOccluder_ShadowFlag
202 : SkShadowFlags::kNone_ShadowFlag;
203 const SkRect& bounds = path.getBounds();
204 SkScalar shadow_x = (bounds.left() + bounds.right()) / 2;
205 SkScalar shadow_y = bounds.top() - 600.0f;
206 SkColor inAmbient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));
207 SkColor inSpot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));
208 SkColor ambientColor, spotColor;
209 SkShadowUtils::ComputeTonalColors(inAmbient, inSpot, &ambientColor,
210 &spotColor);
211 SkShadowUtils::DrawShadow(
212 canvas, path, SkPoint3::Make(0, 0, dpr * elevation),
213 SkPoint3::Make(shadow_x, shadow_y, dpr * kLightHeight),
214 dpr * kLightRadius, ambientColor, spotColor, flags);
215 }
216
217 } // namespace flutter
218