mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
487 lines
21 KiB
C++
487 lines
21 KiB
C++
// 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:
|
|
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<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_
|