// 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& 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& 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& 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: MutatorContext(LayerStateStack* stack) : layer_state_stack_(stack), stack_restore_count_(stack->stack_count()), save_needed_(true) {} friend class LayerStateStack; LayerStateStack* layer_state_stack_; const size_t stack_restore_count_; bool save_needed_; 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 outstanding_color_filter() const { return outstanding_.color_filter; } std::shared_ptr 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& filter); void push_image_filter(const SkRect& bounds, const std::shared_ptr& filter); void push_backdrop(const SkRect& bounds, const std::shared_ptr& 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& filter); void maybe_save_layer(const std::shared_ptr& 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 color_filter; std::shared_ptr 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> state_stack_; friend class MutatorContext; std::shared_ptr delegate_; RenderingAttributes outstanding_; CheckerboardFunc checkerboard_func_ = nullptr; friend class SaveLayerEntry; }; } // namespace flutter #endif // FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_