| // 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_LAYER_STATE_STACK_H_ |
| #define FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ |
| |
| #include "flutter/display_list/dl_canvas.h" |
| #include "flutter/flow/embedded_views.h" |
| #include "flutter/flow/paint_utils.h" |
| |
| namespace flutter { |
| |
| /// The LayerStateStack manages the inherited state passed down between |
| /// |Layer| objects in a |LayerTree| during |Preroll| and |Paint|. |
| /// |
| /// More specifically, it manages the clip and transform state during |
| /// recursive rendering and will hold and lazily apply opacity, ImageFilter |
| /// and ColorFilter attributes to recursive content. This is not a truly |
| /// general state management mechnanism as it makes assumptions that code |
| /// will be applying the attributes to rendered content that happens in |
| /// recursive calls. The automatic save/restore mechanisms only work in |
| /// a context where C++ auto-destruct calls will engage the restore at |
| /// the end of a code block and that any applied attributes will only |
| /// be applied to the content rendered inside that block. These restrictions |
| /// match the organization of the |LayerTree| methods precisely. |
| /// |
| /// The stack can manage a single state delegate. The delegate will provide |
| /// tracking of the current transform and clip and will also execute |
| /// saveLayer calls at the appropriate time if it is a rendering delegate. |
| /// The delegate can be swapped out on the fly (as is typically done by |
| /// PlatformViewLayer when recording the state for multiple "overlay" |
| /// layers that occur between embedded view subtrees. The old delegate |
| /// will be restored to its original state before it became a delegate |
| /// and the new delegate will have all of the state recorded by the stack |
| /// replayed into it to bring it up to speed with the current rendering |
| /// context. |
| /// |
| /// The delegate can be any one of: |
| /// - Preroll delegate: used during Preroll to remember the outstanding |
| /// state for embedded platform layers |
| /// - DlCanvas: used during Paint for rendering output |
| /// The stack will know which state needs to be conveyed to any of these |
| /// delegates and when is the best time to convey that state (i.e. lazy |
| /// saveLayer calls for example). |
| /// |
| /// The rendering state attributes will be automatically applied to the |
| /// nested content using a |saveLayer| call at the point at which we |
| /// encounter rendered content (i.e. various nested layers that exist only |
| /// to apply new state will not trigger the |saveLayer| and the attributes |
| /// can accumulate until we reach actual content that is rendered.) Some |
| /// rendered content can avoid the |saveLayer| if it reports to the object |
| /// that it is able to apply all of the attributes that happen to be |
| /// outstanding (accumulated from parent state-modifiers). A |ContainerLayer| |
| /// can also monitor the attribute rendering capabilities of a list of |
| /// children and can ask the object to apply a protective |saveLayer| or |
| /// not based on the negotiated capabilities of the entire group. |
| /// |
| /// Any code that is planning to modify the clip, transform, or rendering |
| /// attributes for its child content must start by calling the |save| method |
| /// which returns a MutatorContext object. The methods that modify such |
| /// state only exist on the MutatorContext object so it is difficult to get |
| /// that wrong, but the caller must make sure that the call happens within |
| /// a C++ code block that will define the "rendering scope" of those |
| /// state changes as they will be automatically restored on exit from that |
| /// block. Note that the layer might make similar state calls directly on |
| /// the canvas or builder during the Paint cycle (via saveLayer, transform, |
| /// or clip calls), but should avoid doing so if there is any nested content |
| /// that needs to track or react to those state calls. |
| /// |
| /// Code that needs to render content can simply inform the parent of their |
| /// abilities by setting the |PrerollContext::renderable_state_flags| during |
| /// |Preroll| and then render with those attributes during |Paint| by |
| /// requesting the outstanding values of those attributes from the state_stack |
| /// object. Individual leaf layers can ignore this feature as the default |
| /// behavior during |Preroll| will have their parent |ContainerLayer| assume |
| /// that they cannot render any outstanding state attributes and will apply |
| /// the protective saveLayer on their behalf if needed. As such, this object |
| /// only provides "opt-in" features for leaf layers and no responsibilities |
| /// otherwise. |
| /// See |LayerStateStack::fill| |
| /// See |LayerStateStack::outstanding_opacity| |
| /// See |LayerStateStack::outstanding_color_filter| |
| /// See |LayerStateStack::outstanding_image_filter| |
| /// |
| /// State-modifying layers should contain code similar to this pattern in both |
| /// their |Preroll| and |Paint| methods. |
| /// |
| /// void [LayerType]::[Preroll/Paint](context) { |
| /// auto mutator = context.state_stack.save(); |
| /// mutator.translate(origin.x, origin.y); |
| /// mutator.applyOpacity(content_bounds, opacity_value); |
| /// mutator.applyColorFilter(content_bounds, color_filter); |
| /// // or any of the mutator transform, clip or attribute methods |
| /// |
| /// // Children will react to the state applied above during their |
| /// // Preroll/Paint methods or ContainerLayer will protect them |
| /// // conservatively by default. |
| /// [Preroll/Paint]Children(context); |
| /// |
| /// // here the mutator will be auto-destructed and the state accumulated |
| /// // by it will be restored out of the state_stack and its associated |
| /// // delegates. |
| /// } |
| class LayerStateStack { |
| public: |
| LayerStateStack(); |
| |
| // Clears out any old delegate to make room for a new one. |
| void clear_delegate(); |
| |
| // Return the DlCanvas delegate if the state stack has such a delegate. |
| // The state stack will only have one delegate at a time holding either |
| // a DlCanvas or a preroll accumulator. |
| DlCanvas* canvas_delegate() { return delegate_->canvas(); } |
| |
| // Clears the old delegate and sets the canvas delegate to the indicated |
| // DL canvas (if not nullptr). This ensures that only one delegate - either |
| // a DlCanvas or a preroll accumulator - is present at any one time. |
| void set_delegate(DlCanvas* canvas); |
| |
| // Clears the old delegate and sets the state stack up to accumulate |
| // clip and transform information for a Preroll phase. This ensures |
| // that only one delegate - either a DlCanvas or a preroll accumulator - |
| // is present at any one time. |
| void set_preroll_delegate(const SkRect& cull_rect, const SkMatrix& matrix); |
| void set_preroll_delegate(const SkRect& cull_rect); |
| void set_preroll_delegate(const SkMatrix& matrix); |
| |
| // Fills the supplied MatatorsStack object with the mutations recorded |
| // by this LayerStateStack in the order encountered. |
| void fill(MutatorsStack* mutators); |
| |
| // Sets up a checkerboard function that will be used to checkerboard the |
| // contents of any saveLayer executed by the state stack. |
| CheckerboardFunc checkerboard_func() const { return checkerboard_func_; } |
| void set_checkerboard_func(CheckerboardFunc checkerboard_func) { |
| checkerboard_func_ = checkerboard_func; |
| } |
| |
| class AutoRestore { |
| public: |
| ~AutoRestore() { |
| layer_state_stack_->restore_to_count(stack_restore_count_); |
| } |
| |
| private: |
| AutoRestore(LayerStateStack* stack, const SkRect& bounds, int flags) |
| : layer_state_stack_(stack), |
| stack_restore_count_(stack->stack_count()) { |
| if (stack->needs_save_layer(flags)) { |
| stack->save_layer(bounds); |
| } |
| } |
| friend class LayerStateStack; |
| |
| LayerStateStack* layer_state_stack_; |
| const size_t stack_restore_count_; |
| |
| FML_DISALLOW_COPY_ASSIGN_AND_MOVE(AutoRestore); |
| }; |
| |
| class MutatorContext { |
| public: |
| ~MutatorContext() { |
| layer_state_stack_->restore_to_count(stack_restore_count_); |
| } |
| |
| // Immediately executes a saveLayer with all accumulated state |
| // onto the canvas or builder to be applied at the next matching |
| // restore. A saveLayer is always executed by this method even if |
| // there are no outstanding attributes. |
| void saveLayer(const SkRect& bounds); |
| |
| // Records the opacity for application at the next call to |
| // saveLayer or applyState. A saveLayer may be executed at |
| // this time if the opacity cannot be batched with other |
| // outstanding attributes. |
| void applyOpacity(const SkRect& bounds, SkScalar opacity); |
| |
| // Records the image filter for application at the next call to |
| // saveLayer or applyState. A saveLayer may be executed at |
| // this time if the image filter cannot be batched with other |
| // outstanding attributes. |
| // (Currently only opacity is recorded for batching) |
| void applyImageFilter(const SkRect& bounds, |
| const std::shared_ptr<const DlImageFilter>& filter); |
| |
| // Records the color filter for application at the next call to |
| // saveLayer or applyState. A saveLayer may be executed at |
| // this time if the color filter cannot be batched with other |
| // outstanding attributes. |
| // (Currently only opacity is recorded for batching) |
| void applyColorFilter(const SkRect& bounds, |
| const std::shared_ptr<const DlColorFilter>& filter); |
| |
| // Saves the state stack and immediately executes a saveLayer |
| // with the indicated backdrop filter and any outstanding |
| // state attributes. Since the backdrop filter only applies |
| // to the pixels alrady on the screen when this call is made, |
| // the backdrop filter will only be applied to the canvas or |
| // builder installed at the time that this call is made, and |
| // subsequent canvas or builder objects that are made delegates |
| // will only see a saveLayer with the indicated blend_mode. |
| void applyBackdropFilter(const SkRect& bounds, |
| const std::shared_ptr<const DlImageFilter>& filter, |
| DlBlendMode blend_mode); |
| |
| void translate(SkScalar tx, SkScalar ty); |
| void translate(SkPoint tp) { translate(tp.fX, tp.fY); } |
| void transform(const SkM44& m44); |
| void transform(const SkMatrix& matrix); |
| void integralTransform(); |
| |
| void clipRect(const SkRect& rect, bool is_aa); |
| void clipRRect(const SkRRect& rrect, bool is_aa); |
| void clipPath(const SkPath& path, bool is_aa); |
| |
| private: |
| explicit MutatorContext(LayerStateStack* stack) |
| : layer_state_stack_(stack), |
| stack_restore_count_(stack->stack_count()) {} |
| friend class LayerStateStack; |
| |
| LayerStateStack* layer_state_stack_; |
| const size_t stack_restore_count_; |
| bool save_needed_ = true; |
| |
| FML_DISALLOW_COPY_ASSIGN_AND_MOVE(MutatorContext); |
| }; |
| |
| static constexpr int kCallerCanApplyOpacity = 0x1; |
| static constexpr int kCallerCanApplyColorFilter = 0x2; |
| static constexpr int kCallerCanApplyImageFilter = 0x4; |
| static constexpr int kCallerCanApplyAnything = |
| (kCallerCanApplyOpacity | kCallerCanApplyColorFilter | |
| kCallerCanApplyImageFilter); |
| |
| // Apply the outstanding state via saveLayer if necessary, |
| // respecting the flags representing which potentially |
| // outstanding attributes the calling layer can apply |
| // themselves. |
| // |
| // A saveLayer may or may not be sent to the delegates depending |
| // on how the outstanding state intersects with the flags supplied |
| // by the caller. |
| // |
| // An AutoRestore instance will always be returned even if there |
| // was no saveLayer applied. |
| [[nodiscard]] inline AutoRestore applyState(const SkRect& bounds, |
| int can_apply_flags) { |
| return AutoRestore(this, bounds, can_apply_flags); |
| } |
| |
| SkScalar outstanding_opacity() const { return outstanding_.opacity; } |
| |
| std::shared_ptr<const DlColorFilter> outstanding_color_filter() const { |
| return outstanding_.color_filter; |
| } |
| |
| std::shared_ptr<const DlImageFilter> outstanding_image_filter() const { |
| return outstanding_.image_filter; |
| } |
| |
| // The outstanding bounds are the bounds recorded during the last |
| // attribute applied to this state stack. The assumption is that |
| // the nested calls to the state stack will each supply bounds relative |
| // to the content of that single attribute and the bounds of the content |
| // of any outstanding attributes will include the output bounds of |
| // applying any nested attributes. Thus, only the innermost content |
| // bounds received will be sufficient to apply all outstanding attributes. |
| SkRect outstanding_bounds() const { return outstanding_.save_layer_bounds; } |
| |
| // Fill the provided paint object with any oustanding attributes and |
| // return a pointer to it, or return a nullptr if there were no |
| // outstanding attributes to paint with. |
| DlPaint* fill(DlPaint& paint) const { return outstanding_.fill(paint); } |
| |
| // The cull_rect (not the exact clip) relative to the device pixels. |
| // This rectangle may be a conservative estimate of the true clip region. |
| SkRect device_cull_rect() const { return delegate_->device_cull_rect(); } |
| |
| // The cull_rect (not the exact clip) relative to the local coordinates. |
| // This rectangle may be a conservative estimate of the true clip region. |
| SkRect local_cull_rect() const { return delegate_->local_cull_rect(); } |
| |
| // The transform from the local coordinates to the device coordinates |
| // in the most capable 4x4 matrix representation. This matrix may be |
| // more information than is needed to compute bounds for a 2D rendering |
| // primitive, but it will accurately concatenate with other 4x4 matrices |
| // without losing information. |
| SkM44 transform_4x4() const { return delegate_->matrix_4x4(); } |
| |
| // The transform from the local coordinates to the device coordinates |
| // in a more compact 3x3 matrix represenation that provides enough |
| // information to accurately transform 2D primitives into their |
| // resulting 2D bounds. This matrix also has enough information to |
| // concat with other 2D affine transforms, but does not carry enough |
| // information to accurately concat with fully perspective matrics. |
| SkMatrix transform_3x3() const { return delegate_->matrix_3x3(); } |
| |
| // Tests if painting content with the current outstanding attributes |
| // will produce any content. This method does not check the current |
| // transform or clip for being singular or empty. |
| // See |content_culled| |
| bool painting_is_nop() const { return outstanding_.opacity <= 0; } |
| |
| // Tests if painting content with the given bounds will produce any output. |
| // This method does not check the outstanding attributes to verify that |
| // they produce visible results. |
| // See |painting_is_nop| |
| bool content_culled(const SkRect& content_bounds) const { |
| return delegate_->content_culled(content_bounds); |
| } |
| |
| // Saves the current state of the state stack and returns a |
| // MutatorContext which can be used to manipulate the state. |
| // The state stack will be restored to its current state |
| // when the MutatorContext object goes out of scope. |
| [[nodiscard]] inline MutatorContext save() { return MutatorContext(this); } |
| |
| // Returns true if the state stack is in, or has returned to, |
| // its initial state. |
| bool is_empty() const { return state_stack_.empty(); } |
| |
| private: |
| size_t stack_count() const { return state_stack_.size(); } |
| void restore_to_count(size_t restore_count); |
| void reapply_all(); |
| |
| void apply_last_entry() { state_stack_.back()->apply(this); } |
| |
| // The push methods simply push an associated StateEntry on the stack |
| // and then apply it to the current canvas and builder. |
| // --------------------- |
| // void push_attributes(); |
| void push_opacity(const SkRect& rect, SkScalar opacity); |
| void push_color_filter(const SkRect& bounds, |
| const std::shared_ptr<const DlColorFilter>& filter); |
| void push_image_filter(const SkRect& bounds, |
| const std::shared_ptr<const DlImageFilter>& filter); |
| void push_backdrop(const SkRect& bounds, |
| const std::shared_ptr<const DlImageFilter>& filter, |
| DlBlendMode blend_mode); |
| |
| void push_translate(SkScalar tx, SkScalar ty); |
| void push_transform(const SkM44& matrix); |
| void push_transform(const SkMatrix& matrix); |
| void push_integral_transform(); |
| |
| void push_clip_rect(const SkRect& rect, bool is_aa); |
| void push_clip_rrect(const SkRRect& rrect, bool is_aa); |
| void push_clip_path(const SkPath& path, bool is_aa); |
| // --------------------- |
| |
| // The maybe/needs_save_layer methods will determine if the indicated |
| // attribute can be incorporated into the outstanding attributes as is, |
| // or if the apply_flags are compatible with the outstanding attributes. |
| // If the oustanding attributes are incompatible with the new attribute |
| // or the apply flags, then a protective saveLayer will be executed. |
| // --------------------- |
| bool needs_save_layer(int flags) const; |
| void do_save(); |
| void save_layer(const SkRect& bounds); |
| void maybe_save_layer_for_transform(bool needs_save); |
| void maybe_save_layer_for_clip(bool needs_save); |
| void maybe_save_layer(int apply_flags); |
| void maybe_save_layer(SkScalar opacity); |
| void maybe_save_layer(const std::shared_ptr<const DlColorFilter>& filter); |
| void maybe_save_layer(const std::shared_ptr<const DlImageFilter>& filter); |
| // --------------------- |
| |
| struct RenderingAttributes { |
| // We need to record the last bounds we received for the last |
| // attribute that we recorded so that we can perform a saveLayer |
| // on the proper area. When an attribute is applied that cannot |
| // be merged with the existing attributes, it will be submitted |
| // with a bounds for its own source content, not the bounds for |
| // the content that will be included in the saveLayer that applies |
| // the existing outstanding attributes - thus we need to record |
| // the bounds that were supplied with the most recent previous |
| // attribute to be applied. |
| SkRect save_layer_bounds{0, 0, 0, 0}; |
| |
| SkScalar opacity = SK_Scalar1; |
| std::shared_ptr<const DlColorFilter> color_filter; |
| std::shared_ptr<const DlImageFilter> image_filter; |
| |
| DlPaint* fill(DlPaint& paint, |
| DlBlendMode mode = DlBlendMode::kSrcOver) const; |
| |
| bool operator==(const RenderingAttributes& other) const { |
| return save_layer_bounds == other.save_layer_bounds && |
| opacity == other.opacity && |
| Equals(color_filter, other.color_filter) && |
| Equals(image_filter, other.image_filter); |
| } |
| }; |
| |
| class StateEntry { |
| public: |
| virtual ~StateEntry() = default; |
| |
| virtual void apply(LayerStateStack* stack) const = 0; |
| virtual void reapply(LayerStateStack* stack) const { apply(stack); } |
| virtual void restore(LayerStateStack* stack) const {} |
| virtual void update_mutators(MutatorsStack* mutators_stack) const {} |
| |
| protected: |
| StateEntry() = default; |
| |
| FML_DISALLOW_COPY_ASSIGN_AND_MOVE(StateEntry); |
| }; |
| friend class SaveEntry; |
| friend class SaveLayerEntry; |
| friend class BackdropFilterEntry; |
| friend class OpacityEntry; |
| friend class ImageFilterEntry; |
| friend class ColorFilterEntry; |
| friend class TranslateEntry; |
| friend class TransformMatrixEntry; |
| friend class TransformM44Entry; |
| friend class IntegralTransformEntry; |
| friend class ClipEntry; |
| friend class ClipRectEntry; |
| friend class ClipRRectEntry; |
| friend class ClipPathEntry; |
| |
| class Delegate { |
| protected: |
| using ClipOp = DlCanvas::ClipOp; |
| |
| public: |
| virtual ~Delegate() = default; |
| |
| // Mormally when a |Paint| or |Preroll| cycle is completed, the |
| // delegate will have been rewound to its initial state by the |
| // trailing recursive actions of the paint and preroll methods. |
| // When a delegate is swapped out, there may be unresolved state |
| // that the delegate received. This method is called when the |
| // delegate is cleared or swapped out to inform it to rewind its |
| // state and finalize all outstanding save or saveLayer operations. |
| virtual void decommission() = 0; |
| |
| virtual DlCanvas* canvas() const { return nullptr; } |
| |
| virtual SkRect local_cull_rect() const = 0; |
| virtual SkRect device_cull_rect() const = 0; |
| virtual SkM44 matrix_4x4() const = 0; |
| virtual SkMatrix matrix_3x3() const = 0; |
| virtual bool content_culled(const SkRect& content_bounds) const = 0; |
| |
| virtual void save() = 0; |
| virtual void saveLayer(const SkRect& bounds, |
| RenderingAttributes& attributes, |
| DlBlendMode blend, |
| const DlImageFilter* backdrop) = 0; |
| virtual void restore() = 0; |
| |
| virtual void translate(SkScalar tx, SkScalar ty) = 0; |
| virtual void transform(const SkM44& m44) = 0; |
| virtual void transform(const SkMatrix& matrix) = 0; |
| virtual void integralTransform() = 0; |
| |
| virtual void clipRect(const SkRect& rect, ClipOp op, bool is_aa) = 0; |
| virtual void clipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) = 0; |
| virtual void clipPath(const SkPath& path, ClipOp op, bool is_aa) = 0; |
| }; |
| friend class DummyDelegate; |
| friend class DlCanvasDelegate; |
| friend class PrerollDelegate; |
| |
| std::vector<std::unique_ptr<StateEntry>> state_stack_; |
| friend class MutatorContext; |
| |
| std::shared_ptr<Delegate> delegate_; |
| RenderingAttributes outstanding_; |
| CheckerboardFunc checkerboard_func_ = nullptr; |
| |
| friend class SaveLayerEntry; |
| }; |
| |
| } // namespace flutter |
| |
| #endif // FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_ |