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 | #ifndef FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ |
6 | #define FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ |
7 | |
8 | #include "flutter/display_list/dl_canvas.h" |
9 | #include "flutter/flow/embedded_views.h" |
10 | #include "flutter/flow/paint_utils.h" |
11 | |
12 | namespace flutter { |
13 | |
14 | /// The LayerStateStack manages the inherited state passed down between |
15 | /// |Layer| objects in a |LayerTree| during |Preroll| and |Paint|. |
16 | /// |
17 | /// More specifically, it manages the clip and transform state during |
18 | /// recursive rendering and will hold and lazily apply opacity, ImageFilter |
19 | /// and ColorFilter attributes to recursive content. This is not a truly |
20 | /// general state management mechnanism as it makes assumptions that code |
21 | /// will be applying the attributes to rendered content that happens in |
22 | /// recursive calls. The automatic save/restore mechanisms only work in |
23 | /// a context where C++ auto-destruct calls will engage the restore at |
24 | /// the end of a code block and that any applied attributes will only |
25 | /// be applied to the content rendered inside that block. These restrictions |
26 | /// match the organization of the |LayerTree| methods precisely. |
27 | /// |
28 | /// The stack can manage a single state delegate. The delegate will provide |
29 | /// tracking of the current transform and clip and will also execute |
30 | /// saveLayer calls at the appropriate time if it is a rendering delegate. |
31 | /// The delegate can be swapped out on the fly (as is typically done by |
32 | /// PlatformViewLayer when recording the state for multiple "overlay" |
33 | /// layers that occur between embedded view subtrees. The old delegate |
34 | /// will be restored to its original state before it became a delegate |
35 | /// and the new delegate will have all of the state recorded by the stack |
36 | /// replayed into it to bring it up to speed with the current rendering |
37 | /// context. |
38 | /// |
39 | /// The delegate can be any one of: |
40 | /// - Preroll delegate: used during Preroll to remember the outstanding |
41 | /// state for embedded platform layers |
42 | /// - DlCanvas: used during Paint for rendering output |
43 | /// The stack will know which state needs to be conveyed to any of these |
44 | /// delegates and when is the best time to convey that state (i.e. lazy |
45 | /// saveLayer calls for example). |
46 | /// |
47 | /// The rendering state attributes will be automatically applied to the |
48 | /// nested content using a |saveLayer| call at the point at which we |
49 | /// encounter rendered content (i.e. various nested layers that exist only |
50 | /// to apply new state will not trigger the |saveLayer| and the attributes |
51 | /// can accumulate until we reach actual content that is rendered.) Some |
52 | /// rendered content can avoid the |saveLayer| if it reports to the object |
53 | /// that it is able to apply all of the attributes that happen to be |
54 | /// outstanding (accumulated from parent state-modifiers). A |ContainerLayer| |
55 | /// can also monitor the attribute rendering capabilities of a list of |
56 | /// children and can ask the object to apply a protective |saveLayer| or |
57 | /// not based on the negotiated capabilities of the entire group. |
58 | /// |
59 | /// Any code that is planning to modify the clip, transform, or rendering |
60 | /// attributes for its child content must start by calling the |save| method |
61 | /// which returns a MutatorContext object. The methods that modify such |
62 | /// state only exist on the MutatorContext object so it is difficult to get |
63 | /// that wrong, but the caller must make sure that the call happens within |
64 | /// a C++ code block that will define the "rendering scope" of those |
65 | /// state changes as they will be automatically restored on exit from that |
66 | /// block. Note that the layer might make similar state calls directly on |
67 | /// the canvas or builder during the Paint cycle (via saveLayer, transform, |
68 | /// or clip calls), but should avoid doing so if there is any nested content |
69 | /// that needs to track or react to those state calls. |
70 | /// |
71 | /// Code that needs to render content can simply inform the parent of their |
72 | /// abilities by setting the |PrerollContext::renderable_state_flags| during |
73 | /// |Preroll| and then render with those attributes during |Paint| by |
74 | /// requesting the outstanding values of those attributes from the state_stack |
75 | /// object. Individual leaf layers can ignore this feature as the default |
76 | /// behavior during |Preroll| will have their parent |ContainerLayer| assume |
77 | /// that they cannot render any outstanding state attributes and will apply |
78 | /// the protective saveLayer on their behalf if needed. As such, this object |
79 | /// only provides "opt-in" features for leaf layers and no responsibilities |
80 | /// otherwise. |
81 | /// See |LayerStateStack::fill| |
82 | /// See |LayerStateStack::outstanding_opacity| |
83 | /// See |LayerStateStack::outstanding_color_filter| |
84 | /// See |LayerStateStack::outstanding_image_filter| |
85 | /// |
86 | /// State-modifying layers should contain code similar to this pattern in both |
87 | /// their |Preroll| and |Paint| methods. |
88 | /// |
89 | /// void [LayerType]::[Preroll/Paint](context) { |
90 | /// auto mutator = context.state_stack.save(); |
91 | /// mutator.translate(origin.x, origin.y); |
92 | /// mutator.applyOpacity(content_bounds, opacity_value); |
93 | /// mutator.applyColorFilter(content_bounds, color_filter); |
94 | /// // or any of the mutator transform, clip or attribute methods |
95 | /// |
96 | /// // Children will react to the state applied above during their |
97 | /// // Preroll/Paint methods or ContainerLayer will protect them |
98 | /// // conservatively by default. |
99 | /// [Preroll/Paint]Children(context); |
100 | /// |
101 | /// // here the mutator will be auto-destructed and the state accumulated |
102 | /// // by it will be restored out of the state_stack and its associated |
103 | /// // delegates. |
104 | /// } |
105 | class LayerStateStack { |
106 | public: |
107 | LayerStateStack(); |
108 | |
109 | // Clears out any old delegate to make room for a new one. |
110 | void clear_delegate(); |
111 | |
112 | // Return the DlCanvas delegate if the state stack has such a delegate. |
113 | // The state stack will only have one delegate at a time holding either |
114 | // a DlCanvas or a preroll accumulator. |
115 | DlCanvas* canvas_delegate() { return delegate_->canvas(); } |
116 | |
117 | // Clears the old delegate and sets the canvas delegate to the indicated |
118 | // DL canvas (if not nullptr). This ensures that only one delegate - either |
119 | // a DlCanvas or a preroll accumulator - is present at any one time. |
120 | void set_delegate(DlCanvas* canvas); |
121 | |
122 | // Clears the old delegate and sets the state stack up to accumulate |
123 | // clip and transform information for a Preroll phase. This ensures |
124 | // that only one delegate - either a DlCanvas or a preroll accumulator - |
125 | // is present at any one time. |
126 | void set_preroll_delegate(const SkRect& cull_rect, const SkMatrix& matrix); |
127 | void set_preroll_delegate(const SkRect& cull_rect); |
128 | void set_preroll_delegate(const SkMatrix& matrix); |
129 | |
130 | // Fills the supplied MatatorsStack object with the mutations recorded |
131 | // by this LayerStateStack in the order encountered. |
132 | void (MutatorsStack* mutators); |
133 | |
134 | // Sets up a checkerboard function that will be used to checkerboard the |
135 | // contents of any saveLayer executed by the state stack. |
136 | CheckerboardFunc checkerboard_func() const { return checkerboard_func_; } |
137 | void set_checkerboard_func(CheckerboardFunc checkerboard_func) { |
138 | checkerboard_func_ = checkerboard_func; |
139 | } |
140 | |
141 | class AutoRestore { |
142 | public: |
143 | ~AutoRestore() { |
144 | layer_state_stack_->restore_to_count(stack_restore_count_); |
145 | } |
146 | |
147 | private: |
148 | AutoRestore(LayerStateStack* stack, const SkRect& bounds, int flags) |
149 | : layer_state_stack_(stack), |
150 | stack_restore_count_(stack->stack_count()) { |
151 | if (stack->needs_save_layer(flags)) { |
152 | stack->save_layer(bounds); |
153 | } |
154 | } |
155 | friend class LayerStateStack; |
156 | |
157 | LayerStateStack* layer_state_stack_; |
158 | const size_t stack_restore_count_; |
159 | |
160 | FML_DISALLOW_COPY_ASSIGN_AND_MOVE(AutoRestore); |
161 | }; |
162 | |
163 | class MutatorContext { |
164 | public: |
165 | ~MutatorContext() { |
166 | layer_state_stack_->restore_to_count(stack_restore_count_); |
167 | } |
168 | |
169 | // Immediately executes a saveLayer with all accumulated state |
170 | // onto the canvas or builder to be applied at the next matching |
171 | // restore. A saveLayer is always executed by this method even if |
172 | // there are no outstanding attributes. |
173 | void saveLayer(const SkRect& bounds); |
174 | |
175 | // Records the opacity for application at the next call to |
176 | // saveLayer or applyState. A saveLayer may be executed at |
177 | // this time if the opacity cannot be batched with other |
178 | // outstanding attributes. |
179 | void applyOpacity(const SkRect& bounds, SkScalar opacity); |
180 | |
181 | // Records the image filter for application at the next call to |
182 | // saveLayer or applyState. A saveLayer may be executed at |
183 | // this time if the image filter cannot be batched with other |
184 | // outstanding attributes. |
185 | // (Currently only opacity is recorded for batching) |
186 | void applyImageFilter(const SkRect& bounds, |
187 | const std::shared_ptr<const DlImageFilter>& filter); |
188 | |
189 | // Records the color filter for application at the next call to |
190 | // saveLayer or applyState. A saveLayer may be executed at |
191 | // this time if the color filter cannot be batched with other |
192 | // outstanding attributes. |
193 | // (Currently only opacity is recorded for batching) |
194 | void applyColorFilter(const SkRect& bounds, |
195 | const std::shared_ptr<const DlColorFilter>& filter); |
196 | |
197 | // Saves the state stack and immediately executes a saveLayer |
198 | // with the indicated backdrop filter and any outstanding |
199 | // state attributes. Since the backdrop filter only applies |
200 | // to the pixels alrady on the screen when this call is made, |
201 | // the backdrop filter will only be applied to the canvas or |
202 | // builder installed at the time that this call is made, and |
203 | // subsequent canvas or builder objects that are made delegates |
204 | // will only see a saveLayer with the indicated blend_mode. |
205 | void applyBackdropFilter(const SkRect& bounds, |
206 | const std::shared_ptr<const DlImageFilter>& filter, |
207 | DlBlendMode blend_mode); |
208 | |
209 | void translate(SkScalar tx, SkScalar ty); |
210 | void translate(SkPoint tp) { translate(tx: tp.fX, ty: tp.fY); } |
211 | void transform(const SkM44& m44); |
212 | void transform(const SkMatrix& matrix); |
213 | void integralTransform(); |
214 | |
215 | void clipRect(const SkRect& rect, bool is_aa); |
216 | void clipRRect(const SkRRect& rrect, bool is_aa); |
217 | void clipPath(const SkPath& path, bool is_aa); |
218 | |
219 | private: |
220 | MutatorContext(LayerStateStack* stack) |
221 | : layer_state_stack_(stack), |
222 | stack_restore_count_(stack->stack_count()), |
223 | save_needed_(true) {} |
224 | friend class LayerStateStack; |
225 | |
226 | LayerStateStack* layer_state_stack_; |
227 | const size_t stack_restore_count_; |
228 | bool save_needed_; |
229 | |
230 | FML_DISALLOW_COPY_ASSIGN_AND_MOVE(MutatorContext); |
231 | }; |
232 | |
233 | static constexpr int kCallerCanApplyOpacity = 0x1; |
234 | static constexpr int kCallerCanApplyColorFilter = 0x2; |
235 | static constexpr int kCallerCanApplyImageFilter = 0x4; |
236 | static constexpr int kCallerCanApplyAnything = |
237 | (kCallerCanApplyOpacity | kCallerCanApplyColorFilter | |
238 | kCallerCanApplyImageFilter); |
239 | |
240 | // Apply the outstanding state via saveLayer if necessary, |
241 | // respecting the flags representing which potentially |
242 | // outstanding attributes the calling layer can apply |
243 | // themselves. |
244 | // |
245 | // A saveLayer may or may not be sent to the delegates depending |
246 | // on how the outstanding state intersects with the flags supplied |
247 | // by the caller. |
248 | // |
249 | // An AutoRestore instance will always be returned even if there |
250 | // was no saveLayer applied. |
251 | [[nodiscard]] inline AutoRestore applyState(const SkRect& bounds, |
252 | int can_apply_flags) { |
253 | return AutoRestore(this, bounds, can_apply_flags); |
254 | } |
255 | |
256 | SkScalar outstanding_opacity() const { return outstanding_.opacity; } |
257 | |
258 | std::shared_ptr<const DlColorFilter> outstanding_color_filter() const { |
259 | return outstanding_.color_filter; |
260 | } |
261 | |
262 | std::shared_ptr<const DlImageFilter> outstanding_image_filter() const { |
263 | return outstanding_.image_filter; |
264 | } |
265 | |
266 | // The outstanding bounds are the bounds recorded during the last |
267 | // attribute applied to this state stack. The assumption is that |
268 | // the nested calls to the state stack will each supply bounds relative |
269 | // to the content of that single attribute and the bounds of the content |
270 | // of any outstanding attributes will include the output bounds of |
271 | // applying any nested attributes. Thus, only the innermost content |
272 | // bounds received will be sufficient to apply all outstanding attributes. |
273 | SkRect outstanding_bounds() const { return outstanding_.save_layer_bounds; } |
274 | |
275 | // Fill the provided paint object with any oustanding attributes and |
276 | // return a pointer to it, or return a nullptr if there were no |
277 | // outstanding attributes to paint with. |
278 | DlPaint* fill(DlPaint& paint) const { return outstanding_.fill(paint); } |
279 | |
280 | // The cull_rect (not the exact clip) relative to the device pixels. |
281 | // This rectangle may be a conservative estimate of the true clip region. |
282 | SkRect device_cull_rect() const { return delegate_->device_cull_rect(); } |
283 | |
284 | // The cull_rect (not the exact clip) relative to the local coordinates. |
285 | // This rectangle may be a conservative estimate of the true clip region. |
286 | SkRect local_cull_rect() const { return delegate_->local_cull_rect(); } |
287 | |
288 | // The transform from the local coordinates to the device coordinates |
289 | // in the most capable 4x4 matrix representation. This matrix may be |
290 | // more information than is needed to compute bounds for a 2D rendering |
291 | // primitive, but it will accurately concatenate with other 4x4 matrices |
292 | // without losing information. |
293 | SkM44 transform_4x4() const { return delegate_->matrix_4x4(); } |
294 | |
295 | // The transform from the local coordinates to the device coordinates |
296 | // in a more compact 3x3 matrix represenation that provides enough |
297 | // information to accurately transform 2D primitives into their |
298 | // resulting 2D bounds. This matrix also has enough information to |
299 | // concat with other 2D affine transforms, but does not carry enough |
300 | // information to accurately concat with fully perspective matrics. |
301 | SkMatrix transform_3x3() const { return delegate_->matrix_3x3(); } |
302 | |
303 | // Tests if painting content with the current outstanding attributes |
304 | // will produce any content. This method does not check the current |
305 | // transform or clip for being singular or empty. |
306 | // See |content_culled| |
307 | bool painting_is_nop() const { return outstanding_.opacity <= 0; } |
308 | |
309 | // Tests if painting content with the given bounds will produce any output. |
310 | // This method does not check the outstanding attributes to verify that |
311 | // they produce visible results. |
312 | // See |painting_is_nop| |
313 | bool content_culled(const SkRect& content_bounds) const { |
314 | return delegate_->content_culled(content_bounds); |
315 | } |
316 | |
317 | // Saves the current state of the state stack and returns a |
318 | // MutatorContext which can be used to manipulate the state. |
319 | // The state stack will be restored to its current state |
320 | // when the MutatorContext object goes out of scope. |
321 | [[nodiscard]] inline MutatorContext save() { return MutatorContext(this); } |
322 | |
323 | // Returns true if the state stack is in, or has returned to, |
324 | // its initial state. |
325 | bool is_empty() const { return state_stack_.empty(); } |
326 | |
327 | private: |
328 | size_t stack_count() const { return state_stack_.size(); } |
329 | void restore_to_count(size_t restore_count); |
330 | void reapply_all(); |
331 | |
332 | void apply_last_entry() { state_stack_.back()->apply(stack: this); } |
333 | |
334 | // The push methods simply push an associated StateEntry on the stack |
335 | // and then apply it to the current canvas and builder. |
336 | // --------------------- |
337 | // void push_attributes(); |
338 | void push_opacity(const SkRect& rect, SkScalar opacity); |
339 | void push_color_filter(const SkRect& bounds, |
340 | const std::shared_ptr<const DlColorFilter>& filter); |
341 | void push_image_filter(const SkRect& bounds, |
342 | const std::shared_ptr<const DlImageFilter>& filter); |
343 | void push_backdrop(const SkRect& bounds, |
344 | const std::shared_ptr<const DlImageFilter>& filter, |
345 | DlBlendMode blend_mode); |
346 | |
347 | void push_translate(SkScalar tx, SkScalar ty); |
348 | void push_transform(const SkM44& matrix); |
349 | void push_transform(const SkMatrix& matrix); |
350 | void push_integral_transform(); |
351 | |
352 | void push_clip_rect(const SkRect& rect, bool is_aa); |
353 | void push_clip_rrect(const SkRRect& rrect, bool is_aa); |
354 | void push_clip_path(const SkPath& path, bool is_aa); |
355 | // --------------------- |
356 | |
357 | // The maybe/needs_save_layer methods will determine if the indicated |
358 | // attribute can be incorporated into the outstanding attributes as is, |
359 | // or if the apply_flags are compatible with the outstanding attributes. |
360 | // If the oustanding attributes are incompatible with the new attribute |
361 | // or the apply flags, then a protective saveLayer will be executed. |
362 | // --------------------- |
363 | bool needs_save_layer(int flags) const; |
364 | void do_save(); |
365 | void save_layer(const SkRect& bounds); |
366 | void maybe_save_layer_for_transform(bool needs_save); |
367 | void maybe_save_layer_for_clip(bool needs_save); |
368 | void maybe_save_layer(int apply_flags); |
369 | void maybe_save_layer(SkScalar opacity); |
370 | void maybe_save_layer(const std::shared_ptr<const DlColorFilter>& filter); |
371 | void maybe_save_layer(const std::shared_ptr<const DlImageFilter>& filter); |
372 | // --------------------- |
373 | |
374 | struct RenderingAttributes { |
375 | // We need to record the last bounds we received for the last |
376 | // attribute that we recorded so that we can perform a saveLayer |
377 | // on the proper area. When an attribute is applied that cannot |
378 | // be merged with the existing attributes, it will be submitted |
379 | // with a bounds for its own source content, not the bounds for |
380 | // the content that will be included in the saveLayer that applies |
381 | // the existing outstanding attributes - thus we need to record |
382 | // the bounds that were supplied with the most recent previous |
383 | // attribute to be applied. |
384 | SkRect save_layer_bounds{.fLeft: 0, .fTop: 0, .fRight: 0, .fBottom: 0}; |
385 | |
386 | SkScalar opacity = SK_Scalar1; |
387 | std::shared_ptr<const DlColorFilter> color_filter; |
388 | std::shared_ptr<const DlImageFilter> image_filter; |
389 | |
390 | DlPaint* fill(DlPaint& paint, |
391 | DlBlendMode mode = DlBlendMode::kSrcOver) const; |
392 | |
393 | bool operator==(const RenderingAttributes& other) const { |
394 | return save_layer_bounds == other.save_layer_bounds && |
395 | opacity == other.opacity && |
396 | Equals(a: color_filter, b: other.color_filter) && |
397 | Equals(a: image_filter, b: other.image_filter); |
398 | } |
399 | }; |
400 | |
401 | class StateEntry { |
402 | public: |
403 | virtual ~StateEntry() = default; |
404 | |
405 | virtual void apply(LayerStateStack* stack) const = 0; |
406 | virtual void reapply(LayerStateStack* stack) const { apply(stack); } |
407 | virtual void restore(LayerStateStack* stack) const {} |
408 | virtual void (MutatorsStack* mutators_stack) const {} |
409 | |
410 | protected: |
411 | StateEntry() = default; |
412 | |
413 | FML_DISALLOW_COPY_ASSIGN_AND_MOVE(StateEntry); |
414 | }; |
415 | friend class SaveEntry; |
416 | friend class SaveLayerEntry; |
417 | friend class BackdropFilterEntry; |
418 | friend class OpacityEntry; |
419 | friend class ImageFilterEntry; |
420 | friend class ColorFilterEntry; |
421 | friend class TranslateEntry; |
422 | friend class TransformMatrixEntry; |
423 | friend class TransformM44Entry; |
424 | friend class IntegralTransformEntry; |
425 | friend class ClipEntry; |
426 | friend class ClipRectEntry; |
427 | friend class ClipRRectEntry; |
428 | friend class ClipPathEntry; |
429 | |
430 | class Delegate { |
431 | protected: |
432 | using ClipOp = DlCanvas::ClipOp; |
433 | |
434 | public: |
435 | virtual ~Delegate() = default; |
436 | |
437 | // Mormally when a |Paint| or |Preroll| cycle is completed, the |
438 | // delegate will have been rewound to its initial state by the |
439 | // trailing recursive actions of the paint and preroll methods. |
440 | // When a delegate is swapped out, there may be unresolved state |
441 | // that the delegate received. This method is called when the |
442 | // delegate is cleared or swapped out to inform it to rewind its |
443 | // state and finalize all outstanding save or saveLayer operations. |
444 | virtual void decommission() = 0; |
445 | |
446 | virtual DlCanvas* canvas() const { return nullptr; } |
447 | |
448 | virtual SkRect local_cull_rect() const = 0; |
449 | virtual SkRect device_cull_rect() const = 0; |
450 | virtual SkM44 matrix_4x4() const = 0; |
451 | virtual SkMatrix matrix_3x3() const = 0; |
452 | virtual bool content_culled(const SkRect& content_bounds) const = 0; |
453 | |
454 | virtual void save() = 0; |
455 | virtual void saveLayer(const SkRect& bounds, |
456 | RenderingAttributes& attributes, |
457 | DlBlendMode blend, |
458 | const DlImageFilter* backdrop) = 0; |
459 | virtual void restore() = 0; |
460 | |
461 | virtual void translate(SkScalar tx, SkScalar ty) = 0; |
462 | virtual void transform(const SkM44& m44) = 0; |
463 | virtual void transform(const SkMatrix& matrix) = 0; |
464 | virtual void integralTransform() = 0; |
465 | |
466 | virtual void clipRect(const SkRect& rect, ClipOp op, bool is_aa) = 0; |
467 | virtual void clipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) = 0; |
468 | virtual void clipPath(const SkPath& path, ClipOp op, bool is_aa) = 0; |
469 | }; |
470 | friend class DummyDelegate; |
471 | friend class DlCanvasDelegate; |
472 | friend class PrerollDelegate; |
473 | |
474 | std::vector<std::unique_ptr<StateEntry>> state_stack_; |
475 | friend class MutatorContext; |
476 | |
477 | std::shared_ptr<Delegate> delegate_; |
478 | RenderingAttributes outstanding_; |
479 | CheckerboardFunc checkerboard_func_ = nullptr; |
480 | |
481 | friend class SaveLayerEntry; |
482 | }; |
483 | |
484 | } // namespace flutter |
485 | |
486 | #endif // FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ |
487 | |