blob: 65c1076df1361068d94dd727bf7e2e0f905e50f4 [file] [log] [blame] [edit]
// 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/display_list_builder.h"
#include "flutter/display_list/display_list_canvas_recorder.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| precisely.
///
/// The stack can manage a single state delegate. The stack will both
/// record the state internally regardless of any delegate and will also
/// apply it to a delegate as needed. The delegate can be swapped out
/// on the fly (as is typically done by PlatformViewLayer when recording
/// the state for multiple inter-embedded-view sub-trees) and 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:
/// - MutatorsStack: used during Preroll to remember the outstanding
/// state for embedded platform layers
/// - SkCanvas: used during Paint for the default output to a Skia
/// surface
/// - DisplayListBuilder: used during Paint to construct a DisplayList
/// for Impeller 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);
///
/// // 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:
explicit LayerStateStack(const SkRect* cull_rect = nullptr);
CheckerboardFunc checkerboard_func() const { return checkerboard_func_; }
void set_checkerboard_func(CheckerboardFunc checkerboard_func) {
checkerboard_func_ = checkerboard_func;
}
// Clears out any old delegate to make room for a new one.
void clear_delegate();
// Return the SkCanvas delegate if the state stack has such a delegate.
// The state stack will only have one of an SkCanvas, Builder, or Mutators
// delegate at any given time.
// See also |builder_delegate| and |mutators_delegate|.
SkCanvas* canvas_delegate() { return canvas_; }
// Return the DisplayListBuilder delegate if the state stack has such a
// delegate.
// The state stack will only have one of an SkCanvas, Builder, or Mutators
// delegate at any given time.
// See also |builder_delegate| and |mutators_delegate|.
DisplayListBuilder* builder_delegate() { return builder_; }
// Return the MutatorsStack delegate if the state stack has such a
// delegate.
// The state stack will only have one of an SkCanvas, Builder, or Mutators
// delegate at any given time.
// See also |builder_delegate| and |mutators_delegate|.
MutatorsStack* mutators_delegate() { return mutators_; }
// Clears the old delegate and sets the canvas delegate to the indicated
// canvas (if not nullptr). This ensures that only one delegate - either
// a canvas, a builder, or mutator stack - is present at any one time.
void set_delegate(SkCanvas* canvas);
// Clears the old delegate and sets the builder delegate to the indicated
// buider (if not nullptr). This ensures that only one delegate - either
// a canvas, a builder, or mutator stack - is present at any one time.
void set_delegate(DisplayListBuilder* builder);
void set_delegate(sk_sp<DisplayListBuilder> builder) {
set_delegate(builder.get());
}
void set_delegate(DisplayListCanvasRecorder& recorder) {
set_delegate(recorder.builder().get());
}
// Clears the old delegate and sets the mutators delegate to the indicated
// MutatorsStack (if not null). This ensures that only one delegate - either
// a canvas, a builder, or mutator stack - is present at any one time.
void set_delegate(MutatorsStack* stack);
// Overrides the initial cull rect and/or transform when it is not known at
// the time that the LayerStateStack is constructed. Must be called before
// any state has been pushed on the stack.
void set_initial_cull_rect(const SkRect& cull_rect);
void set_initial_transform(const SkMatrix& matrix);
void set_initial_transform(const SkM44& matrix);
void set_initial_state(const SkRect& cull_rect, const SkMatrix& matrix);
void set_initial_state(const SkRect& cull_rect, const SkM44& matrix);
class AutoRestore {
public:
~AutoRestore();
protected:
LayerStateStack* layer_state_stack_;
private:
AutoRestore(LayerStateStack* stack);
friend class LayerStateStack;
const size_t stack_restore_count_;
};
static constexpr int kCallerCanApplyOpacity = 0x1;
static constexpr int kCallerCanApplyColorFilter = 0x2;
static constexpr int kCallerCanApplyImageFilter = 0x4;
static constexpr int kCallerCanApplyAnything =
(kCallerCanApplyOpacity | kCallerCanApplyColorFilter |
kCallerCanApplyImageFilter);
class MutatorContext : public AutoRestore {
public:
// 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) : AutoRestore(stack) {}
friend class LayerStateStack;
};
// 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]] AutoRestore applyState(const SkRect& bounds,
int can_apply_flags = 0);
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;
}
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.
SkPaint* fill(SkPaint& paint) const { return outstanding_.fill(paint); }
// 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); }
SkRect device_cull_rect() const { return cull_rect_; }
SkRect local_cull_rect() const;
SkM44 transform_4x4() const { return matrix_; }
SkMatrix transform_3x3() const { return matrix_.asM33(); }
// Tests if painting content with the current outstanding attributes
// will produce any content.
bool painting_is_nop() const { return outstanding_.opacity <= 0; }
// Tests if painting content with the given bounds will produce any output.
bool content_culled(const SkRect& content_bounds) const;
// 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]] MutatorContext save();
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 save_layer(const SkRect& bounds);
void maybe_save_layer_for_transform();
void maybe_save_layer_for_clip();
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);
// ---------------------
void intersect_cull_rect(const SkRect& clip, SkClipOp op, bool is_aa);
void intersect_cull_rect(const SkRRect& clip, SkClipOp op, bool is_aa);
void intersect_cull_rect(const SkPath& clip, SkClipOp op, bool is_aa);
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;
SkPaint* fill(SkPaint& paint,
DlBlendMode mode = DlBlendMode::kSrcOver) const;
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 {}
};
class AttributesEntry : public StateEntry {
public:
AttributesEntry(RenderingAttributes attributes) : attributes_(attributes) {}
virtual void apply(LayerStateStack* stack) const override {}
void restore(LayerStateStack* stack) const override;
private:
const RenderingAttributes attributes_;
};
class SaveEntry : public StateEntry {
public:
SaveEntry() = default;
void apply(LayerStateStack* stack) const override;
void restore(LayerStateStack* stack) const override;
protected:
virtual void do_checkerboard(LayerStateStack* stack) const {}
};
class SaveLayerEntry : public SaveEntry {
public:
SaveLayerEntry(const SkRect& bounds, DlBlendMode blend_mode)
: bounds_(bounds), blend_mode_(blend_mode) {}
void apply(LayerStateStack* stack) const override;
protected:
const SkRect bounds_;
const DlBlendMode blend_mode_;
void do_checkerboard(LayerStateStack* stack) const override;
};
class OpacityEntry : public StateEntry {
public:
OpacityEntry(const SkRect& bounds, SkScalar opacity)
: bounds_(bounds), opacity_(opacity) {}
void apply(LayerStateStack* stack) const override;
void restore(LayerStateStack* stack) const override;
private:
const SkRect bounds_;
const SkScalar opacity_;
};
class ImageFilterEntry : public StateEntry {
public:
ImageFilterEntry(const SkRect& bounds,
const std::shared_ptr<const DlImageFilter>& filter)
: bounds_(bounds), filter_(filter) {}
~ImageFilterEntry() override = default;
void apply(LayerStateStack* stack) const override;
private:
const SkRect bounds_;
const std::shared_ptr<const DlImageFilter> filter_;
};
class ColorFilterEntry : public StateEntry {
public:
ColorFilterEntry(const SkRect& bounds,
const std::shared_ptr<const DlColorFilter>& filter)
: bounds_(bounds), filter_(filter) {}
~ColorFilterEntry() override = default;
void apply(LayerStateStack* stack) const override;
private:
const SkRect bounds_;
const std::shared_ptr<const DlColorFilter> filter_;
};
class BackdropFilterEntry : public SaveLayerEntry {
public:
BackdropFilterEntry(const SkRect& bounds,
const std::shared_ptr<const DlImageFilter>& filter,
DlBlendMode blend_mode)
: SaveLayerEntry(bounds, blend_mode), filter_(filter) {}
~BackdropFilterEntry() override = default;
void apply(LayerStateStack* stack) const override;
void restore(LayerStateStack* stack) const override;
void reapply(LayerStateStack* stack) const override;
private:
const std::shared_ptr<const DlImageFilter> filter_;
friend class LayerStateStack;
};
class TransformEntry : public StateEntry {
public:
TransformEntry(const SkM44& matrix) : previous_matrix_(matrix) {}
void restore(LayerStateStack* stack) const override;
private:
const SkM44 previous_matrix_;
};
class TranslateEntry : public TransformEntry {
public:
TranslateEntry(const SkM44& previous_matrix, SkScalar tx, SkScalar ty)
: TransformEntry(previous_matrix), tx_(tx), ty_(ty) {}
void apply(LayerStateStack* stack) const override;
private:
const SkScalar tx_;
const SkScalar ty_;
};
class TransformMatrixEntry : public TransformEntry {
public:
TransformMatrixEntry(const SkM44 previous_matrix, const SkMatrix& matrix)
: TransformEntry(previous_matrix), matrix_(matrix) {}
void apply(LayerStateStack* stack) const override;
private:
const SkMatrix matrix_;
};
class TransformM44Entry : public TransformEntry {
public:
TransformM44Entry(const SkM44 previous_matrix, const SkM44& m44)
: TransformEntry(previous_matrix), m44_(m44) {}
void apply(LayerStateStack* stack) const override;
private:
const SkM44 m44_;
};
class IntegralTransformEntry : public TransformEntry {
public:
IntegralTransformEntry(const SkM44 previous_matrix)
: TransformEntry(previous_matrix) {}
void apply(LayerStateStack* stack) const override;
};
class ClipEntry : public StateEntry {
protected:
ClipEntry(const SkRect& cull_rect, bool is_aa)
: previous_cull_rect_(cull_rect), is_aa_(is_aa) {}
void restore(LayerStateStack* stack) const override;
const SkRect previous_cull_rect_;
const bool is_aa_;
};
class ClipRectEntry : public ClipEntry {
public:
ClipRectEntry(const SkRect& cull_rect, const SkRect& clip_rect, bool is_aa)
: ClipEntry(cull_rect, is_aa), clip_rect_(clip_rect) {}
void apply(LayerStateStack* stack) const override;
private:
const SkRect clip_rect_;
};
class ClipRRectEntry : public ClipEntry {
public:
ClipRRectEntry(const SkRect& cull_rect,
const SkRRect& clip_rrect,
bool is_aa)
: ClipEntry(cull_rect, is_aa), clip_rrect_(clip_rrect) {}
void apply(LayerStateStack* stack) const override;
private:
const SkRRect clip_rrect_;
};
class ClipPathEntry : public ClipEntry {
public:
ClipPathEntry(const SkRect& cull_rect, const SkPath& clip_path, bool is_aa)
: ClipEntry(cull_rect, is_aa), clip_path_(clip_path) {}
~ClipPathEntry() override = default;
void apply(LayerStateStack* stack) const override;
private:
const SkPath clip_path_;
};
std::vector<std::unique_ptr<StateEntry>> state_stack_;
friend class MutatorContext;
SkM44 initial_matrix_;
SkM44 matrix_;
SkRect initial_cull_rect_;
SkRect cull_rect_;
SkCanvas* canvas_ = nullptr;
DisplayListBuilder* builder_ = nullptr;
MutatorsStack* mutators_ = nullptr;
int restore_count_ = 0;
RenderingAttributes outstanding_;
CheckerboardFunc checkerboard_func_ = nullptr;
friend class SaveLayerEntry;
};
} // namespace flutter
#endif // FLUTTER_FLOW_LAYERS_LAYER_STATE_STACK_H_