[go: nahoru, domu]

blob: f48c35cf7e82eb1b7743f6bf60e543eabf891141 [file] [log] [blame]
// Copyright 2015 The Chromium 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_
#include <algorithm>
#include "cc/trees/sticky_position_constraint.h"
#include "third_party/blink/renderer/platform/geometry/float_point_3d.h"
#include "third_party/blink/renderer/platform/graphics/compositing_reasons.h"
#include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_property_node.h"
#include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
using CompositorStickyConstraint = cc::StickyPositionConstraint;
// A transform (e.g., created by css "transform" or "perspective", or for
// internal positioning such as paint offset or scrolling) along with a
// reference to the parent TransformPaintPropertyNode. The scroll tree is
// referenced by transform nodes and a transform node with an associated scroll
// node will be a 2d transform for scroll offset.
//
// The transform tree is rooted at a node with no parent. This root node should
// not be modified.
class PLATFORM_EXPORT TransformPaintPropertyNode
: public PaintPropertyNode<TransformPaintPropertyNode> {
public:
enum class BackfaceVisibility : unsigned char {
// backface-visibility is not inherited per the css spec. However, for an
// element that don't create a new plane, for now we let the element
// inherit the parent backface-visibility.
kInherited,
// backface-visibility: hidden for the new plane.
kHidden,
// backface-visibility: visible for the new plane.
kVisible,
};
// Stores a transform and origin with an optimization for the identity and
// 2d translation cases that avoids allocating a full matrix and origin.
class TransformAndOrigin {
DISALLOW_NEW();
public:
TransformAndOrigin() {}
// These constructors are not explicit so that we can use FloatSize or
// TransformationMatrix directly in the initialization list of State.
TransformAndOrigin(const FloatSize& translation_2d)
: translation_2d_(translation_2d) {}
// This should be used for arbitrary matrix only. If the caller knows that
// the transform is identity or a 2d translation, the translation_2d version
// should be used instead.
TransformAndOrigin(const TransformationMatrix& matrix,
const FloatPoint3D& origin = FloatPoint3D(),
bool disable_optimization = false) {
if (!disable_optimization && matrix.IsIdentityOr2DTranslation())
translation_2d_ = matrix.To2DTranslation();
else
matrix_and_origin_.reset(new MatrixAndOrigin{matrix, origin});
}
bool IsIdentityOr2DTranslation() const { return !matrix_and_origin_; }
bool IsIdentity() const {
return !matrix_and_origin_ && translation_2d_.IsZero();
}
const FloatSize& Translation2D() const {
DCHECK(IsIdentityOr2DTranslation());
return translation_2d_;
}
const TransformationMatrix& Matrix() const {
DCHECK(matrix_and_origin_);
return matrix_and_origin_->matrix;
}
TransformationMatrix SlowMatrix() const {
return matrix_and_origin_
? matrix_and_origin_->matrix
: TransformationMatrix().Translate(translation_2d_.Width(),
translation_2d_.Height());
}
FloatPoint3D Origin() const {
return matrix_and_origin_ ? matrix_and_origin_->origin : FloatPoint3D();
}
bool TransformEquals(const TransformAndOrigin& other) const {
return translation_2d_ == other.translation_2d_ &&
((!matrix_and_origin_ && !other.matrix_and_origin_) ||
(matrix_and_origin_ && other.matrix_and_origin_ &&
matrix_and_origin_->matrix == other.matrix_and_origin_->matrix));
}
bool ChangePreserves2dAxisAlignment(const TransformAndOrigin& other) const {
if (IsIdentityOr2DTranslation() && other.IsIdentityOr2DTranslation())
return true;
if (IsIdentityOr2DTranslation())
return other.Matrix().Preserves2dAxisAlignment();
if (other.IsIdentityOr2DTranslation())
return Matrix().Preserves2dAxisAlignment();
// TODO(crbug.com/960481): Consider more rare corner cases.
return (Matrix().Inverse() * other.Matrix()).Preserves2dAxisAlignment();
}
private:
struct MatrixAndOrigin {
TransformationMatrix matrix;
FloatPoint3D origin;
};
FloatSize translation_2d_;
std::unique_ptr<MatrixAndOrigin> matrix_and_origin_;
};
struct AnimationState {
AnimationState() {}
bool is_running_animation_on_compositor = false;
};
// To make it less verbose and more readable to construct and update a node,
// a struct with default values is used to represent the state.
struct State {
TransformAndOrigin transform_and_origin;
scoped_refptr<const ScrollPaintPropertyNode> scroll;
// Use bitfield packing instead of separate bools to save space.
struct Flags {
bool flattens_inherited_transform : 1;
bool affected_by_outer_viewport_bounds_delta : 1;
bool in_subtree_of_page_scale : 1;
bool animation_is_axis_aligned : 1;
} flags = {false, false, true, false};
BackfaceVisibility backface_visibility = BackfaceVisibility::kInherited;
unsigned rendering_context_id = 0;
CompositingReasons direct_compositing_reasons = CompositingReason::kNone;
CompositorElementId compositor_element_id;
std::unique_ptr<CompositorStickyConstraint> sticky_constraint;
PaintPropertyChangeType ComputeChange(
const State& other,
const AnimationState& animation_state) const {
if (flags.flattens_inherited_transform !=
other.flags.flattens_inherited_transform ||
flags.affected_by_outer_viewport_bounds_delta !=
other.flags.affected_by_outer_viewport_bounds_delta ||
flags.in_subtree_of_page_scale !=
other.flags.in_subtree_of_page_scale ||
flags.animation_is_axis_aligned !=
other.flags.animation_is_axis_aligned ||
backface_visibility != other.backface_visibility ||
rendering_context_id != other.rendering_context_id ||
compositor_element_id != other.compositor_element_id ||
scroll != other.scroll || !StickyConstraintEquals(other)) {
return PaintPropertyChangeType::kChangedOnlyValues;
}
bool matrix_changed =
!transform_and_origin.TransformEquals(other.transform_and_origin);
bool origin_changed =
transform_and_origin.Origin() != other.transform_and_origin.Origin();
bool transform_changed = matrix_changed || origin_changed;
bool transform_has_simple_change = true;
if (!transform_changed) {
transform_has_simple_change = false;
} else if (animation_state.is_running_animation_on_compositor) {
transform_has_simple_change = false;
} else if (matrix_changed &&
!transform_and_origin.ChangePreserves2dAxisAlignment(
other.transform_and_origin)) {
// An additional cc::EffectNode may be required if
// blink::TransformPaintPropertyNode is not axis-aligned (see:
// PropertyTreeManager::NeedsSyntheticEffect). Changes to axis alignment
// are therefore treated as non-simple. We do not need to check origin
// because axis alignment is not affected by transform origin.
transform_has_simple_change = false;
}
// If the transform changed, and it's not simple then we need to report
// values change.
if (transform_changed && !transform_has_simple_change &&
!animation_state.is_running_animation_on_compositor) {
return PaintPropertyChangeType::kChangedOnlyValues;
}
bool non_reraster_values_changed =
direct_compositing_reasons != other.direct_compositing_reasons;
// Both simple value change and non-reraster change is upgraded to value
// change.
if (non_reraster_values_changed && transform_has_simple_change)
return PaintPropertyChangeType::kChangedOnlyValues;
if (non_reraster_values_changed)
return PaintPropertyChangeType::kChangedOnlyNonRerasterValues;
if (transform_has_simple_change)
return PaintPropertyChangeType::kChangedOnlySimpleValues;
// At this point, our transform change isn't simple, and the above checks
// didn't return a values change, so it must mean that we're running a
// compositor animation here.
if (transform_changed) {
DCHECK(animation_state.is_running_animation_on_compositor);
return PaintPropertyChangeType::kChangedOnlyCompositedValues;
}
return PaintPropertyChangeType::kUnchanged;
}
bool StickyConstraintEquals(const State& other) const {
if (!sticky_constraint && !other.sticky_constraint)
return true;
return sticky_constraint && other.sticky_constraint &&
*sticky_constraint == *other.sticky_constraint;
}
};
// This node is really a sentinel, and does not represent a real transform
// space.
static const TransformPaintPropertyNode& Root();
static scoped_refptr<TransformPaintPropertyNode> Create(
const TransformPaintPropertyNode& parent,
State&& state) {
return base::AdoptRef(new TransformPaintPropertyNode(
&parent, std::move(state), false /* is_parent_alias */));
}
static scoped_refptr<TransformPaintPropertyNode> CreateAlias(
const TransformPaintPropertyNode& parent) {
return base::AdoptRef(new TransformPaintPropertyNode(
&parent, State{}, true /* is_parent_alias */));
}
PaintPropertyChangeType Update(
const TransformPaintPropertyNode& parent,
State&& state,
const AnimationState& animation_state = AnimationState()) {
auto parent_changed = SetParent(&parent);
auto state_changed = state_.ComputeChange(state, animation_state);
if (state_changed != PaintPropertyChangeType::kUnchanged) {
DCHECK(!IsParentAlias()) << "Changed the state of an alias node.";
state_ = std::move(state);
AddChanged(state_changed);
Validate();
}
return std::max(parent_changed, state_changed);
}
// If |relative_to_node| is an ancestor of |this|, returns true if any node is
// marked changed, at least significance of |change|, along the path from
// |this| to |relative_to_node| (not included). Otherwise returns the combined
// changed status of the paths from |this| and |relative_to_node| to the root.
bool Changed(PaintPropertyChangeType change,
const TransformPaintPropertyNode& relative_to_node) const;
bool IsIdentityOr2DTranslation() const {
return state_.transform_and_origin.IsIdentityOr2DTranslation();
}
bool IsIdentity() const { return state_.transform_and_origin.IsIdentity(); }
// Only available when IsIdentityOr2DTranslation() is true.
const FloatSize& Translation2D() const {
return state_.transform_and_origin.Translation2D();
}
// Only available when IsIdentityOr2DTranslation() is false.
const TransformationMatrix& Matrix() const {
return state_.transform_and_origin.Matrix();
}
TransformationMatrix MatrixWithOriginApplied() const {
return TransformationMatrix(Matrix()).ApplyTransformOrigin(Origin());
}
// The slow version always return meaningful TransformationMatrix regardless
// of IsIdentityOr2DTranslation(). Should be used only in contexts that are
// not performance sensitive.
TransformationMatrix SlowMatrix() const {
return state_.transform_and_origin.SlowMatrix();
}
FloatPoint3D Origin() const { return state_.transform_and_origin.Origin(); }
// The associated scroll node, or nullptr otherwise.
const ScrollPaintPropertyNode* ScrollNode() const {
return state_.scroll.get();
}
// If true, this node is translated by the viewport bounds delta, which is
// used to keep bottom-fixed elements appear fixed to the bottom of the
// screen in the presence of URL bar movement.
bool IsAffectedByOuterViewportBoundsDelta() const {
return state_.flags.affected_by_outer_viewport_bounds_delta;
}
// If true, this node is a descendant of the page scale transform. This is
// important for avoiding raster during pinch-zoom (see: crbug.com/951861).
bool IsInSubtreeOfPageScale() const {
return state_.flags.in_subtree_of_page_scale;
}
const CompositorStickyConstraint* GetStickyConstraint() const {
return state_.sticky_constraint.get();
}
// If this is a scroll offset translation (i.e., has an associated scroll
// node), returns this. Otherwise, returns the transform node that this node
// scrolls with respect to. This can require a full ancestor traversal.
const TransformPaintPropertyNode& NearestScrollTranslationNode() const;
// If true, content with this transform node (or its descendant) appears in
// the plane of its parent. This is implemented by flattening the total
// accumulated transform from its ancestors.
bool FlattensInheritedTransform() const {
return state_.flags.flattens_inherited_transform;
}
// Returns the local BackfaceVisibility value set on this node. To be used
// for testing only; use |BackfaceVisibilitySameAsParent()| or
// |IsBackfaceHidden()| for production code.
BackfaceVisibility GetBackfaceVisibilityForTesting() const {
return state_.backface_visibility;
}
// Returns true if the backface visibility for this node is the same as that
// of its parent. This will be true for the Root node.
bool BackfaceVisibilitySameAsParent() const {
if (IsRoot())
return true;
if (state_.backface_visibility == BackfaceVisibility::kInherited)
return true;
if (state_.backface_visibility ==
Parent()->Unalias().state_.backface_visibility)
return true;
return IsBackfaceHidden() == Parent()->Unalias().IsBackfaceHidden();
}
// Returns true if the flattens inherited transform setting for this node is
// the same as that of its parent. This will be true for the Root node.
bool FlattensInheritedTransformSameAsParent() const {
if (IsRoot())
return true;
return state_.flags.flattens_inherited_transform ==
Parent()->Unalias().state_.flags.flattens_inherited_transform;
}
// Returns the first non-inherited BackefaceVisibility value along the
// transform node ancestor chain, including this node's value if it is
// non-inherited. TODO(wangxianzhu): Let PaintPropertyTreeBuilder calculate
// the value instead of walking up the tree.
bool IsBackfaceHidden() const {
const auto* node = this;
while (node &&
node->state_.backface_visibility == BackfaceVisibility::kInherited)
node = node->Parent();
return node &&
node->state_.backface_visibility == BackfaceVisibility::kHidden;
}
bool HasDirectCompositingReasons() const {
return DirectCompositingReasons() != CompositingReason::kNone;
}
bool HasDirectCompositingReasonsOtherThan3dTransform() const {
return DirectCompositingReasons() & ~CompositingReason::k3DTransform;
}
// TODO(crbug.com/900241): Use HaveActiveTransformAnimation() instead of this
// function when we can track animations for each property type.
bool RequiresCompositingForAnimation() const {
return DirectCompositingReasons() &
CompositingReason::kComboActiveAnimation;
}
bool HasActiveTransformAnimation() const {
return state_.direct_compositing_reasons &
CompositingReason::kActiveTransformAnimation;
}
bool TransformAnimationIsAxisAligned() const {
return state_.flags.animation_is_axis_aligned;
}
bool RequiresCompositingForRootScroller() const {
return state_.direct_compositing_reasons & CompositingReason::kRootScroller;
}
const CompositorElementId& GetCompositorElementId() const {
return state_.compositor_element_id;
}
// Content whose transform nodes have a common rendering context ID are 3D
// sorted. If this is 0, content will not be 3D sorted.
unsigned RenderingContextId() const { return state_.rendering_context_id; }
bool HasRenderingContext() const { return state_.rendering_context_id; }
std::unique_ptr<JSONObject> ToJSON() const;
// Returns memory usage of the transform cache of this node plus ancestors.
size_t CacheMemoryUsageInBytes() const;
private:
friend class PaintPropertyNode<TransformPaintPropertyNode>;
TransformPaintPropertyNode(const TransformPaintPropertyNode* parent,
State&& state,
bool is_parent_alias)
: PaintPropertyNode(parent, is_parent_alias), state_(std::move(state)) {
Validate();
}
CompositingReasons DirectCompositingReasons() const {
DCHECK(!Parent() || !IsParentAlias());
return state_.direct_compositing_reasons;
}
void Validate() const {
#if DCHECK_IS_ON()
if (IsParentAlias())
DCHECK(IsIdentity());
if (state_.scroll) {
// If there is an associated scroll node, this can only be a 2d
// translation for scroll offset.
DCHECK(IsIdentityOr2DTranslation());
// The scroll compositor element id should be stored on the scroll node.
DCHECK(!state_.compositor_element_id);
}
#endif
}
void AddChanged(PaintPropertyChangeType changed) {
// TODO(crbug.com/814815): This is a workaround of the bug. When the bug is
// fixed, change the following condition to
// DCHECK(!transform_cache_ || !transform_cache_->IsValid());
DCHECK_NE(PaintPropertyChangeType::kUnchanged, changed);
if (transform_cache_ && transform_cache_->IsValid()) {
DLOG(WARNING) << "Transform tree changed without invalidating the cache.";
GeometryMapperTransformCache::ClearCache();
GeometryMapperClipCache::ClearCache();
}
PaintPropertyNode::AddChanged(changed);
}
// For access to GetTransformCache() and SetCachedTransform.
friend class GeometryMapper;
friend class GeometryMapperTest;
friend class GeometryMapperTransformCache;
friend class GeometryMapperTransformCacheTest;
const GeometryMapperTransformCache& GetTransformCache() const {
if (!transform_cache_)
transform_cache_.reset(new GeometryMapperTransformCache);
transform_cache_->UpdateIfNeeded(*this);
return *transform_cache_;
}
void UpdateScreenTransform() const {
DCHECK(transform_cache_);
transform_cache_->UpdateScreenTransform(*this);
}
State state_;
mutable std::unique_ptr<GeometryMapperTransformCache> transform_cache_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_