Commit a6e47206 authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Add expression representation to InterpolableLength

This is a second attempt of crrev.com/c/1777025, as the previous
attempt led to a performance regression.

As CSSLengthArray cannot represent all lengths, and in particular,
cannot represent lengths where min/max() are present, this patch
augments InterpolableLength with a CSSMathExpressionNode representation,
so that all lengths can be interpolated and animated.

Bug: 991672, 1002177
Change-Id: Ic8af313872b900eac2587531c56a8eca65d55a5f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1805718Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#698895}
parent 6280e33c
...@@ -14,6 +14,22 @@ ...@@ -14,6 +14,22 @@
namespace blink { namespace blink {
using UnitType = CSSPrimitiveValue::UnitType;
namespace {
CSSMathExpressionNode* NumberNode(double number) {
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(number, UnitType::kNumber));
}
CSSMathExpressionNode* PercentageNode(double number) {
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(number, UnitType::kPercentage));
}
} // namespace
// static // static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels( std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels(
double pixels) { double pixels) {
...@@ -49,13 +65,12 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertCSSValue( ...@@ -49,13 +65,12 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertCSSValue(
return nullptr; return nullptr;
CSSLengthArray length_array; CSSLengthArray length_array;
if (!primitive_value->AccumulateLengthArray(length_array)) { if (primitive_value->AccumulateLengthArray(length_array))
// TODO(crbug.com/991672): Implement interpolation when CSS comparison return std::make_unique<InterpolableLength>(std::move(length_array));
// functions min/max are involved.
return nullptr;
}
return std::make_unique<InterpolableLength>(std::move(length_array)); DCHECK(primitive_value->IsMathFunctionValue());
return std::make_unique<InterpolableLength>(
*To<CSSMathFunctionValue>(primitive_value)->ExpressionNode());
} }
// static // static
...@@ -66,8 +81,9 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertLength( ...@@ -66,8 +81,9 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertLength(
return nullptr; return nullptr;
if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) { if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) {
// TODO(crbug.com/991672): Support interpolation on min/max results. auto unzoomed_calc = length.GetCalculationValue().Zoom(1.0 / zoom);
return nullptr; return std::make_unique<InterpolableLength>(
*CSSMathExpressionNode::Create(*unzoomed_calc));
} }
PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent(); PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent();
...@@ -90,27 +106,97 @@ PairwiseInterpolationValue InterpolableLength::MergeSingles( ...@@ -90,27 +106,97 @@ PairwiseInterpolationValue InterpolableLength::MergeSingles(
std::unique_ptr<InterpolableValue> start, std::unique_ptr<InterpolableValue> start,
std::unique_ptr<InterpolableValue> end) { std::unique_ptr<InterpolableValue> end) {
// TODO(crbug.com/991672): We currently have a lot of "fast paths" that do not // TODO(crbug.com/991672): We currently have a lot of "fast paths" that do not
// go through here, and hence, do not merge the type flags of two lengths. We // go through here, and hence, do not merge the percentage info of two
// should stop doing that. // lengths. We should stop doing that.
auto& start_length = To<InterpolableLength>(*start); auto& start_length = To<InterpolableLength>(*start);
auto& end_length = To<InterpolableLength>(*end); auto& end_length = To<InterpolableLength>(*end);
start_length.length_array_.type_flags |= end_length.length_array_.type_flags; if (start_length.HasPercentage() || end_length.HasPercentage()) {
end_length.length_array_.type_flags = start_length.length_array_.type_flags; start_length.SetHasPercentage();
end_length.SetHasPercentage();
}
if (start_length.IsExpression() || end_length.IsExpression()) {
start_length.SetExpression(start_length.AsExpression());
end_length.SetExpression(end_length.AsExpression());
}
return PairwiseInterpolationValue(std::move(start), std::move(end)); return PairwiseInterpolationValue(std::move(start), std::move(end));
} }
InterpolableLength::InterpolableLength(CSSLengthArray&& length_array) {
SetLengthArray(std::move(length_array));
}
void InterpolableLength::SetLengthArray(CSSLengthArray&& length_array) {
type_ = Type::kLengthArray;
length_array_ = std::move(length_array);
expression_.Clear();
}
InterpolableLength::InterpolableLength(
const CSSMathExpressionNode& expression) {
SetExpression(expression);
}
void InterpolableLength::SetExpression(
const CSSMathExpressionNode& expression) {
type_ = Type::kExpression;
expression_ = &expression;
}
std::unique_ptr<InterpolableValue> InterpolableLength::Clone() const {
return std::make_unique<InterpolableLength>(*this);
}
bool InterpolableLength::HasPercentage() const {
if (IsLengthArray()) {
return length_array_.type_flags.test(
CSSPrimitiveValue::kUnitTypePercentage);
}
return expression_->HasPercentage();
}
void InterpolableLength::SetHasPercentage() {
if (HasPercentage())
return;
if (IsLengthArray()) {
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return;
}
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, zero_percent,
{PercentageNode(0)});
SetExpression(*CSSMathExpressionBinaryOperation::Create(
expression_, zero_percent, CSSMathOperator::kAdd));
}
void InterpolableLength::SubtractFromOneHundredPercent() { void InterpolableLength::SubtractFromOneHundredPercent() {
for (double& value : length_array_.values) if (IsLengthArray()) {
value *= -1; for (double& value : length_array_.values)
length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100; value *= -1;
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage); length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100;
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return;
}
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, hundred_percent,
{PercentageNode(100)});
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
hundred_percent, expression_, CSSMathOperator::kSubtract));
} }
static double ClampToRange(double x, ValueRange range) { static double ClampToRange(double x, ValueRange range) {
return (range == kValueRangeNonNegative && x < 0) ? 0 : x; return (range == kValueRangeNonNegative && x < 0) ? 0 : x;
} }
static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) { static const CSSNumericLiteralValue& ClampNumericLiteralValueToRange(
const CSSNumericLiteralValue& value,
ValueRange range) {
if (range == kValueRangeAll || value.DoubleValue() >= 0)
return value;
return *CSSNumericLiteralValue::Create(0, value.GetType());
}
static UnitType IndexToUnitType(wtf_size_t index) {
return CSSPrimitiveValue::LengthUnitTypeToUnitType( return CSSPrimitiveValue::LengthUnitTypeToUnitType(
static_cast<CSSPrimitiveValue::LengthUnitType>(index)); static_cast<CSSPrimitiveValue::LengthUnitType>(index));
} }
...@@ -118,6 +204,9 @@ static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) { ...@@ -118,6 +204,9 @@ static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) {
Length InterpolableLength::CreateLength( Length InterpolableLength::CreateLength(
const CSSToLengthConversionData& conversion_data, const CSSToLengthConversionData& conversion_data,
ValueRange range) const { ValueRange range) const {
if (IsExpression())
return Length(expression_->ToCalcValue(conversion_data, range));
bool has_percentage = HasPercentage(); bool has_percentage = HasPercentage();
double pixels = 0; double pixels = 0;
double percentage = 0; double percentage = 0;
...@@ -147,11 +236,40 @@ Length InterpolableLength::CreateLength( ...@@ -147,11 +236,40 @@ Length InterpolableLength::CreateLength(
const CSSPrimitiveValue* InterpolableLength::CreateCSSValue( const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
ValueRange range) const { ValueRange range) const {
if (IsExpression())
return CSSMathFunctionValue::Create(expression_, range);
DCHECK(IsLengthArray());
if (length_array_.type_flags.count() > 1u) {
const CSSMathExpressionNode& expression = AsExpression();
if (!expression.IsNumericLiteral())
return CSSMathFunctionValue::Create(&AsExpression(), range);
// This creates a temporary CSSMathExpressionNode. Eliminate it if this
// results in significant performance regression.
return &ClampNumericLiteralValueToRange(
To<CSSMathExpressionNumericLiteral>(expression).GetValue(), range);
}
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
if (length_array_.type_flags.test(i)) {
double value = ClampToRange(length_array_.values[i], range);
UnitType unit_type = IndexToUnitType(i);
return CSSNumericLiteralValue::Create(value, unit_type);
}
}
return CSSNumericLiteralValue::Create(0, UnitType::kPixels);
}
const CSSMathExpressionNode& InterpolableLength::AsExpression() const {
if (IsExpression())
return *expression_;
DCHECK(IsLengthArray());
bool has_percentage = HasPercentage(); bool has_percentage = HasPercentage();
CSSMathExpressionNode* root_node = nullptr; CSSMathExpressionNode* root_node = nullptr;
CSSNumericLiteralValue* first_value = nullptr;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) { for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i]; double value = length_array_.values[i];
if (value == 0 && if (value == 0 &&
...@@ -160,41 +278,53 @@ const CSSPrimitiveValue* InterpolableLength::CreateCSSValue( ...@@ -160,41 +278,53 @@ const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
} }
CSSNumericLiteralValue* current_value = CSSNumericLiteralValue* current_value =
CSSNumericLiteralValue::Create(value, IndexToUnitType(i)); CSSNumericLiteralValue::Create(value, IndexToUnitType(i));
if (!first_value) {
DCHECK(!root_node);
first_value = current_value;
continue;
}
CSSMathExpressionNode* current_node = CSSMathExpressionNode* current_node =
CSSMathExpressionNumericLiteral::Create(current_value); CSSMathExpressionNumericLiteral::Create(current_value);
if (!root_node) { if (!root_node) {
root_node = CSSMathExpressionNumericLiteral::Create(first_value); root_node = current_node;
} else {
root_node = CSSMathExpressionBinaryOperation::Create(
root_node, current_node, CSSMathOperator::kAdd);
} }
root_node = CSSMathExpressionBinaryOperation::Create(
root_node, current_node, CSSMathOperator::kAdd);
} }
if (root_node) { if (root_node)
return CSSMathFunctionValue::Create(root_node, range); return *root_node;
} return *CSSMathExpressionNumericLiteral::Create(
if (first_value) { CSSNumericLiteralValue::Create(0, UnitType::kPixels));
if (range == kValueRangeNonNegative && first_value->DoubleValue() < 0) }
return CSSNumericLiteralValue::Create(0, first_value->GetType());
return first_value; void InterpolableLength::Scale(double scale) {
if (IsLengthArray()) {
for (auto& value : length_array_.values)
value *= scale;
return;
} }
return CSSNumericLiteralValue::Create(0,
CSSPrimitiveValue::UnitType::kPixels); DCHECK(IsExpression());
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
expression_, NumberNode(scale), CSSMathOperator::kMultiply));
} }
void InterpolableLength::ScaleAndAdd(double scale, void InterpolableLength::ScaleAndAdd(double scale,
const InterpolableValue& other) { const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other); const InterpolableLength& other_length = To<InterpolableLength>(other);
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) { if (IsLengthArray() && other_length.IsLengthArray()) {
length_array_.values[i] = for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] * scale + other_length.length_array_.values[i]; length_array_.values[i] = length_array_.values[i] * scale +
other_length.length_array_.values[i];
}
length_array_.type_flags |= other_length.length_array_.type_flags;
return;
} }
length_array_.type_flags |= other_length.length_array_.type_flags;
CSSMathExpressionNode* scaled =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply);
CSSMathExpressionNode* result =
CSSMathExpressionBinaryOperation::CreateSimplified(
scaled, &other_length.AsExpression(), CSSMathOperator::kAdd);
SetExpression(*result);
} }
void InterpolableLength::AssertCanInterpolateWith( void InterpolableLength::AssertCanInterpolateWith(
...@@ -204,23 +334,45 @@ void InterpolableLength::AssertCanInterpolateWith( ...@@ -204,23 +334,45 @@ void InterpolableLength::AssertCanInterpolateWith(
// two |InterpolableLength| objects should also assign them the same shape // two |InterpolableLength| objects should also assign them the same shape
// (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We // (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We
// currently fail to do that, and hit the following DCHECK: // currently fail to do that, and hit the following DCHECK:
// DCHECK_EQ(length_array_.type_flags, // DCHECK_EQ(HasPercentage(),
// To<InterpolableLength>(other).length_array_.type_flags); // To<InterpolableLength>(other).HasPercentage());
} }
void InterpolableLength::Interpolate(const InterpolableValue& to, void InterpolableLength::Interpolate(const InterpolableValue& to,
const double progress, const double progress,
InterpolableValue& result) const { InterpolableValue& result) const {
const CSSLengthArray& to_length_array = const auto& to_length = To<InterpolableLength>(to);
To<InterpolableLength>(to).length_array_; auto& result_length = To<InterpolableLength>(result);
CSSLengthArray& result_length_array = if (IsLengthArray() && to_length.IsLengthArray()) {
To<InterpolableLength>(result).length_array_; if (!result_length.IsLengthArray())
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) { result_length.SetLengthArray(CSSLengthArray());
result_length_array.values[i] = const CSSLengthArray& to_length_array = to_length.length_array_;
Blend(length_array_.values[i], to_length_array.values[i], progress); CSSLengthArray& result_length_array =
To<InterpolableLength>(result).length_array_;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
result_length_array.values[i] =
Blend(length_array_.values[i], to_length_array.values[i], progress);
}
result_length_array.type_flags =
length_array_.type_flags | to_length_array.type_flags;
return;
} }
result_length_array.type_flags =
length_array_.type_flags | to_length_array.type_flags; CSSMathExpressionNode* blended_from =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), NumberNode(1 - progress),
CSSMathOperator::kMultiply);
CSSMathExpressionNode* blended_to =
CSSMathExpressionBinaryOperation::CreateSimplified(
&to_length.AsExpression(), NumberNode(progress),
CSSMathOperator::kMultiply);
CSSMathExpressionNode* result_expression =
CSSMathExpressionBinaryOperation::CreateSimplified(
blended_from, blended_to, CSSMathOperator::kAdd);
result_length.SetExpression(*result_expression);
DCHECK_EQ(result_length.HasPercentage(),
HasPercentage() || to_length.HasPercentage());
} }
} // namespace blink } // namespace blink
...@@ -10,19 +10,19 @@ ...@@ -10,19 +10,19 @@
#include "third_party/blink/renderer/core/animation/pairwise_interpolation_value.h" #include "third_party/blink/renderer/core/animation/pairwise_interpolation_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h" #include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/platform/geometry/length.h" #include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/casting.h" #include "third_party/blink/renderer/platform/wtf/casting.h"
namespace blink { namespace blink {
class CSSToLengthConversionData; class CSSToLengthConversionData;
class CSSMathExpressionNode;
class CORE_EXPORT InterpolableLength final : public InterpolableValue { class CORE_EXPORT InterpolableLength final : public InterpolableValue {
public: public:
~InterpolableLength() final {} ~InterpolableLength() final {}
InterpolableLength(const CSSLengthArray& length_array) InterpolableLength(CSSLengthArray&& length_array);
: length_array_(length_array) {} explicit InterpolableLength(const CSSMathExpressionNode& expression);
InterpolableLength(CSSLengthArray&& length_array)
: length_array_(std::move(length_array)) {}
static std::unique_ptr<InterpolableLength> CreatePixels(double pixels); static std::unique_ptr<InterpolableLength> CreatePixels(double pixels);
static std::unique_ptr<InterpolableLength> CreatePercent(double pixels); static std::unique_ptr<InterpolableLength> CreatePercent(double pixels);
...@@ -45,10 +45,8 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue { ...@@ -45,10 +45,8 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
// expressions. // expressions.
const CSSPrimitiveValue* CreateCSSValue(ValueRange range) const; const CSSPrimitiveValue* CreateCSSValue(ValueRange range) const;
bool HasPercentage() const { void SetHasPercentage();
return length_array_.type_flags.test( bool HasPercentage() const;
CSSPrimitiveValue::kUnitTypePercentage);
}
void SubtractFromOneHundredPercent(); void SubtractFromOneHundredPercent();
// InterpolableValue: // InterpolableValue:
...@@ -57,17 +55,11 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue { ...@@ -57,17 +55,11 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
NOTREACHED(); NOTREACHED();
return false; return false;
} }
std::unique_ptr<InterpolableValue> Clone() const final { std::unique_ptr<InterpolableValue> Clone() const final;
return std::make_unique<InterpolableLength>(length_array_);
}
std::unique_ptr<InterpolableValue> CloneAndZero() const final { std::unique_ptr<InterpolableValue> CloneAndZero() const final {
return std::make_unique<InterpolableLength>(CSSLengthArray()); return std::make_unique<InterpolableLength>(CSSLengthArray());
} }
void Scale(double scale) final { void Scale(double scale) final;
for (double& value : length_array_.values) {
value *= scale;
}
}
void ScaleAndAdd(double scale, const InterpolableValue& other) final; void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final; void AssertCanInterpolateWith(const InterpolableValue& other) const final;
...@@ -77,7 +69,17 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue { ...@@ -77,7 +69,17 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
const double progress, const double progress,
InterpolableValue& result) const final; InterpolableValue& result) const final;
bool IsLengthArray() const { return type_ == Type::kLengthArray; }
bool IsExpression() const { return type_ == Type::kExpression; }
void SetLengthArray(CSSLengthArray&& length_array);
void SetExpression(const CSSMathExpressionNode& expression);
const CSSMathExpressionNode& AsExpression() const;
enum class Type { kLengthArray, kExpression };
Type type_;
CSSLengthArray length_array_; CSSLengthArray length_array_;
Persistent<const CSSMathExpressionNode> expression_;
}; };
template <> template <>
......
...@@ -120,6 +120,11 @@ class CORE_EXPORT CSSMathExpressionNode ...@@ -120,6 +120,11 @@ class CORE_EXPORT CSSMathExpressionNode
virtual bool IsComputationallyIndependent() const = 0; virtual bool IsComputationallyIndependent() const = 0;
CalculationCategory Category() const { return category_; } CalculationCategory Category() const { return category_; }
bool HasPercentage() const {
return category_ == kCalcPercent || category_ == kCalcPercentNumber ||
category_ == kCalcPercentLength ||
category_ == kCalcPercentLengthNumber;
}
// Returns the unit type of the math expression *without doing any type // Returns the unit type of the math expression *without doing any type
// conversion* (e.g., 1px + 1em needs type conversion to resolve). // conversion* (e.g., 1px + 1em needs type conversion to resolve).
...@@ -164,6 +169,8 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final ...@@ -164,6 +169,8 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final
CSSMathExpressionNumericLiteral(const CSSNumericLiteralValue* value, CSSMathExpressionNumericLiteral(const CSSNumericLiteralValue* value,
bool is_integer); bool is_integer);
const CSSNumericLiteralValue& GetValue() const { return *value_; }
bool IsNumericLiteral() const final { return true; } bool IsNumericLiteral() const final { return true; }
bool IsZero() const final; bool IsZero() const final;
......
<!doctype html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#comp-func">
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
<title>Tests interpolation between CSS comparison functions</title>
<style>
@keyframes anim {
from {
width: min(50px, 30%);
height: min(75%, 160px);
}
to {
width: max(75%, 100px);
height: max(50px, 20%);
}
}
.test {
background-color: green;
animation: anim 2000000s linear;
animation-delay: -1000000s;
}
.container {
position: absolute;
width: 200px;
height: 200px;
}
</style>
<p>Test passes if there is a filled green square.</p>
<div class="container">
<div class="test"></div>
</div>
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