blob: ec5ea4932b8ce6f970559ea2e388aa864942e3b9 [file] [log] [blame]
// 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_