Relands "[Impeller] Render conics without conversion from Flutter apps (#166305)" (#166598)

Reverts flutter/flutter#166591

Golden diffs were not discovered the first time this was submitted.
Re-submitting to double check the goldens.
This commit is contained in:
Jim Graham 2025-04-04 13:47:09 -07:00 committed by GitHub
parent a56ff4890d
commit 790d1b1d9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 281 additions and 64 deletions

View File

@ -5,8 +5,8 @@
#include "flutter/display_list/geometry/dl_path.h"
#include "flutter/display_list/geometry/dl_geometry_types.h"
#include "flutter/impeller/geometry/path.h"
#include "flutter/impeller/geometry/path_builder.h"
#include "impeller/geometry/path.h"
namespace {
inline constexpr flutter::DlPathFillType ToDlFillType(SkPathFillType sk_type) {
@ -452,9 +452,10 @@ class ImpellerPathReceiver final : public DlPathReceiver {
void QuadTo(const DlPoint& cp, const DlPoint& p2) override {
builder_.QuadraticCurveTo(cp, p2);
}
// For legacy compatibility we do not override ConicTo to let the dispatcher
// convert conics to quads until we update Impeller for full support of
// rational quadratics
bool ConicTo(const DlPoint& cp, const DlPoint& p2, DlScalar weight) override {
builder_.ConicCurveTo(cp, p2, weight);
return true;
}
void CubicTo(const DlPoint& cp1,
const DlPoint& cp2,
const DlPoint& p2) override {

View File

@ -4,9 +4,9 @@
#include "flutter/display_list/geometry/dl_path.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "flutter/display_list/testing/dl_test_mock_path_receiver.h"
#include "flutter/third_party/skia/include/core/SkRRect.h"
namespace flutter {
@ -595,31 +595,6 @@ TEST(DisplayListPath, IsLineFromImpellerPath) {
}
namespace {
class DlPathReceiverMock : public DlPathReceiver {
public:
MOCK_METHOD(void,
RecommendSizes,
(size_t verb_count, size_t point_count),
(override));
MOCK_METHOD(void, RecommendBounds, (const DlRect& bounds), (override));
MOCK_METHOD(void,
SetPathInfo,
(DlPathFillType fill_type, bool is_convex),
(override));
MOCK_METHOD(void, MoveTo, (const DlPoint& p2), (override));
MOCK_METHOD(void, LineTo, (const DlPoint& p2), (override));
MOCK_METHOD(void, QuadTo, (const DlPoint& cp, const DlPoint& p2), (override));
MOCK_METHOD(bool,
ConicTo,
(const DlPoint& cp, const DlPoint& p2, DlScalar weight),
(override));
MOCK_METHOD(void,
CubicTo,
(const DlPoint& cp1, const DlPoint& cp2, const DlPoint& p2),
(override));
MOCK_METHOD(void, Close, (), (override));
};
using ::testing::AtMost;
using ::testing::Return;
} // namespace
@ -925,6 +900,104 @@ TEST(DisplayListPath, DispatchImpellerPathConvexSpecified) {
path.Dispatch(mock_receiver);
}
TEST(DisplayListPath, DispatchSkiaPathConicToQuads) {
// If we execute conicTo with a weight of exactly 1.0, SkPath will turn
// it into a quadTo, so we avoid that by using 0.999
SkScalar weights[4] = {
0.02f,
0.5f,
SK_ScalarSqrt2 * 0.5f,
1.0f - kEhCloseEnough,
};
for (SkScalar weight : weights) {
SkPath sk_path;
sk_path.moveTo(10, 10);
sk_path.conicTo(20, 10, 20, 20, weight);
std::array<DlPoint, 5> i_points;
impeller::ConicPathComponent i_conic(DlPoint(10, 10), DlPoint(20, 10),
DlPoint(20, 20), weight);
i_conic.SubdivideToQuadraticPoints(i_points);
::testing::StrictMock<DlPathReceiverMock> mock_receiver;
// Recommendations must happen before any of the path segments is dispatched
::testing::ExpectationSet all_recommendations;
all_recommendations += //
EXPECT_CALL(mock_receiver, RecommendSizes(2u, 3u)) //
.Times(AtMost(1));
all_recommendations +=
EXPECT_CALL(mock_receiver,
RecommendBounds(DlRect::MakeLTRB(10, 10, 20, 20)))
.Times(AtMost(1));
EXPECT_CALL(mock_receiver, SetPathInfo(DlPathFillType::kNonZero, true));
{
::testing::InSequence sequence;
EXPECT_CALL(mock_receiver, MoveTo(DlPoint(10, 10)))
.After(all_recommendations);
EXPECT_CALL(mock_receiver,
ConicTo(DlPoint(20, 10), DlPoint(20, 20), weight))
.WillOnce(Return(false));
EXPECT_CALL(mock_receiver, QuadTo(i_points[1], i_points[2]));
EXPECT_CALL(mock_receiver, QuadTo(i_points[3], i_points[4]));
}
DlPath(sk_path).Dispatch(mock_receiver);
}
}
TEST(DisplayListPath, DispatchImpellerPathConicToQuads) {
// If we execute conicTo with a weight of exactly 1.0, SkPath will turn
// it into a quadTo, so we avoid that by using 0.999
DlScalar weights[4] = {
0.02f,
0.5f,
SK_ScalarSqrt2 * 0.5f,
1.0f - kEhCloseEnough,
};
for (DlScalar weight : weights) {
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(10, 10));
path_builder.ConicCurveTo(DlPoint(20, 10), DlPoint(20, 20), weight);
std::array<DlPoint, 5> i_points;
impeller::ConicPathComponent i_conic(DlPoint(10, 10), DlPoint(20, 10),
DlPoint(20, 20), weight);
i_conic.SubdivideToQuadraticPoints(i_points);
::testing::StrictMock<DlPathReceiverMock> mock_receiver;
// Recommendations must happen before any of the path segments is dispatched
::testing::ExpectationSet all_recommendations;
all_recommendations += //
EXPECT_CALL(mock_receiver, RecommendSizes(2u, 6u)) //
.Times(AtMost(1));
all_recommendations +=
EXPECT_CALL(mock_receiver,
RecommendBounds(DlRect::MakeLTRB(10, 10, 20, 20)))
.Times(AtMost(1));
EXPECT_CALL(mock_receiver, SetPathInfo(DlPathFillType::kNonZero, false));
{
::testing::InSequence sequence;
EXPECT_CALL(mock_receiver, MoveTo(DlPoint(10, 10)))
.After(all_recommendations);
EXPECT_CALL(mock_receiver,
ConicTo(DlPoint(20, 10), DlPoint(20, 20), weight))
.WillOnce(Return(false));
EXPECT_CALL(mock_receiver, QuadTo(i_points[1], i_points[2]));
EXPECT_CALL(mock_receiver, QuadTo(i_points[3], i_points[4]));
}
DlPath(path_builder).Dispatch(mock_receiver);
}
}
#ifndef NDEBUG
// Tests that verify we don't try to use inverse path modes as they aren't
// supported by either Flutter public APIs or Impeller

View File

@ -12,10 +12,11 @@
#include "flutter/display_list/effects/dl_image_filters.h"
#include "flutter/display_list/skia/dl_sk_conversions.h"
#include "flutter/impeller/geometry/path_component.h"
#include "flutter/third_party/skia/include/core/SkColorSpace.h"
#include "flutter/third_party/skia/include/core/SkSamplingOptions.h"
#include "flutter/third_party/skia/include/core/SkTileMode.h"
#include "gtest/gtest.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkSamplingOptions.h"
#include "third_party/skia/include/core/SkTileMode.h"
namespace flutter {
namespace testing {
@ -402,9 +403,7 @@ TEST(DisplayListSkConversions, ConicToQuads) {
}
}
// This tests the new conic subdivision code in the Impeller conic path
// component object vs the code we used to rely on inside Skia
TEST(DisplayListSkConversions, ConicPathToQuads) {
TEST(DisplayListSkConversions, ConicPathToImpeller) {
// If we execute conicTo with a weight of exactly 1.0, SkPath will turn
// it into a quadTo, so we avoid that by using 0.999
SkScalar weights[4] = {
@ -426,35 +425,16 @@ TEST(DisplayListSkConversions, ConicPathToQuads) {
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kContour);
++it;
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kQuadratic);
auto quad1 = it.quadratic();
ASSERT_NE(quad1, nullptr);
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kConic);
auto conic = it.conic();
ASSERT_NE(conic, nullptr);
++it;
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kQuadratic);
auto quad2 = it.quadratic();
ASSERT_NE(quad2, nullptr);
++it;
SkPoint sk_points[5];
int ncurves = SkPath::ConvertConicToQuads(
SkPoint::Make(10, 10), SkPoint::Make(20, 10), SkPoint::Make(20, 20),
weight, sk_points, 1);
ASSERT_EQ(ncurves, 2);
EXPECT_FLOAT_EQ(sk_points[0].fX, quad1->p1.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[0].fY, quad1->p1.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[1].fX, quad1->cp.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[1].fY, quad1->cp.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fX, quad1->p2.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fY, quad1->p2.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fX, quad2->p1.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fY, quad2->p1.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[3].fX, quad2->cp.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[3].fY, quad2->cp.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[4].fX, quad2->p2.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[4].fY, quad2->p2.y) << "weight: " << weight;
EXPECT_EQ(conic->p1, DlPoint(10, 10)) << "weight: " << weight;
EXPECT_EQ(conic->cp, DlPoint(20, 10)) << "weight: " << weight;
EXPECT_EQ(conic->p2, DlPoint(20, 20)) << "weight: " << weight;
EXPECT_EQ(conic->weight.x, weight) << "weight: " << weight;
EXPECT_EQ(conic->weight.y, weight) << "weight: " << weight;
}
}

View File

@ -10,6 +10,7 @@ source_set("display_list_testing") {
sources = [
"dl_test_equality.h",
"dl_test_mock_path_receiver.h",
"dl_test_snippets.cc",
"dl_test_snippets.h",
]

View File

@ -0,0 +1,42 @@
// 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_DISPLAY_LIST_TESTING_DL_TEST_MOCK_PATH_RECEIVER_H_
#define FLUTTER_DISPLAY_LIST_TESTING_DL_TEST_MOCK_PATH_RECEIVER_H_
#include "gmock/gmock.h"
#include "flutter/display_list/geometry/dl_path.h"
namespace flutter {
namespace testing {
class DlPathReceiverMock : public DlPathReceiver {
public:
MOCK_METHOD(void,
RecommendSizes,
(size_t verb_count, size_t point_count),
(override));
MOCK_METHOD(void, RecommendBounds, (const DlRect& bounds), (override));
MOCK_METHOD(void,
SetPathInfo,
(DlPathFillType fill_type, bool is_convex),
(override));
MOCK_METHOD(void, MoveTo, (const DlPoint& p2), (override));
MOCK_METHOD(void, LineTo, (const DlPoint& p2), (override));
MOCK_METHOD(void, QuadTo, (const DlPoint& cp, const DlPoint& p2), (override));
MOCK_METHOD(bool,
ConicTo,
(const DlPoint& cp, const DlPoint& p2, DlScalar weight),
(override));
MOCK_METHOD(void,
CubicTo,
(const DlPoint& cp1, const DlPoint& cp2, const DlPoint& p2),
(override));
MOCK_METHOD(void, Close, (), (override));
};
} // namespace testing
} // namespace flutter
#endif // FLUTTER_DISPLAY_LIST_TESTING_DL_TEST_MOCK_PATH_RECEIVER_H_

View File

@ -344,7 +344,9 @@ void ConicPathComponent::AppendPolylinePoints(
Scalar scale_factor,
std::vector<Point>& points) const {
ToLinearPathComponents(scale_factor, [&points](const Point& point) {
points.emplace_back(point);
if (point != points.back()) {
points.emplace_back(point);
}
});
}

View File

@ -59,6 +59,7 @@ using DlImageSampling = flutter::DlImageSampling;
using SaveLayerOptions = flutter::SaveLayerOptions;
using DisplayListOpType = flutter::DisplayListOpType;
using DisplayListOpCategory = flutter::DisplayListOpCategory;
using DlPathFillType = flutter::DlPathFillType;
using DlPath = flutter::DlPath;
using DisplayListStreamDispatcher = flutter::testing::DisplayListStreamDispatcher;
@ -137,6 +138,14 @@ extern std::ostream& operator<<(
return os << "DisplayListOpCategory::???";
}
extern std::ostream& operator<<(
std::ostream& os, const flutter::DlPathFillType& type) {
switch (type) {
DLT_OSTREAM_CASE(DlPathFillType, Odd);
DLT_OSTREAM_CASE(DlPathFillType, NonZero);
}
}
#undef DLT_OSTREAM_CASE
std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) {
@ -167,6 +176,12 @@ extern std::ostream& operator<<(std::ostream& os, const DlPath& path) {
<< ")";
}
extern std::ostream& operator<<(std::ostream& os, const flutter::testing::DlVerbosePath& path) {
DisplayListStreamDispatcher dispatcher(os, 0);
dispatcher.out(path);
return os;
}
std::ostream& operator<<(std::ostream& os, const flutter::DlClipOp& op) {
switch (op) {
case flutter::DlClipOp::kDifference: return os << "DlClipOp::kDifference";
@ -625,6 +640,75 @@ void DisplayListStreamDispatcher::out(const DlImageFilter* filter) {
outdent(1);
}
}
DisplayListStreamDispatcher::DlPathStreamer::~DlPathStreamer() {
if (done_with_info_) {
dispatcher_.outdent(2);
dispatcher_.startl() << "}" << std::endl;
}
}
void DisplayListStreamDispatcher::DlPathStreamer::RecommendSizes(
size_t verb_count, size_t point_count) {
FML_DCHECK(!done_with_info_);
dispatcher_.startl() << "sizes: "
<< verb_count << " verbs, " << point_count << " points" << std::endl;
};
void DisplayListStreamDispatcher::DlPathStreamer::RecommendBounds(
const DlRect& bounds) {
FML_DCHECK(!done_with_info_);
dispatcher_.startl() << "bounds: " << bounds << std::endl;
};
void DisplayListStreamDispatcher::DlPathStreamer::SetPathInfo(
DlPathFillType fill_type, bool is_convex) {
FML_DCHECK(!done_with_info_);
dispatcher_.startl() << "info: "
<< fill_type << ", convex: " << is_convex << std::endl;
}
void DisplayListStreamDispatcher::DlPathStreamer::MoveTo(const DlPoint& p2) {
if (!done_with_info_) {
done_with_info_ = true;
dispatcher_.startl() << "{" << std::endl;
dispatcher_.indent(2);
}
dispatcher_.startl() << "MoveTo(" << p2 << ")," << std::endl;
}
void DisplayListStreamDispatcher::DlPathStreamer::LineTo(const DlPoint& p2) {
FML_DCHECK(done_with_info_);
dispatcher_.startl() << "LineTo(" << p2 << ")," << std::endl;
}
void DisplayListStreamDispatcher::DlPathStreamer::QuadTo(const DlPoint& cp,
const DlPoint& p2) {
FML_DCHECK(done_with_info_);
dispatcher_.startl() << "QuadTo(" << cp << ", " << p2 << ")," << std::endl;
}
bool DisplayListStreamDispatcher::DlPathStreamer::ConicTo(const DlPoint& cp,
const DlPoint& p2,
DlScalar weight) {
FML_DCHECK(done_with_info_);
dispatcher_.startl() << "ConicTo(" << cp << ", " << p2 << ", " << weight
<< ")," << std::endl;
return true;
}
void DisplayListStreamDispatcher::DlPathStreamer::CubicTo(const DlPoint& cp1,
const DlPoint& cp2,
const DlPoint& p2) {
FML_DCHECK(done_with_info_);
dispatcher_.startl() << "CubicTo(" << cp1 << ", " << cp2 << ", " << p2 << ", "
<< p2 << ")," << std::endl;
}
void DisplayListStreamDispatcher::DlPathStreamer::Close() {
FML_DCHECK(done_with_info_);
dispatcher_.startl() << "Close()," << std::endl;
}
void DisplayListStreamDispatcher::out(const DlVerbosePath& path) {
os_ << "DlPath(" << std::endl;
indent(2);
{
DlPathStreamer streamer(*this);
path.path.Dispatch(streamer);
}
outdent(2);
os_ << ")";
}
void DisplayListStreamDispatcher::setImageFilter(const DlImageFilter* filter) {
startl() << "setImageFilter(";
indent(15);

View File

@ -35,6 +35,12 @@ namespace flutter::testing {
const sk_sp<const DisplayList>& b) {
return DisplayListsNE_Verbose(a.get(), b.get());
}
class DlVerbosePath {
public:
explicit DlVerbosePath(const DlPath& path) : path(path) {}
const DlPath& path;
};
} // namespace flutter::testing
@ -76,6 +82,10 @@ extern std::ostream& operator<<(std::ostream& os,
extern std::ostream& operator<<(std::ostream& os,
const flutter::DisplayListOpCategory& category);
extern std::ostream& operator<<(std::ostream& os, const flutter::DlPath& path);
extern std::ostream& operator<<(std::ostream& os,
const flutter::testing::DlVerbosePath& path);
extern std::ostream& operator<<(std::ostream& os,
const flutter::DlPathFillType& type);
extern std::ostream& operator<<(std::ostream& os,
const flutter::DlImageFilter& type);
extern std::ostream& operator<<(std::ostream& os,
@ -204,6 +214,7 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
void out(const DlColorFilter* filter);
void out(const DlImageFilter& filter);
void out(const DlImageFilter* filter);
void out(const DlVerbosePath& path);
private:
std::ostream& os_;
@ -219,6 +230,29 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
std::ostream& out_array(std::string name, int count, const T array[]);
std::ostream& startl();
class DlPathStreamer : public DlPathReceiver {
public:
~DlPathStreamer();
explicit DlPathStreamer(DisplayListStreamDispatcher& dispatcher)
: dispatcher_(dispatcher) {}
void RecommendSizes(size_t verb_count, size_t point_count);
void RecommendBounds(const DlRect& bounds);
void SetPathInfo(DlPathFillType fill_type, bool is_convex);
void MoveTo(const DlPoint& p2);
void LineTo(const DlPoint& p2);
void QuadTo(const DlPoint& cp, const DlPoint& p2);
bool ConicTo(const DlPoint& cp, const DlPoint& p2, DlScalar weight);
void CubicTo(const DlPoint& cp1, const DlPoint& cp2, const DlPoint& p2);
void Close();
private:
DisplayListStreamDispatcher& dispatcher_;
bool done_with_info_ = false;
};
friend class DlPathStreamer;
};
class DisplayListGeneralReceiver : public DlOpReceiver {