• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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