Commit d5e7b86f authored by Xianzhu Wang's avatar Xianzhu Wang Committed by Commit Bot

Separate transform and origin for composited SVG animation

Previously we always baked transform origin into transform for SVG
transforms in paint properties. However, when there is composited SVG
transform animation, the compositor animates the transform part only,
so we need to store transform origin separately in paint properties.

Also disable SVG compositing when there is SMIL animation which is not
supported by the compositor.

Bug: 1133718
Change-Id: Icd2cf67e3952a9006ff2d74fb798a058db96a433
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2441386
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarFredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#813880}
parent 4ada2151
......@@ -61,7 +61,8 @@ FloatRect TransformHelper::ComputeReferenceBox(
}
AffineTransform TransformHelper::ComputeTransform(
const LayoutObject& layout_object) {
const LayoutObject& layout_object,
ComputedStyle::ApplyTransformOrigin apply_transform_origin) {
const ComputedStyle& style = layout_object.StyleRef();
if (DependsOnReferenceBox(style)) {
UseCounter::Count(layout_object.GetDocument(),
......@@ -81,8 +82,7 @@ AffineTransform TransformHelper::ComputeTransform(
// https://svgwg.org/svg2-draft/coords.html#ObjectBoundingBoxUnits
TransformationMatrix transform;
FloatRect reference_box = ComputeReferenceBox(layout_object);
style.ApplyTransform(transform, reference_box,
ComputedStyle::kIncludeTransformOrigin,
style.ApplyTransform(transform, reference_box, apply_transform_origin,
ComputedStyle::kIncludeMotionPath,
ComputedStyle::kIncludeIndependentTransformProperties);
const float zoom = style.EffectiveZoom();
......@@ -92,4 +92,17 @@ AffineTransform TransformHelper::ComputeTransform(
return transform.ToAffineTransform();
}
FloatPoint TransformHelper::ComputeTransformOrigin(
const LayoutObject& layout_object) {
const auto& style = layout_object.StyleRef();
FloatRect reference_box = ComputeReferenceBox(layout_object);
FloatPoint origin(
FloatValueForLength(style.TransformOriginX(), reference_box.Width()) +
reference_box.X(),
FloatValueForLength(style.TransformOriginY(), reference_box.Height()) +
reference_box.Y());
// See the comment in ComputeTransform() for the reason of scaling by 1/zoom.
return origin.ScaledBy(1 / style.EffectiveZoom());
}
} // namespace blink
......@@ -5,12 +5,12 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_TRANSFORM_HELPER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_TRANSFORM_HELPER_H_
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class ComputedStyle;
class FloatRect;
class LayoutObject;
......@@ -28,7 +28,10 @@ class TransformHelper {
// Compute the transform for the LayoutObject based on the various
// 'transform*' properties.
static AffineTransform ComputeTransform(const LayoutObject&);
static AffineTransform ComputeTransform(const LayoutObject&,
ComputedStyle::ApplyTransformOrigin);
static FloatPoint ComputeTransformOrigin(const LayoutObject&);
};
// The following enumeration is used to optimize cases where the scale is known
......
......@@ -14,9 +14,11 @@
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_video.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
namespace blink {
......@@ -186,15 +188,25 @@ CompositingReasons
CompositingReasonFinder::DirectReasonsForSVGChildPaintProperties(
const LayoutObject& object) {
DCHECK(object.IsSVGChild());
if (RuntimeEnabledFeatures::CompositeSVGEnabled() && !object.IsText()) {
const ComputedStyle& style = object.StyleRef();
auto reasons = CompositingReasonsForAnimation(object) |
CompositingReasonsForWillChange(style);
if (style.HasBackdropFilter())
reasons |= CompositingReason::kBackdropFilter;
return reasons;
if (!RuntimeEnabledFeatures::CompositeSVGEnabled())
return CompositingReason::kNone;
if (object.IsText())
return CompositingReason::kNone;
const ComputedStyle& style = object.StyleRef();
auto reasons = CompositingReasonsForAnimation(object);
if (reasons != CompositingReason::kNone &&
To<SVGElement>(object.GetNode())->HasMainThreadAnimations()) {
// TODO(crbug.com/1134652): For now we disable compositing if there are
// both compositor-supported animations and main-thread animations.
// The better way might be to allow compositing but disable composited
// animation.
return CompositingReason::kNone;
}
return CompositingReason::kNone;
reasons |= CompositingReasonsForWillChange(style);
if (style.HasBackdropFilter())
reasons |= CompositingReason::kBackdropFilter;
return reasons;
}
CompositingReasons CompositingReasonFinder::CompositingReasonsFor3DTransform(
......@@ -283,6 +295,31 @@ CompositingReasons CompositingReasonFinder::NonStyleDeterminedDirectReasons(
return direct_reasons;
}
static bool SupportsCompositedTransformAnimation(const LayoutObject& object) {
if (object.IsSVGChild()) {
if (!RuntimeEnabledFeatures::CompositeSVGEnabled())
return false;
// Embedded SVG doesn't support transforms for now.
if (object.IsSVGViewportContainer())
return false;
// TODO(crbug.com/1134775): If a foreignObject's effect zoom is not 1,
// its transform node contains an additional scale, and composited
// animation would remove the scale.
if (object.IsSVGForeignObject() && object.StyleRef().EffectiveZoom() != 1)
return false;
// TODO(crbug.com/1134775): Similarly, composited animation would also
// remove the additional translation of LayoutSVGTransformableContainer.
if (object.IsSVGTransformableContainer() &&
!ToLayoutSVGTransformableContainer(object)
.AdditionalTranslation()
.IsZero())
return false;
return true;
}
// Transforms don't apply on non-replaced inline elements.
return object.IsBox();
}
CompositingReasons CompositingReasonFinder::CompositingReasonsForAnimation(
const LayoutObject& object) {
CompositingReasons reasons = CompositingReason::kNone;
......@@ -290,11 +327,8 @@ CompositingReasons CompositingReasonFinder::CompositingReasonsForAnimation(
if (style.SubtreeWillChangeContents())
return reasons;
// Transforms don't apply on non-replaced inline elements.
bool supports_composited_animations =
object.IsBox() ||
(RuntimeEnabledFeatures::CompositeSVGEnabled() && object.IsSVGChild());
if (supports_composited_animations && style.HasCurrentTransformAnimation())
if (style.HasCurrentTransformAnimation() &&
SupportsCompositedTransformAnimation(object))
reasons |= CompositingReason::kActiveTransformAnimation;
if (style.HasCurrentOpacityAnimation())
reasons |= CompositingReason::kActiveOpacityAnimation;
......
......@@ -383,4 +383,61 @@ TEST_F(CompositingReasonFinderTest, CompositedSVGText) {
CompositingReasonFinder::DirectReasonsForPaintProperties(*text));
}
TEST_F(CompositingReasonFinderTest, SVGCompositedTransformAnimation) {
SetBodyInnerHTML(R"HTML(
<style>
.animate {
width: 100px;
height: 100px;
animation: wave 1s infinite;
}
@keyframes wave {
0% { transform: rotate(-5deg); }
100% { transform: rotate(5deg); }
}
</style>
<svg id="svg" class="animate">
<rect id="rect" class="animate"/>
<rect id="rect-smil" class="animate">
<animateMotion dur="10s" repeatCount="indefinite"
path="M0,0 L100,100 z"/>
</rect>
<svg id="embedded-svg" class="animate"/>
<foreignObject id="foreign" class="animate"/>
<foreignObject id="foreign-zoomed" class="animate"
style="zoom: 1.5; will-change: opacity"/>
<use id="use" href="#rect" class="animate"/>
<use id="use-offset" href="#rect" x="10" class="animate"/>
</svg>
)HTML");
EXPECT_EQ(CompositingReason::kActiveTransformAnimation |
CompositingReason::kSVGRoot,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("svg")));
EXPECT_EQ(CompositingReason::kActiveTransformAnimation,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("rect")));
EXPECT_EQ(CompositingReason::kNone,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("rect-smil")));
EXPECT_EQ(CompositingReason::kNone,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("embedded-svg")));
EXPECT_EQ(CompositingReason::kActiveTransformAnimation,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("foreign")));
// The foreignObject that can't use composited animation still gets other
// compositing reasons.
EXPECT_EQ(CompositingReason::kWillChangeOpacity,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("foreign-zoomed")));
EXPECT_EQ(CompositingReason::kActiveTransformAnimation,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("use")));
EXPECT_EQ(CompositingReason::kNone,
CompositingReasonFinder::DirectReasonsForPaintProperties(
*GetLayoutObjectByElementId("use-offset")));
}
} // namespace blink
......@@ -31,6 +31,7 @@
#include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
#include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
#include "third_party/blink/renderer/core/page/link_highlight.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
......@@ -216,6 +217,8 @@ class FragmentPaintPropertyTreeBuilder {
ALWAYS_INLINE void UpdateTransformIsolationNode();
ALWAYS_INLINE void UpdateEffectIsolationNode();
ALWAYS_INLINE void UpdateClipIsolationNode();
ALWAYS_INLINE void SetTransformNodeStateForSVGChild(
TransformPaintPropertyNode::State&);
bool NeedsPaintPropertyUpdate() const {
return object_.NeedsPaintPropertyUpdate() ||
......@@ -707,15 +710,37 @@ static bool NeedsTransformForSVGChild(
static void SetTransformNodeStateFromAffineTransform(
TransformPaintPropertyNode::State& state,
const AffineTransform& transform,
bool disable_2d_translation_optimization) {
if (!disable_2d_translation_optimization &&
transform.IsIdentityOrTranslation())
const AffineTransform& transform) {
if (transform.IsIdentityOrTranslation())
state.transform_and_origin = {FloatSize(transform.E(), transform.F())};
else
state.transform_and_origin = {TransformationMatrix(transform)};
}
void FragmentPaintPropertyTreeBuilder::SetTransformNodeStateForSVGChild(
TransformPaintPropertyNode::State& state) {
if (full_context_.direct_compositing_reasons &
CompositingReason::kActiveTransformAnimation) {
// For composited transform animation to work, we need to store transform
// origin separately. It's baked in object_.LocalToSVGParentTransform().
DCHECK(!To<SVGElement>(object_.GetNode())->HasMainThreadAnimations());
// Composited transform animation works only if LocalToSVGParentTransform()
// reflect the CSS transform properties. If this fails, we need to exclude
// the case in CompositingReasonFinder for kActiveTransformAnimation.
DCHECK_EQ(TransformHelper::ComputeTransform(
object_, ComputedStyle::kIncludeTransformOrigin),
object_.LocalToSVGParentTransform());
state.transform_and_origin = {
TransformationMatrix(TransformHelper::ComputeTransform(
object_, ComputedStyle::kExcludeTransformOrigin)),
FloatPoint3D(TransformHelper::ComputeTransformOrigin(object_))};
return;
}
SetTransformNodeStateFromAffineTransform(state,
object_.LocalToSVGParentTransform());
}
// SVG does not use the general transform update of |UpdateTransform|, instead
// creating a transform node for SVG-specific transforms without 3D.
void FragmentPaintPropertyTreeBuilder::UpdateTransformForSVGChild(
......@@ -728,15 +753,10 @@ void FragmentPaintPropertyTreeBuilder::UpdateTransformForSVGChild(
context_.current.paint_offset.IsZero());
if (NeedsPaintPropertyUpdate()) {
AffineTransform transform = object_.LocalToSVGParentTransform();
if (NeedsTransformForSVGChild(object_, direct_compositing_reasons)) {
// The origin is included in the local transform, so leave origin empty.
TransformPaintPropertyNode::State state;
bool disable_2d_translation_optimization =
full_context_.direct_compositing_reasons &
CompositingReason::kActiveTransformAnimation;
SetTransformNodeStateFromAffineTransform(
state, transform, disable_2d_translation_optimization);
SetTransformNodeStateForSVGChild(state);
// TODO(pdr): There is additional logic in
// FragmentPaintPropertyTreeBuilder::UpdateTransform that likely needs to
......@@ -1869,8 +1889,7 @@ void FragmentPaintPropertyTreeBuilder::UpdateReplacedContentTransform() {
}
if (!content_to_parent_space.IsIdentity()) {
TransformPaintPropertyNode::State state;
SetTransformNodeStateFromAffineTransform(state, content_to_parent_space,
false);
SetTransformNodeStateFromAffineTransform(state, content_to_parent_space);
state.flags.flattens_inherited_transform =
context_.current.should_flatten_inherited_transform;
OnUpdate(properties_->UpdateReplacedContentTransform(
......
......@@ -6994,4 +6994,27 @@ TEST_P(PaintPropertyTreeBuilderTest, SVGChildBackdropFilter) {
.PaintProperties());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGTransformAnimationAndOrigin) {
SetBodyInnerHTML(R"HTML(
<svg width="200" height="200">
<rect id="rect"
style="animation: 2s infinite spin; transform-origin: 50% 50%">
</svg>
<style>
@keyframes spin {
0% { transform: rotate(0); }
100% { transform: rotate(360deg); }
}
</style>
)HTML");
auto* properties = PaintPropertiesForElement("rect");
ASSERT_TRUE(properties);
auto* transform_node = properties->Transform();
ASSERT_TRUE(transform_node);
EXPECT_TRUE(transform_node->HasActiveTransformAnimation());
EXPECT_EQ(TransformationMatrix(), transform_node->Matrix());
EXPECT_EQ(FloatPoint3D(100, 100, 0), transform_node->Origin());
}
} // namespace blink
......@@ -44,8 +44,7 @@ class ScopedSVGTransformState {
public:
ScopedSVGTransformState(const PaintInfo& paint_info,
const LayoutObject& object,
const AffineTransform& transform) {
const LayoutObject& object) {
DCHECK(object.IsSVGChild());
const auto* fragment = paint_info.FragmentToPaint(object);
......@@ -56,14 +55,6 @@ class ScopedSVGTransformState {
return;
if (const auto* transform_node = properties->Transform()) {
#if DCHECK_IS_ON()
if (transform_node->IsIdentityOr2DTranslation()) {
DCHECK_EQ(transform_node->Translation2D(),
transform.ToTransformationMatrix().To2DTranslation());
} else {
DCHECK_EQ(transform_node->Matrix(), transform.ToTransformationMatrix());
}
#endif
transform_property_scope_.emplace(
paint_info.context.GetPaintController(), *transform_node, object,
DisplayItem::PaintPhaseToSVGTransformType(paint_info.phase));
......
......@@ -61,9 +61,8 @@ void SVGContainerPainter::Paint(const PaintInfo& paint_info) {
paint_info_before_filtering.ApplyInfiniteCullRect();
}
ScopedSVGTransformState transform_state(
paint_info_before_filtering, layout_svg_container_,
layout_svg_container_.LocalToSVGParentTransform());
ScopedSVGTransformState transform_state(paint_info_before_filtering,
layout_svg_container_);
{
base::Optional<ScopedPaintChunkProperties> scoped_paint_chunk_properties;
if (layout_svg_container_.IsSVGViewportContainer() &&
......
......@@ -39,8 +39,7 @@ void SVGImagePainter::Paint(const PaintInfo& paint_info) {
}
// Images cannot have children so do not call TransformCullRect.
ScopedSVGTransformState transform_state(
paint_info, layout_svg_image_, layout_svg_image_.LocalSVGTransform());
ScopedSVGTransformState transform_state(paint_info, layout_svg_image_);
{
ScopedSVGPaintState paint_state(layout_svg_image_, paint_info);
if (!DrawingRecorder::UseCachedDrawingIfPossible(
......
......@@ -55,8 +55,7 @@ void SVGShapePainter::Paint(const PaintInfo& paint_info) {
}
// Shapes cannot have children so do not call TransformCullRect.
ScopedSVGTransformState transform_state(
paint_info, layout_svg_shape_, layout_svg_shape_.LocalSVGTransform());
ScopedSVGTransformState transform_state(paint_info, layout_svg_shape_);
{
ScopedSVGPaintState paint_state(layout_svg_shape_, paint_info);
if (!DrawingRecorder::UseCachedDrawingIfPossible(
......
......@@ -24,9 +24,7 @@ void SVGTextPainter::Paint(const PaintInfo& paint_info) {
if (const auto* transform = properties->Transform())
block_info.TransformCullRect(*transform);
}
ScopedSVGTransformState transform_state(
block_info, layout_svg_text_,
layout_svg_text_.LocalToSVGParentTransform());
ScopedSVGTransformState transform_state(block_info, layout_svg_text_);
if (block_info.phase == PaintPhase::kForeground)
SVGModelObjectPainter::RecordHitTestData(layout_svg_text_, block_info);
......
......@@ -25,6 +25,7 @@ class ElementSMILAnimations : public GarbageCollected<ElementSMILAnimations> {
void AddAnimation(const QualifiedName& attribute, SVGAnimationElement*);
void RemoveAnimation(const QualifiedName& attribute, SVGAnimationElement*);
bool HasAnimations() const { return !sandwiches_.IsEmpty(); }
bool Apply(SMILTime elapsed);
......
......@@ -51,6 +51,7 @@
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
#include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
#include "third_party/blink/renderer/core/svg/animation/element_smil_animations.h"
#include "third_party/blink/renderer/core/svg/properties/svg_animated_property.h"
#include "third_party/blink/renderer/core/svg/properties/svg_property.h"
#include "third_party/blink/renderer/core/svg/svg_animated_string.h"
......@@ -229,7 +230,7 @@ void SVGElement::ClearWebAnimatedAttributes() {
animated_attributes.clear();
}
ElementSMILAnimations* SVGElement::GetSMILAnimations() {
ElementSMILAnimations* SVGElement::GetSMILAnimations() const {
if (!HasSVGRareData())
return nullptr;
return SvgRareData()->GetSMILAnimations();
......@@ -266,6 +267,16 @@ void SVGElement::ClearAnimatedAttribute(const QualifiedName& attribute) {
});
}
bool SVGElement::HasMainThreadAnimations() const {
if (!HasSVGRareData())
return false;
if (!SvgRareData()->WebAnimatedAttributes().IsEmpty())
return true;
if (GetSMILAnimations() && GetSMILAnimations()->HasAnimations())
return true;
return false;
}
AffineTransform SVGElement::LocalCoordinateSpaceTransform(CTMScope) const {
// To be overriden by SVGGraphicsElement (or as special case SVGTextElement
// and SVGPatternElement)
......@@ -284,8 +295,10 @@ AffineTransform SVGElement::CalculateTransform(
const LayoutObject* layout_object = GetLayoutObject();
AffineTransform matrix;
if (layout_object && layout_object->StyleRef().HasTransform())
matrix = TransformHelper::ComputeTransform(*layout_object);
if (layout_object && layout_object->StyleRef().HasTransform()) {
matrix = TransformHelper::ComputeTransform(
*layout_object, ComputedStyle::kIncludeTransformOrigin);
}
// Apply any "motion transform" contribution if requested (and existing.)
if (apply_motion_transform == kIncludeMotionTransform && HasSVGRareData())
......
......@@ -99,13 +99,15 @@ class CORE_EXPORT SVGElement : public Element {
SVGPropertyBase*);
void ClearWebAnimatedAttributes();
ElementSMILAnimations* GetSMILAnimations();
ElementSMILAnimations* GetSMILAnimations() const;
ElementSMILAnimations& EnsureSMILAnimations();
const ComputedStyle* BaseComputedStyleForSMIL();
void SetAnimatedAttribute(const QualifiedName&, SVGPropertyBase*);
void ClearAnimatedAttribute(const QualifiedName&);
bool HasMainThreadAnimations() const;
SVGSVGElement* ownerSVGElement() const;
SVGElement* viewportElement() const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment