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 @@
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
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels(
double pixels) {
......@@ -49,13 +65,12 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertCSSValue(
return nullptr;
CSSLengthArray length_array;
if (!primitive_value->AccumulateLengthArray(length_array)) {
// TODO(crbug.com/991672): Implement interpolation when CSS comparison
// functions min/max are involved.
return nullptr;
}
if (primitive_value->AccumulateLengthArray(length_array))
return std::make_unique<InterpolableLength>(std::move(length_array));
return std::make_unique<InterpolableLength>(std::move(length_array));
DCHECK(primitive_value->IsMathFunctionValue());
return std::make_unique<InterpolableLength>(
*To<CSSMathFunctionValue>(primitive_value)->ExpressionNode());
}
// static
......@@ -66,8 +81,9 @@ std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertLength(
return nullptr;
if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) {
// TODO(crbug.com/991672): Support interpolation on min/max results.
return nullptr;
auto unzoomed_calc = length.GetCalculationValue().Zoom(1.0 / zoom);
return std::make_unique<InterpolableLength>(
*CSSMathExpressionNode::Create(*unzoomed_calc));
}
PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent();
......@@ -90,27 +106,97 @@ PairwiseInterpolationValue InterpolableLength::MergeSingles(
std::unique_ptr<InterpolableValue> start,
std::unique_ptr<InterpolableValue> end) {
// 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
// should stop doing that.
// go through here, and hence, do not merge the percentage info of two
// lengths. We should stop doing that.
auto& start_length = To<InterpolableLength>(*start);
auto& end_length = To<InterpolableLength>(*end);
start_length.length_array_.type_flags |= end_length.length_array_.type_flags;
end_length.length_array_.type_flags = start_length.length_array_.type_flags;
if (start_length.HasPercentage() || end_length.HasPercentage()) {
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));
}
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() {
for (double& value : length_array_.values)
value *= -1;
length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100;
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
if (IsLengthArray()) {
for (double& value : length_array_.values)
value *= -1;
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) {
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(
static_cast<CSSPrimitiveValue::LengthUnitType>(index));
}
......@@ -118,6 +204,9 @@ static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) {
Length InterpolableLength::CreateLength(
const CSSToLengthConversionData& conversion_data,
ValueRange range) const {
if (IsExpression())
return Length(expression_->ToCalcValue(conversion_data, range));
bool has_percentage = HasPercentage();
double pixels = 0;
double percentage = 0;
......@@ -147,11 +236,40 @@ Length InterpolableLength::CreateLength(
const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
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();
CSSMathExpressionNode* root_node = nullptr;
CSSNumericLiteralValue* first_value = nullptr;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i];
if (value == 0 &&
......@@ -160,41 +278,53 @@ const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
}
CSSNumericLiteralValue* current_value =
CSSNumericLiteralValue::Create(value, IndexToUnitType(i));
if (!first_value) {
DCHECK(!root_node);
first_value = current_value;
continue;
}
CSSMathExpressionNode* current_node =
CSSMathExpressionNumericLiteral::Create(current_value);
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) {
return CSSMathFunctionValue::Create(root_node, range);
}
if (first_value) {
if (range == kValueRangeNonNegative && first_value->DoubleValue() < 0)
return CSSNumericLiteralValue::Create(0, first_value->GetType());
return first_value;
if (root_node)
return *root_node;
return *CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(0, UnitType::kPixels));
}
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,
const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] =
length_array_.values[i] * scale + other_length.length_array_.values[i];
if (IsLengthArray() && other_length.IsLengthArray()) {
for (wtf_size_t i = 0; i < length_array_.values.size(); ++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(
......@@ -204,23 +334,45 @@ void InterpolableLength::AssertCanInterpolateWith(
// two |InterpolableLength| objects should also assign them the same shape
// (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We
// currently fail to do that, and hit the following DCHECK:
// DCHECK_EQ(length_array_.type_flags,
// To<InterpolableLength>(other).length_array_.type_flags);
// DCHECK_EQ(HasPercentage(),
// To<InterpolableLength>(other).HasPercentage());
}
void InterpolableLength::Interpolate(const InterpolableValue& to,
const double progress,
InterpolableValue& result) const {
const CSSLengthArray& to_length_array =
To<InterpolableLength>(to).length_array_;
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);
const auto& to_length = To<InterpolableLength>(to);
auto& result_length = To<InterpolableLength>(result);
if (IsLengthArray() && to_length.IsLengthArray()) {
if (!result_length.IsLengthArray())
result_length.SetLengthArray(CSSLengthArray());
const CSSLengthArray& to_length_array = to_length.length_array_;
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
......@@ -10,19 +10,19 @@
#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/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
namespace blink {
class CSSToLengthConversionData;
class CSSMathExpressionNode;
class CORE_EXPORT InterpolableLength final : public InterpolableValue {
public:
~InterpolableLength() final {}
InterpolableLength(const CSSLengthArray& length_array)
: length_array_(length_array) {}
InterpolableLength(CSSLengthArray&& length_array)
: length_array_(std::move(length_array)) {}
InterpolableLength(CSSLengthArray&& length_array);
explicit InterpolableLength(const CSSMathExpressionNode& expression);
static std::unique_ptr<InterpolableLength> CreatePixels(double pixels);
static std::unique_ptr<InterpolableLength> CreatePercent(double pixels);
......@@ -45,10 +45,8 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
// expressions.
const CSSPrimitiveValue* CreateCSSValue(ValueRange range) const;
bool HasPercentage() const {
return length_array_.type_flags.test(
CSSPrimitiveValue::kUnitTypePercentage);
}
void SetHasPercentage();
bool HasPercentage() const;
void SubtractFromOneHundredPercent();
// InterpolableValue:
......@@ -57,17 +55,11 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
NOTREACHED();
return false;
}
std::unique_ptr<InterpolableValue> Clone() const final {
return std::make_unique<InterpolableLength>(length_array_);
}
std::unique_ptr<InterpolableValue> Clone() const final;
std::unique_ptr<InterpolableValue> CloneAndZero() const final {
return std::make_unique<InterpolableLength>(CSSLengthArray());
}
void Scale(double scale) final {
for (double& value : length_array_.values) {
value *= scale;
}
}
void Scale(double scale) final;
void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
......@@ -77,7 +69,17 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
const double progress,
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_;
Persistent<const CSSMathExpressionNode> expression_;
};
template <>
......
......@@ -120,6 +120,11 @@ class CORE_EXPORT CSSMathExpressionNode
virtual bool IsComputationallyIndependent() const = 0;
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
// conversion* (e.g., 1px + 1em needs type conversion to resolve).
......@@ -164,6 +169,8 @@ class CORE_EXPORT CSSMathExpressionNumericLiteral final
CSSMathExpressionNumericLiteral(const CSSNumericLiteralValue* value,
bool is_integer);
const CSSNumericLiteralValue& GetValue() const { return *value_; }
bool IsNumericLiteral() const final { return true; }
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