New optimized general convex path shadow algorithm (#178370)

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

Convex paths will now use an optimized mesh-based algorithm to render
shadows. The algorithm is based on the code in SkShadowTessellator.

Fixes https://github.com/flutter/flutter/issues/170764

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.
This commit is contained in:
Jim Graham 2025-12-23 09:50:23 -08:00 committed by GitHub
parent 6ff7f30047
commit b568f332e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 5351 additions and 111 deletions

View File

@ -78,4 +78,17 @@ float16_t IPSigmoid(float16_t x) {
return 1.03731472073hf / (1.0hf + exp(-4.0hf * x)) - 0.0186573603638hf;
}
/// Converts a fraction in the range [0,1] to a guassian weighted distribution
/// over the same range ([0,1]).
float16_t IPHalfFractionToFastGaussianCDF(float16_t fraction) {
// IPErf produces outputs over [0, 1] from an input range of [-2, +2].
// We need to convert the fraction to the appropriate range.
//
// [0, 1] => [-2, +2]
// 0 * 4 - 2 == -2
// 1 * 4 - 2 == +2
float16_t x = fraction * 4.0hf - 2.0hf;
return (1.0hf + IPErf(x)) * 0.5hf;
}
#endif

View File

@ -78,6 +78,7 @@ template("display_list_unittests_component") {
"aiks_dl_opacity_unittests.cc",
"aiks_dl_path_unittests.cc",
"aiks_dl_runtime_effect_unittests.cc",
"aiks_dl_shadow_unittests.cc",
"aiks_dl_text_unittests.cc",
"aiks_dl_unittests.cc",
"aiks_dl_vertices_unittests.cc",

View File

@ -0,0 +1,934 @@
// 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.
#include "flutter/impeller/display_list/aiks_unittests.h"
#include "flutter/display_list/dl_builder.h"
#include "flutter/display_list/dl_color.h"
#include "flutter/display_list/dl_paint.h"
#include "flutter/display_list/geometry/dl_path_builder.h"
#include "flutter/impeller/entity/geometry/shadow_path_geometry.h"
#include "flutter/testing/testing.h"
namespace impeller {
namespace testing {
using namespace flutter;
namespace {
/// @brief Reflect the segments of a path around a coordinate using the
/// PathReceiver interface.
class PathReflector : public PathReceiver {
public:
/// Reflect a path horizontally around the given x coordinate.
static PathReflector ReflectAroundX(Scalar x_coordinate) {
return PathReflector(-1.0f, x_coordinate * 2.0f, 1.0f, 0.0f);
}
/// Reflect a path vertically around the given y coordinate.
static PathReflector ReflectAroundY(Scalar y_coordinate) {
return PathReflector(1.0f, 0.0f, -1.0f, y_coordinate * 2.0f);
}
/// Reflect a path horizontally and vertically around the given coordinate.
static PathReflector ReflectAround(const Point& anchor) {
return PathReflector(-1.0f, anchor.x * 2.0f, -1.0f, anchor.y * 2.0f);
}
// |PathReceiver|
void MoveTo(const Point& p2, bool will_be_closed) override {
path_builder_.MoveTo(reflect(p2));
}
// |PathReceiver|
void LineTo(const Point& p2) override { path_builder_.LineTo(reflect(p2)); }
// |PathReceiver|
void QuadTo(const Point& cp, const Point& p2) override {
path_builder_.QuadraticCurveTo(reflect(cp), reflect(p2));
}
// |PathReceiver|
bool ConicTo(const Point& cp, const Point& p2, Scalar weight) override {
path_builder_.ConicCurveTo(reflect(cp), reflect(p2), weight);
return true;
}
// |PathReceiver|
void CubicTo(const Point& cp1, const Point& cp2, const Point& p2) override {
path_builder_.CubicCurveTo(reflect(cp1), reflect(cp2), reflect(p2));
}
// |PathReceiver|
void Close() override { path_builder_.Close(); }
DlPath TakePath() { return path_builder_.TakePath(); }
private:
PathReflector(Scalar scale_x,
Scalar translate_x,
Scalar scale_y,
Scalar translate_y)
: scale_x_(scale_x),
translate_x_(translate_x),
scale_y_(scale_y),
translate_y_(translate_y) {}
const Scalar scale_x_;
const Scalar translate_x_;
const Scalar scale_y_;
const Scalar translate_y_;
DlPoint reflect(const DlPoint& in_point) {
return DlPoint(in_point.x * scale_x_ + translate_x_,
in_point.y * scale_y_ + translate_y_);
}
DlPathBuilder path_builder_;
};
DlPath ReflectPath(const DlPath& path) {
PathReflector reflector =
PathReflector::ReflectAroundY(path.GetBounds().GetCenter().y);
path.Dispatch(reflector);
return reflector.TakePath();
}
void DrawShadowMesh(DisplayListBuilder& builder,
const DlPath& path,
Scalar elevation,
Scalar dpr) {
bool should_optimize = path.IsConvex();
Matrix matrix = builder.GetMatrix();
// From dl_dispatcher, making a MaskFilter.
Scalar light_radius = 800 / 600;
EXPECT_EQ(light_radius, 1.0f); // Value in dl_dispatcher is bad.
Scalar occluder_z = elevation * dpr;
Radius radius = Radius{light_radius * occluder_z / matrix.GetScale().y};
Sigma sigma = radius;
// From canvas.cc computing the device radius.
Scalar device_radius = sigma.sigma * 2.8 * matrix.GetMaxBasisLengthXY();
Tessellator tessellator;
std::shared_ptr<ShadowVertices> shadow_vertices =
ShadowPathGeometry::MakeAmbientShadowVertices(tessellator, path,
device_radius, matrix);
EXPECT_EQ(shadow_vertices != nullptr, should_optimize);
Point shadow_translate = Point(0, occluder_z) * matrix.Invert().GetScale().y;
DlPaint paint;
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setColor(DlColor::kDarkGrey());
if (shadow_vertices) {
builder.Save();
builder.Translate(shadow_translate.x, shadow_translate.y);
auto indices = shadow_vertices->GetIndices();
auto vertices = shadow_vertices->GetVertices();
DlPathBuilder mesh_builder;
for (size_t i = 0; i < shadow_vertices->GetIndexCount(); i += 3) {
mesh_builder.MoveTo(vertices[indices[i + 0]]);
mesh_builder.LineTo(vertices[indices[i + 1]]);
mesh_builder.LineTo(vertices[indices[i + 2]]);
mesh_builder.Close();
}
DlPath mesh_path = mesh_builder.TakePath();
builder.DrawPath(mesh_path, paint);
builder.Restore();
}
builder.Save();
builder.Translate(shadow_translate.x, shadow_translate.y);
paint.setColor(DlColor::kPurple());
builder.DrawPath(path, paint);
builder.Restore();
}
DlPath MakeComplexPath(const DlPath& path) {
DlPathBuilder path_builder;
path_builder.AddPath(path);
// A single line contour won't make any visible change to the shadow,
// but none of the shadow to mesh converters will touch a path that
// has multiple contours so this path should always default to the
// general shadow code based on a blur filter.
path_builder.LineTo(DlPoint(0, 0));
return path_builder.TakePath();
}
void DrawShadowAndCompareMeshes(DisplayListBuilder& builder,
const DlPath& path,
Scalar elevation,
Scalar dpr,
const DlPath* simple_path = nullptr) {
DlPath complex_path = MakeComplexPath(path);
builder.Save();
if (simple_path) {
builder.DrawShadow(*simple_path, DlColor::kBlue(), elevation, true, dpr);
}
builder.Translate(300, 0);
builder.DrawShadow(path, DlColor::kBlue(), elevation, true, dpr);
builder.Translate(300, 0);
builder.DrawShadow(complex_path, DlColor::kBlue(), elevation, true, dpr);
builder.Restore();
builder.Translate(0, 300);
builder.Save();
// Draw the mesh wireframe underneath the regular path output in the
// row above us.
builder.Translate(300, 0);
builder.DrawShadow(path, DlColor::kBlue(), elevation, true, dpr);
DrawShadowMesh(builder, path, elevation, dpr);
builder.Restore();
}
// Makes a Round Rect path using conics, but the weights on the corners is
// off by just a tiny amount so the path will not be recognized.
DlPath MakeAlmostRoundRectPath(const Rect& bounds,
const RoundingRadii& radii,
bool clockwise = true) {
DlScalar left = bounds.GetLeft();
DlScalar top = bounds.GetTop();
DlScalar right = bounds.GetRight();
DlScalar bottom = bounds.GetBottom();
// A weight of sqrt(2)/2 is how you really perform conic circular sections,
// but by tweaking it slightly the path will not be recognized as an oval
// and accelerated.
constexpr Scalar kWeight = kSqrt2Over2 - 0.0005f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(right - radii.top_right.width, top));
path_builder.ConicCurveTo(DlPoint(right, top),
DlPoint(right, top + radii.top_right.height),
kWeight);
path_builder.LineTo(DlPoint(right, bottom - radii.bottom_right.height));
path_builder.ConicCurveTo(DlPoint(right, bottom),
DlPoint(right - radii.bottom_right.width, bottom),
kWeight);
path_builder.LineTo(DlPoint(left + radii.bottom_left.width, bottom));
path_builder.ConicCurveTo(DlPoint(left, bottom),
DlPoint(left, bottom - radii.bottom_left.height),
kWeight);
path_builder.LineTo(DlPoint(left, top + radii.top_left.height));
path_builder.ConicCurveTo(DlPoint(left, top),
DlPoint(left + radii.top_left.width, top), //
kWeight);
path_builder.Close();
DlPath path = path_builder.TakePath();
if (!clockwise) {
path = ReflectPath(path);
}
return path;
}
} // namespace
TEST_P(AiksTest, DrawShadowDoesNotOptimizeHourglass) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(100, 100));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.LineTo(DlPoint(300, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowDoesNotOptimizeInnerOuterSpiral) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
int step_count = 20;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(300, 200));
for (int i = 1; i < step_count * 2; i++) {
Scalar angle = (k2Pi * i) / step_count;
Scalar radius = 80.0f + std::abs(i - step_count);
path_builder.LineTo(DlPoint(200, 200) + DlPoint(std::cos(angle) * radius,
std::sin(angle) * radius));
}
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowDoesNotOptimizeOuterInnerSpiral) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
int step_count = 20;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(280, 200));
for (int i = 1; i < step_count * 2; i++) {
Scalar angle = (k2Pi * i) / step_count;
Scalar radius = 100.0f - std::abs(i - step_count);
path_builder.LineTo(DlPoint(200, 200) + DlPoint(std::cos(angle) * radius,
std::sin(angle) * radius));
}
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowDoesNotOptimizeMultipleContours) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(150, 100));
path_builder.LineTo(DlPoint(200, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.Close();
path_builder.MoveTo(DlPoint(250, 100));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(200, 300));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseTriangle) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseTriangle) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.LineTo(DlPoint(100, 300));
path_builder.LineTo(DlPoint(300, 300));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(100, 100));
// Tweak one corner by a sub-pixel amount to prevent recognition as
// a rectangle, but still generating a rectangular shadow.
path_builder.LineTo(DlPoint(299.9, 100));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeRectLTRB(100, 100, 300, 300);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(100, 100));
path_builder.LineTo(DlPoint(100, 300));
path_builder.LineTo(DlPoint(300, 300));
// Tweak one corner by a sub-pixel amount to prevent recognition as
// a rectangle, but still generating a rectangular shadow.
path_builder.LineTo(DlPoint(299.9, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeRectLTRB(100, 100, 300, 300);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseCircle) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
// A weight of sqrt(2) is how you really perform conic circular sections,
// but by tweaking it slightly the path will not be recognized as an oval
// and accelerated.
constexpr Scalar kWeight = kSqrt2Over2 - 0.0005f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.ConicCurveTo(DlPoint(300, 100), DlPoint(300, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 300), DlPoint(200, 300), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 300), DlPoint(100, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 100), DlPoint(200, 100), kWeight);
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeCircle(DlPoint(200, 200), 100);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseCircle) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
// A weight of sqrt(2)/2 is how you really perform conic circular sections,
// but by tweaking it slightly the path will not be recognized as an oval
// and accelerated.
constexpr Scalar kWeight = kSqrt2Over2 - 0.0005f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.ConicCurveTo(DlPoint(100, 100), DlPoint(100, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 300), DlPoint(200, 300), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 300), DlPoint(300, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 100), DlPoint(200, 100), kWeight);
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeCircle(DlPoint(200, 200), 100);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseOval) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
// A weight of sqrt(2) is how you really perform conic circular sections,
// but by tweaking it slightly the path will not be recognized as an oval
// and accelerated.
constexpr Scalar kWeight = kSqrt2Over2 - 0.0005f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 120));
path_builder.ConicCurveTo(DlPoint(300, 120), DlPoint(300, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 280), DlPoint(200, 280), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 280), DlPoint(100, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 120), DlPoint(200, 120), kWeight);
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeOvalLTRB(100, 120, 300, 280);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseOval) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
// A weight of sqrt(2)/2 is how you really perform conic circular sections,
// but by tweaking it slightly the path will not be recognized as an oval
// and accelerated.
constexpr Scalar kWeight = kSqrt2Over2 - 0.0005f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 120));
path_builder.ConicCurveTo(DlPoint(100, 120), DlPoint(100, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(100, 280), DlPoint(200, 280), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 280), DlPoint(300, 200), kWeight);
path_builder.ConicCurveTo(DlPoint(300, 120), DlPoint(200, 120), kWeight);
path_builder.Close();
DlPath path = path_builder.TakePath();
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const DlPath simple_path = DlPath::MakeOvalLTRB(100, 120, 300, 280);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseUniformRoundRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPath path = MakeAlmostRoundRectPath(DlRect::MakeLTRB(100, 100, 300, 300),
DlRoundingRadii::MakeRadius(30), true);
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const RoundRect round_rect =
RoundRect::MakeRectRadius(Rect::MakeLTRB(100, 100, 300, 300), 30);
const DlPath simple_path = DlPath::MakeRoundRect(round_rect);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseUniformRoundRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPath path = MakeAlmostRoundRectPath(DlRect::MakeLTRB(100, 100, 300, 300),
DlRoundingRadii::MakeRadius(30), false);
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
const RoundRect round_rect =
RoundRect::MakeRectRadius(Rect::MakeLTRB(100, 100, 300, 300), 30);
const DlPath simple_path = DlPath::MakeRoundRect(round_rect);
DrawShadowAndCompareMeshes(builder, path, elevation, dpr, &simple_path);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseMultiRadiiRoundRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlRoundingRadii radii = DlRoundingRadii{
.top_left = {80, 60},
.top_right = {20, 25},
.bottom_left = {60, 80},
.bottom_right = {25, 20},
};
DlPath path = MakeAlmostRoundRectPath(DlRect::MakeLTRB(100, 100, 300, 300),
radii, true);
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseMultiRadiiRoundRect) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlRoundingRadii radii = DlRoundingRadii{
.top_left = {80, 60},
.top_right = {20, 25},
.bottom_left = {60, 80},
.bottom_right = {25, 20},
};
DlPath path = MakeAlmostRoundRectPath(DlRect::MakeLTRB(100, 100, 300, 300),
radii, false);
// Path must be convex, but unrecognizable as a simple shape.
ASSERT_TRUE(path.IsConvex());
ASSERT_FALSE(path.IsRect());
ASSERT_FALSE(path.IsOval());
ASSERT_FALSE(path.IsRoundRect());
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseQuadratic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.QuadraticCurveTo(DlPoint(300, 100), DlPoint(300, 200));
path_builder.QuadraticCurveTo(DlPoint(300, 300), DlPoint(200, 300));
path_builder.QuadraticCurveTo(DlPoint(100, 300), DlPoint(100, 200));
path_builder.QuadraticCurveTo(DlPoint(100, 100), DlPoint(200, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseQuadratic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.QuadraticCurveTo(DlPoint(100, 100), DlPoint(100, 200));
path_builder.QuadraticCurveTo(DlPoint(100, 300), DlPoint(200, 300));
path_builder.QuadraticCurveTo(DlPoint(300, 300), DlPoint(300, 200));
path_builder.QuadraticCurveTo(DlPoint(300, 100), DlPoint(200, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseConic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.ConicCurveTo(DlPoint(300, 100), DlPoint(300, 200), 0.4f);
path_builder.ConicCurveTo(DlPoint(300, 300), DlPoint(200, 300), 0.4f);
path_builder.ConicCurveTo(DlPoint(100, 300), DlPoint(100, 200), 0.4f);
path_builder.ConicCurveTo(DlPoint(100, 100), DlPoint(200, 100), 0.4f);
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseConic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.ConicCurveTo(DlPoint(100, 100), DlPoint(100, 200), 0.4f);
path_builder.ConicCurveTo(DlPoint(100, 300), DlPoint(200, 300), 0.4f);
path_builder.ConicCurveTo(DlPoint(300, 300), DlPoint(300, 200), 0.4f);
path_builder.ConicCurveTo(DlPoint(300, 100), DlPoint(200, 100), 0.4f);
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseCubic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.CubicCurveTo(DlPoint(280, 100), DlPoint(300, 120),
DlPoint(300, 200));
path_builder.CubicCurveTo(DlPoint(300, 280), DlPoint(280, 300),
DlPoint(200, 300));
path_builder.CubicCurveTo(DlPoint(120, 300), DlPoint(100, 280),
DlPoint(100, 200));
path_builder.CubicCurveTo(DlPoint(100, 120), DlPoint(120, 100),
DlPoint(200, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseCubic) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.CubicCurveTo(DlPoint(120, 100), DlPoint(100, 120),
DlPoint(100, 200));
path_builder.CubicCurveTo(DlPoint(100, 280), DlPoint(120, 300),
DlPoint(200, 300));
path_builder.CubicCurveTo(DlPoint(280, 300), DlPoint(300, 280),
DlPoint(300, 200));
path_builder.CubicCurveTo(DlPoint(300, 120), DlPoint(280, 100),
DlPoint(200, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseOctagon) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(100, 125));
path_builder.LineTo(DlPoint(125, 100));
path_builder.LineTo(DlPoint(275, 100));
path_builder.LineTo(DlPoint(300, 125));
path_builder.LineTo(DlPoint(300, 275));
path_builder.LineTo(DlPoint(275, 300));
path_builder.LineTo(DlPoint(125, 300));
path_builder.LineTo(DlPoint(100, 275));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeCounterClockwiseOctagon) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(100, 125));
path_builder.LineTo(DlPoint(100, 275));
path_builder.LineTo(DlPoint(125, 300));
path_builder.LineTo(DlPoint(275, 300));
path_builder.LineTo(DlPoint(300, 275));
path_builder.LineTo(DlPoint(300, 125));
path_builder.LineTo(DlPoint(275, 100));
path_builder.LineTo(DlPoint(125, 100));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeWithExtraneousMoveTos) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(0, 0));
path_builder.MoveTo(DlPoint(1000, 1000));
path_builder.MoveTo(DlPoint(100, 50));
path_builder.MoveTo(DlPoint(200, 100));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.Close();
path_builder.MoveTo(DlPoint(1000, 1000));
path_builder.MoveTo(DlPoint(500, 300));
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest, DrawShadowCanOptimizeClockwiseWithExtraColinearVertices) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.LineTo(DlPoint(250, 200));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(200, 300));
path_builder.LineTo(DlPoint(100, 300));
path_builder.LineTo(DlPoint(150, 200));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
TEST_P(AiksTest,
DrawShadowCanOptimizeCounterClockwiseWithExtraColinearVertices) {
DisplayListBuilder builder;
builder.Clear(DlColor::kWhite());
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar dpr = std::max(GetContentScale().x, GetContentScale().y);
Scalar elevation = 30.0f;
DlPathBuilder path_builder;
path_builder.MoveTo(DlPoint(200, 100));
path_builder.LineTo(DlPoint(150, 200));
path_builder.LineTo(DlPoint(100, 300));
path_builder.LineTo(DlPoint(200, 300));
path_builder.LineTo(DlPoint(300, 300));
path_builder.LineTo(DlPoint(250, 200));
path_builder.Close();
DlPath path = path_builder.TakePath();
DrawShadowAndCompareMeshes(builder, path, elevation, dpr);
auto dl = builder.Build();
ASSERT_TRUE(OpenPlaygroundHere(dl));
}
} // namespace testing
} // namespace impeller

View File

@ -9,6 +9,7 @@
#include <unordered_map>
#include <utility>
#include "display_list/dl_vertices.h"
#include "display_list/effects/color_filters/dl_blend_color_filter.h"
#include "display_list/effects/color_filters/dl_matrix_color_filter.h"
#include "display_list/effects/dl_color_filter.h"
@ -20,6 +21,7 @@
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/display_list/color_filter.h"
#include "impeller/display_list/dl_vertices_geometry.h"
#include "impeller/display_list/image_filter.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/entity/contents/atlas_contents.h"
@ -30,6 +32,7 @@
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/framebuffer_blend_contents.h"
#include "impeller/entity/contents/line_contents.h"
#include "impeller/entity/contents/shadow_vertices_contents.h"
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include "impeller/entity/contents/solid_rsuperellipse_blur_contents.h"
#include "impeller/entity/contents/text_contents.h"
@ -45,6 +48,7 @@
#include "impeller/entity/geometry/line_geometry.h"
#include "impeller/entity/geometry/point_field_geometry.h"
#include "impeller/entity/geometry/rect_geometry.h"
#include "impeller/entity/geometry/shadow_path_geometry.h"
#include "impeller/entity/geometry/stroke_path_geometry.h"
#include "impeller/entity/save_layer_utils.h"
#include "impeller/geometry/color.h"
@ -177,24 +181,105 @@ static std::unique_ptr<EntityPassTarget> CreateRenderTarget(
} // namespace
std::shared_ptr<SolidRRectLikeBlurContents>
Canvas::RRectBlurShape::BuildBlurContent() {
return std::make_shared<SolidRRectBlurContents>();
}
class Canvas::RRectBlurShape : public BlurShape {
public:
RRectBlurShape(const Rect& rect, Scalar corner_radius)
: rect_(rect), corner_radius_(corner_radius) {}
Geometry& Canvas::RRectBlurShape::BuildGeometry(Rect rect, Scalar radius) {
return geom_.emplace(rect, Size{radius, radius});
}
Rect GetBounds() const override { return rect_; }
std::shared_ptr<SolidRRectLikeBlurContents>
Canvas::RSuperellipseBlurShape::BuildBlurContent() {
return std::make_shared<SolidRSuperellipseBlurContents>();
}
std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
auto contents = std::make_shared<SolidRRectBlurContents>();
contents->SetSigma(sigma);
contents->SetShape(rect_, corner_radius_);
return contents;
}
Geometry& Canvas::RSuperellipseBlurShape::BuildGeometry(Rect rect,
Scalar radius) {
return geom_.emplace(rect, radius);
}
const Geometry& BuildDrawGeometry() override {
return geom_.emplace(rect_, Size(corner_radius_));
}
private:
const Rect rect_;
const Scalar corner_radius_;
std::optional<RoundRectGeometry> geom_; // optional stack allocation
};
class Canvas::RSuperellipseBlurShape : public BlurShape {
public:
RSuperellipseBlurShape(const Rect& rect, Scalar corner_radius)
: rect_(rect), corner_radius_(corner_radius) {}
Rect GetBounds() const override { return rect_; }
std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
auto contents = std::make_shared<SolidRSuperellipseBlurContents>();
contents->SetSigma(sigma);
contents->SetShape(rect_, corner_radius_);
return contents;
}
const Geometry& BuildDrawGeometry() override {
return geom_.emplace(rect_, corner_radius_);
}
private:
const Rect rect_;
const Scalar corner_radius_;
std::optional<RoundSuperellipseGeometry> geom_; // optional stack allocation
};
class Canvas::PathBlurShape : public BlurShape {
public:
/// Construct a PathBlurShape from a path source, a set of shadow vertices
/// (typically produced by ShadowPathGeometry) and the sigma that was used
/// to generate the vertex mesh.
///
/// The sigma was already used to generate the shadow vertices, so it is
/// provided here only to make sure it matches the sigma we will see in
/// our BuildBlurContent method.
///
/// The source was used to generate the mesh and it might be used again
/// for the SOLID mask operation so we save it here in case the mask
/// rendering code calls our BuildDrawGeometry method. Its lifetime
/// must survive the lifetime of this object, typically because the
/// source object was stack allocated not long before this object is
/// also being stack allocated.
PathBlurShape(const PathSource& source [[clang::lifetimebound]],
std::shared_ptr<ShadowVertices> shadow_vertices,
Sigma sigma)
: sigma_(sigma),
source_(source),
shadow_vertices_(std::move(shadow_vertices)) {}
Rect GetBounds() const override {
return shadow_vertices_->GetBounds().value_or(Rect());
}
std::shared_ptr<SolidBlurContents> BuildBlurContent(Sigma sigma) override {
// We have to use the sigma to generate the mesh up front in order to
// even know if we can perform the operation, but then the method that
// actually uses our contents informs us of the sigma, but it's too
// late to make use of it. Instead we remember what sigma we used and
// make sure they match.
FML_DCHECK(sigma_.sigma == sigma.sigma);
return ShadowVerticesContents::Make(shadow_vertices_);
}
const Geometry& BuildDrawGeometry() override {
return source_geometry_.emplace(source_);
}
private:
const Sigma sigma_;
const PathSource& source_;
const std::shared_ptr<ShadowVertices> shadow_vertices_;
// optional stack allocation - for BuildGeometry
std::optional<FillPathFromSourceGeometry> source_geometry_;
};
Canvas::Canvas(ContentContext& renderer,
const RenderTarget& render_target,
@ -326,6 +411,12 @@ void Canvas::RestoreToCount(size_t count) {
}
void Canvas::DrawPath(const flutter::DlPath& path, const Paint& paint) {
if (IsShadowBlurDrawOperation(paint)) {
if (AttemptDrawBlurredPathSource(path, paint)) {
return;
}
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetBlendMode(paint.blend_mode);
@ -456,25 +547,7 @@ bool Canvas::AttemptDrawAntialiasedCircle(const Point& center,
return true;
}
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
Size corner_radii,
const Paint& paint) {
RRectBlurShape rrect_shape;
return AttemptDrawBlurredRRectLike(rect, corner_radii, paint, rrect_shape);
}
bool Canvas::AttemptDrawBlurredRSuperellipse(const Rect& rect,
Size corner_radii,
const Paint& paint) {
RSuperellipseBlurShape rsuperellipse_shape;
return AttemptDrawBlurredRRectLike(rect, corner_radii, paint,
rsuperellipse_shape);
}
bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
Size corner_radii,
const Paint& paint,
RRectLikeBlurShape& shape) {
bool Canvas::IsShadowBlurDrawOperation(const Paint& paint) {
if (paint.style != Paint::Style::kFill) {
return false;
}
@ -488,15 +561,84 @@ bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
}
// A blur sigma that is not positive enough should not result in a blur.
// We test both the sigma value and the converted radius value as the
// algorithms might use either and either indicates the blur is too small
// to be noticeable.
if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) {
return false;
}
// The current rrect blur math doesn't work on ovals.
if (fabsf(corner_radii.width - corner_radii.height) > kEhCloseEnough) {
Radius radius = paint.mask_blur_descriptor->sigma;
if (radius.radius <= kEhCloseEnough) {
return false;
}
Scalar corner_radius = corner_radii.width;
return true;
}
bool Canvas::AttemptDrawBlurredPathSource(const PathSource& source,
const Paint& paint) {
FML_DCHECK(IsShadowBlurDrawOperation);
// This has_value() test should always succeed as it is checked by the
// IsShadowBlurDrawOperation method which should have been called before
// this method, but we check again here to avoid warnings from the
// following code.
if (paint.mask_blur_descriptor.has_value()) {
// This value was determined by empirical eyesight tests so that the
// shadow mesh results will match the results of the shape-specific
// optimized shadow shaders.
static constexpr Scalar kSigmaScale = 2.8f;
Sigma sigma = paint.mask_blur_descriptor->sigma;
const Matrix& matrix = GetCurrentTransform();
Scalar basis_scale = matrix.GetMaxBasisLengthXY();
Scalar device_radius = sigma.sigma * kSigmaScale * basis_scale;
std::shared_ptr<ShadowVertices> shadow_vertices =
ShadowPathGeometry::MakeAmbientShadowVertices(
renderer_.GetTessellator(), source, device_radius, matrix);
if (shadow_vertices) {
PathBlurShape shape(source, std::move(shadow_vertices), sigma);
return AttemptDrawBlur(shape, paint);
}
}
return false;
}
Scalar Canvas::GetCommonRRectLikeRadius(const RoundingRadii& radii) {
if (!radii.AreAllCornersSame()) {
return -1;
}
const Size& corner_radii = radii.top_left;
if (ScalarNearlyEqual(corner_radii.width, corner_radii.height)) {
return corner_radii.width;
}
return -1;
}
bool Canvas::AttemptDrawBlurredRRect(const RoundRect& round_rect,
const Paint& paint) {
Scalar radius = GetCommonRRectLikeRadius(round_rect.GetRadii());
if (radius < 0) {
RoundRectPathSource source(round_rect);
return AttemptDrawBlurredPathSource(source, paint);
}
RRectBlurShape shape(round_rect.GetBounds(), radius);
return AttemptDrawBlur(shape, paint);
}
bool Canvas::AttemptDrawBlurredRSuperellipse(const RoundSuperellipse& rse,
const Paint& paint) {
Scalar radius = GetCommonRRectLikeRadius(rse.GetRadii());
if (radius < 0) {
RoundSuperellipsePathSource source(rse);
return AttemptDrawBlurredPathSource(source, paint);
}
RSuperellipseBlurShape shape(rse.GetBounds(), radius);
return AttemptDrawBlur(shape, paint);
}
bool Canvas::AttemptDrawBlur(BlurShape& shape, const Paint& paint) {
FML_DCHECK(IsShadowBlurDrawOperation(paint));
// For symmetrically mask blurred solid RRects, absorb the mask blur and use
// a faster SDF approximation.
@ -510,6 +652,14 @@ bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
Paint rrect_paint = {.mask_blur_descriptor = paint.mask_blur_descriptor};
if (!rrect_paint.mask_blur_descriptor.has_value()) {
// This should never happen in practice because the caller would have
// first called |IsShadowBlurDrawOperation| on the paint object, but
// we test anyway to make the compiler happy about the dereferences
// below.
return false;
}
// In some cases, we need to render the mask blur to a separate layer.
//
// 1. If the blur style is normal, we'll be drawing using one draw call and
@ -534,7 +684,7 @@ bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
paint.image_filter) ||
(paint.mask_blur_descriptor->style == FilterContents::BlurStyle::kSolid &&
(!rrect_color.IsOpaque() || paint.blend_mode != BlendMode::kSrcOver))) {
Rect render_bounds = rect;
Rect render_bounds = shape.GetBounds();
if (paint.mask_blur_descriptor->style !=
FilterContents::BlurStyle::kInner) {
render_bounds =
@ -556,13 +706,12 @@ bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
Save(1u);
}
auto draw_blurred_rrect = [this, &rect, corner_radius, &rrect_paint,
&shape]() {
auto contents = shape.BuildBlurContent();
auto draw_blurred_rrect = [this, &rrect_paint, &shape]() {
std::shared_ptr<SolidBlurContents> contents =
shape.BuildBlurContent(rrect_paint.mask_blur_descriptor->sigma);
FML_DCHECK(contents);
contents->SetColor(rrect_paint.color);
contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma);
contents->SetShape(rect, corner_radius);
Entity blurred_rrect_entity;
blurred_rrect_entity.SetTransform(GetCurrentTransform());
@ -587,19 +736,19 @@ bool Canvas::AttemptDrawBlurredRRectLike(const Rect& rect,
entity.SetTransform(GetCurrentTransform());
entity.SetBlendMode(rrect_paint.blend_mode);
Geometry& geom = shape.BuildGeometry(rect, corner_radius);
const Geometry& geom = shape.BuildDrawGeometry();
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, rrect_paint,
/*reuse_depth=*/true);
break;
}
case FilterContents::BlurStyle::kOuter: {
Geometry& geom = shape.BuildGeometry(rect, corner_radius);
const Geometry& geom = shape.BuildDrawGeometry();
ClipGeometry(geom, Entity::ClipOperation::kDifference);
draw_blurred_rrect();
break;
}
case FilterContents::BlurStyle::kInner: {
Geometry& geom = shape.BuildGeometry(rect, corner_radius);
const Geometry& geom = shape.BuildDrawGeometry();
ClipGeometry(geom, Entity::ClipOperation::kIntersect);
draw_blurred_rrect();
break;
@ -660,8 +809,11 @@ void Canvas::DrawDashedLine(const Point& p0,
}
void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
if (AttemptDrawBlurredRRect(rect, {}, paint)) {
return;
if (IsShadowBlurDrawOperation(paint)) {
RRectBlurShape shape(rect, 0.0f);
if (AttemptDrawBlur(shape, paint)) {
return;
}
}
Entity entity;
@ -689,8 +841,20 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
return;
}
if (AttemptDrawBlurredRRect(rect, rect.GetSize() * 0.5f, paint)) {
return;
if (IsShadowBlurDrawOperation(paint)) {
if (rect.IsSquare()) {
// RRectBlurShape takes the corner radii which are half of the
// overall width and height of the DrawOval bounds rect.
RRectBlurShape shape(rect, rect.GetWidth() * 0.5f);
if (AttemptDrawBlur(shape, paint)) {
return;
}
} else {
EllipsePathSource source(rect);
if (AttemptDrawBlurredPathSource(source, paint)) {
return;
}
}
}
Entity entity;
@ -759,22 +923,22 @@ void Canvas::DrawArc(const Arc& arc, const Paint& paint) {
}
void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) {
auto& rect = round_rect.GetBounds();
auto& radii = round_rect.GetRadii();
if (radii.AreAllCornersSame()) {
if (AttemptDrawBlurredRRect(rect, radii.top_left, paint)) {
if (IsShadowBlurDrawOperation(paint)) {
if (AttemptDrawBlurredRRect(round_rect, paint)) {
return;
}
}
if (paint.style == Paint::Style::kFill) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetBlendMode(paint.blend_mode);
if (round_rect.GetRadii().AreAllCornersSame() &&
paint.style == Paint::Style::kFill) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetBlendMode(paint.blend_mode);
RoundRectGeometry geom(rect, radii.top_left);
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
return;
}
RoundRectGeometry geom(round_rect.GetBounds(),
round_rect.GetRadii().top_left);
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
return;
}
Entity entity;
@ -808,11 +972,10 @@ void Canvas::DrawDiffRoundRect(const RoundRect& outer,
void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse,
const Paint& paint) {
auto& rect = round_superellipse.GetBounds();
auto& radii = round_superellipse.GetRadii();
if (radii.AreAllCornersSame() &&
AttemptDrawBlurredRSuperellipse(rect, radii.top_left, paint)) {
return;
if (IsShadowBlurDrawOperation(paint)) {
if (AttemptDrawBlurredRSuperellipse(round_superellipse, paint)) {
return;
}
}
Entity entity;
@ -820,7 +983,8 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse,
entity.SetBlendMode(paint.blend_mode);
if (paint.style == Paint::Style::kFill) {
RoundSuperellipseGeometry geom(rect, radii);
RoundSuperellipseGeometry geom(round_superellipse.GetBounds(),
round_superellipse.GetRadii());
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
} else {
StrokeRoundSuperellipseGeometry geom(round_superellipse, paint.stroke);
@ -831,11 +995,13 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse,
void Canvas::DrawCircle(const Point& center,
Scalar radius,
const Paint& paint) {
Size half_size(radius, radius);
if (AttemptDrawBlurredRRect(
Rect::MakeOriginSize(center - half_size, half_size * 2),
{radius, radius}, paint)) {
return;
if (IsShadowBlurDrawOperation(paint)) {
Rect bounds = Rect::MakeLTRB(center.x - radius, center.y - radius,
center.x + radius, center.y + radius);
RRectBlurShape shape(bounds, radius);
if (AttemptDrawBlur(shape, paint)) {
return;
}
}
if (AttemptDrawAntialiasedCircle(center, radius, paint)) {

View File

@ -283,31 +283,17 @@ class Canvas {
bool EnsureFinalMipmapGeneration() const;
private:
class RRectLikeBlurShape {
class BlurShape {
public:
virtual ~RRectLikeBlurShape() = default;
virtual std::shared_ptr<SolidRRectLikeBlurContents> BuildBlurContent() = 0;
virtual Geometry& BuildGeometry(Rect rect, Scalar radius) = 0;
};
class RRectBlurShape : public RRectLikeBlurShape {
public:
std::shared_ptr<SolidRRectLikeBlurContents> BuildBlurContent() override;
Geometry& BuildGeometry(Rect rect, Scalar radius) override;
private:
std::optional<RoundRectGeometry> geom_; // optional stack allocation
};
class RSuperellipseBlurShape : public RRectLikeBlurShape {
public:
std::shared_ptr<SolidRRectLikeBlurContents> BuildBlurContent() override;
Geometry& BuildGeometry(Rect rect, Scalar radius) override;
private:
std::optional<RoundSuperellipseGeometry>
geom_; // optional stack allocation
virtual ~BlurShape() = default;
virtual Rect GetBounds() const = 0;
virtual std::shared_ptr<SolidBlurContents> BuildBlurContent(
Sigma sigma) = 0;
virtual const Geometry& BuildDrawGeometry() = 0;
};
class RRectBlurShape;
class RSuperellipseBlurShape;
class PathBlurShape;
ContentContext& renderer_;
RenderTarget render_target_;
@ -395,22 +381,27 @@ class Canvas {
void AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth = false);
/// Returns true if this operation is consistent with a DrawShadow-like
/// operation.
static bool IsShadowBlurDrawOperation(const Paint& paint);
bool AttemptDrawAntialiasedCircle(const Point& center,
Scalar radius,
const Paint& paint);
bool AttemptDrawBlurredRRect(const Rect& rect,
Size corner_radii,
const Paint& paint);
/// Returns the radius common to both width and height of all corners,
/// or -1 if the radii are not uniform.
static Scalar GetCommonRRectLikeRadius(const RoundingRadii& radii);
bool AttemptDrawBlurredRSuperellipse(const Rect& rect,
Size corner_radii,
bool AttemptDrawBlurredPathSource(const PathSource& source,
const Paint& paint);
bool AttemptDrawBlurredRRect(const RoundRect& round_rect, const Paint& paint);
bool AttemptDrawBlurredRSuperellipse(const RoundSuperellipse& rse,
const Paint& paint);
bool AttemptDrawBlurredRRectLike(const Rect& rect,
Size corner_radii,
const Paint& paint,
RRectLikeBlurShape& shape);
bool AttemptDrawBlur(BlurShape& shape, const Paint& paint);
/// For simple DrawImageRect calls, optimize any draws with a color filter
/// into the corresponding atlas draw.

View File

@ -43,6 +43,8 @@ impeller_shaders("entity_shaders") {
"shaders/rrect_like_blur.vert",
"shaders/rsuperellipse_blur.frag",
"shaders/runtime_effect.vert",
"shaders/shadow_vertices.frag",
"shaders/shadow_vertices.vert",
"shaders/solid_fill.frag",
"shaders/solid_fill.vert",
"shaders/texture_fill.frag",
@ -178,6 +180,8 @@ impeller_component("entity") {
"contents/radial_gradient_contents.h",
"contents/runtime_effect_contents.cc",
"contents/runtime_effect_contents.h",
"contents/shadow_vertices_contents.cc",
"contents/shadow_vertices_contents.h",
"contents/solid_color_contents.cc",
"contents/solid_color_contents.h",
"contents/solid_rrect_blur_contents.cc",
@ -228,6 +232,8 @@ impeller_component("entity") {
"geometry/round_rect_geometry.h",
"geometry/round_superellipse_geometry.cc",
"geometry/round_superellipse_geometry.h",
"geometry/shadow_path_geometry.cc",
"geometry/shadow_path_geometry.h",
"geometry/stroke_path_geometry.cc",
"geometry/stroke_path_geometry.h",
"geometry/superellipse_geometry.cc",
@ -286,6 +292,7 @@ impeller_component("entity_unittests") {
"entity_playground.h",
"entity_unittests.cc",
"geometry/geometry_unittests.cc",
"geometry/shadow_path_geometry_unittests.cc",
"render_target_cache_unittests.cc",
"save_layer_utils_unittests.cc",
]
@ -298,6 +305,7 @@ impeller_component("entity_unittests") {
"//flutter/display_list/testing:display_list_testing",
"//flutter/impeller/renderer/testing:mocks",
"//flutter/impeller/typographer/backends/skia:typographer_skia_backend",
"//flutter/testing",
"//flutter/txt",
]
}

View File

@ -290,6 +290,7 @@ struct ContentContext::Pipelines {
Variants<RadialGradientUniformFillPipeline> radial_gradient_uniform_fill;
Variants<RRectBlurPipeline> rrect_blur;
Variants<RSuperellipseBlurPipeline> rsuperellipse_blur;
Variants<ShadowVerticesShader> shadow_vertices_;
Variants<SolidFillPipeline> solid_fill;
Variants<SrgbToLinearFilterPipeline> srgb_to_linear_filter;
Variants<SweepGradientFillPipeline> sweep_gradient_fill;
@ -715,6 +716,7 @@ ContentContext::ContentContext(
options_trianglestrip);
pipelines_->color_matrix_color_filter.CreateDefault(*context_,
options_trianglestrip);
pipelines_->shadow_vertices_.CreateDefault(*context_, options);
pipelines_->vertices_uber_1_.CreateDefault(*context_, options,
{supports_decal});
pipelines_->vertices_uber_2_.CreateDefault(*context_, options,
@ -1508,6 +1510,11 @@ PipelineRef ContentContext::GetFramebufferBlendSoftLightPipeline(
return GetPipeline(this, pipelines_->framebuffer_blend_softlight, opts);
}
PipelineRef ContentContext::GetDrawShadowVerticesPipeline(
ContentContextOptions opts) const {
return GetPipeline(this, pipelines_->shadow_vertices_, opts);
}
PipelineRef ContentContext::GetDrawVerticesUberPipeline(
BlendMode blend_mode,
ContentContextOptions opts) const {

View File

@ -162,6 +162,7 @@ class ContentContext {
PipelineRef GetDestinationOutBlendPipeline(ContentContextOptions opts) const;
PipelineRef GetDestinationOverBlendPipeline(ContentContextOptions opts) const;
PipelineRef GetDownsamplePipeline(ContentContextOptions opts) const;
PipelineRef GetDrawShadowVerticesPipeline(ContentContextOptions opts) const;
PipelineRef GetDownsampleBoundedPipeline(ContentContextOptions opts) const;
PipelineRef GetDrawVerticesUberPipeline(BlendMode blend_mode, ContentContextOptions opts) const;
PipelineRef GetFastGradientPipeline(ContentContextOptions opts) const;

View File

@ -48,6 +48,8 @@
#include "impeller/entity/rrect_blur.frag.h"
#include "impeller/entity/rrect_like_blur.vert.h"
#include "impeller/entity/rsuperellipse_blur.frag.h"
#include "impeller/entity/shadow_vertices.frag.h"
#include "impeller/entity/shadow_vertices.vert.h"
#include "impeller/entity/solid_fill.frag.h"
#include "impeller/entity/solid_fill.vert.h"
#include "impeller/entity/srgb_to_linear_filter.frag.h"
@ -145,6 +147,7 @@ using RadialGradientSSBOFillPipeline = GradientPipelineHandle<RadialGradientSsbo
using RadialGradientUniformFillPipeline = GradientPipelineHandle<RadialGradientUniformFillFragmentShader>;
using RRectBlurPipeline = RenderPipelineHandle<RrectLikeBlurVertexShader, RrectBlurFragmentShader>;
using RSuperellipseBlurPipeline = RenderPipelineHandle<RrectLikeBlurVertexShader, RsuperellipseBlurFragmentShader>;
using ShadowVerticesShader = RenderPipelineHandle<ShadowVerticesVertexShader, ShadowVerticesFragmentShader>;
using SolidFillPipeline = RenderPipelineHandle<SolidFillVertexShader, SolidFillFragmentShader>;
using SrgbToLinearFilterPipeline = RenderPipelineHandle<FilterPositionVertexShader, SrgbToLinearFilterFragmentShader>;
using SweepGradientFillPipeline = GradientPipelineHandle<SweepGradientFillFragmentShader>;

View File

@ -0,0 +1,83 @@
// 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.
#include "shadow_vertices_contents.h"
#include <format>
#include "fml/logging.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/contents/filters/blend_filter_contents.h"
#include "impeller/entity/contents/pipelines.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/entity/geometry/vertices_geometry.h"
#include "impeller/geometry/color.h"
#include "impeller/renderer/render_pass.h"
namespace impeller {
//------------------------------------------------------
// ShadowVerticesContents
ShadowVerticesContents::ShadowVerticesContents(
const std::shared_ptr<ShadowVertices>& geometry)
: geometry_(geometry) {}
ShadowVerticesContents::~ShadowVerticesContents() {}
std::shared_ptr<ShadowVerticesContents> ShadowVerticesContents::Make(
const std::shared_ptr<ShadowVertices>& geometry) {
return std::make_shared<ShadowVerticesContents>(geometry);
}
std::optional<Rect> ShadowVerticesContents::GetCoverage(
const Entity& entity) const {
return geometry_->GetBounds();
}
void ShadowVerticesContents::SetColor(Color color) {
shadow_color_ = color;
}
bool ShadowVerticesContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = ShadowVerticesVertexShader;
using FS = ShadowVerticesFragmentShader;
GeometryResult geometry_result =
geometry_->GetPositionBuffer(renderer, entity, pass);
if (geometry_result.vertex_buffer.vertex_count == 0) {
return true;
}
FML_DCHECK(geometry_result.mode == GeometryResult::Mode::kNormal);
#ifdef IMPELLER_DEBUG
pass.SetCommandLabel("DrawShadow VertexMesh");
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer));
auto options = OptionsFromPassAndEntity(pass, entity);
options.primitive_type = geometry_result.type;
pass.SetPipeline(renderer.GetDrawShadowVerticesPipeline(options));
VS::FrameInfo frame_info;
FS::FragInfo frag_info;
frame_info.mvp = entity.GetShaderTransform(pass);
frag_info.shadow_color = shadow_color_.Premultiply();
auto& host_buffer = renderer.GetTransientsDataBuffer();
FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));
return pass.Draw().ok();
}
} // namespace impeller

View File

@ -0,0 +1,52 @@
// 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_IMPELLER_ENTITY_CONTENTS_SHADOW_VERTICES_CONTENTS_H_
#define FLUTTER_IMPELLER_ENTITY_CONTENTS_SHADOW_VERTICES_CONTENTS_H_
#include <memory>
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/geometry/shadow_path_geometry.h"
#include "impeller/geometry/color.h"
namespace impeller {
/// A vertices contents for (optional) per-color vertices + texture and any
/// blend mode.
class ShadowVerticesContents final : public SolidBlurContents {
public:
static std::shared_ptr<ShadowVerticesContents> Make(
const std::shared_ptr<ShadowVertices>& geometry);
// |SolidBlurContents|
void SetColor(Color color) override;
// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;
explicit ShadowVerticesContents(
const std::shared_ptr<ShadowVertices>& geometry);
~ShadowVerticesContents() override;
private:
const std::shared_ptr<ShadowVertices> geometry_;
Color shadow_color_;
ShadowVerticesContents(const ShadowVerticesContents&) = delete;
ShadowVerticesContents& operator=(const ShadowVerticesContents&) = delete;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_SHADOW_VERTICES_CONTENTS_H_

View File

@ -15,9 +15,19 @@
namespace impeller {
/// @brief A base class for any accelerated single color blur Contents
/// that lets the |Canvas::AttemptDrawBlur| call deliver the
/// color after the contents has been constructed and the method
/// has a chance to re-consider the actual color that will be
/// used to render the shadow.
class SolidBlurContents : public Contents {
public:
virtual void SetColor(Color color) = 0;
};
/// @brief A base class for SolidRRectBlurContents and
/// SolidRSuperellipseBlurContents.
class SolidRRectLikeBlurContents : public Contents {
class SolidRRectLikeBlurContents : public SolidBlurContents {
public:
~SolidRRectLikeBlurContents() override;
@ -25,7 +35,8 @@ class SolidRRectLikeBlurContents : public Contents {
void SetSigma(Sigma sigma);
void SetColor(Color color);
// |SolidBlurContents|
void SetColor(Color color) override;
Color GetColor() const;

View File

@ -92,6 +92,13 @@ bool FillPathSourceGeometry::CoversArea(const Matrix& transform,
return coverage.Contains(rect);
}
FillPathFromSourceGeometry::FillPathFromSourceGeometry(const PathSource& source)
: FillPathSourceGeometry(std::nullopt), source_(source) {}
const PathSource& FillPathFromSourceGeometry::GetSource() const {
return source_;
}
FillPathGeometry::FillPathGeometry(const flutter::DlPath& path,
std::optional<Rect> inner_rect)
: FillPathSourceGeometry(inner_rect), path_(path) {}

View File

@ -49,6 +49,19 @@ class FillPathSourceGeometry : public Geometry {
FillPathSourceGeometry& operator=(const FillPathSourceGeometry&) = delete;
};
/// @brief A Geometry that produces fillable vertices from a |PathSource| object
/// using the |FillPathSourceGeometry|.
class FillPathFromSourceGeometry final : public FillPathSourceGeometry {
public:
explicit FillPathFromSourceGeometry(const PathSource& source);
protected:
const PathSource& GetSource() const override;
private:
const PathSource& source_;
};
/// @brief A Geometry that produces fillable vertices from a |DlPath| object
/// using the |FillPathSourceGeometry| base class and the inherent
/// ability for a |DlPath| object to perform path iteration.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,116 @@
// 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_IMPELLER_ENTITY_GEOMETRY_SHADOW_PATH_GEOMETRY_H_
#define FLUTTER_IMPELLER_ENTITY_GEOMETRY_SHADOW_PATH_GEOMETRY_H_
#include "flutter/impeller/entity/geometry/geometry.h"
#include "flutter/impeller/geometry/path_source.h"
#include "flutter/impeller/tessellator/tessellator.h"
namespace impeller {
/// A class to hold a vertex mesh for rendering shadows. The vertices are
/// each associated with a gaussian coefficent that represents where that
/// vertex lives in the shadow from a value of 1.0 (at the edge of or fully
/// in the darkest part of the umbra) to 0.0 at the edge of or fully outside
/// the penumbra).
///
/// The vertices are also associated with a vector of indices that assemble
/// them into a mesh that covers the full umbra and penumbra of the shape.
///
/// The mesh is usually intended to be rendered at device (pixel) resolution.
class ShadowVertices {
public:
static const std::shared_ptr<ShadowVertices> kEmpty;
static std::shared_ptr<ShadowVertices> Make(std::vector<Point> vertices,
std::vector<uint16_t> indices,
std::vector<Scalar> gaussians) {
return std::make_shared<ShadowVertices>(
std::move(vertices), std::move(indices), std::move(gaussians));
}
constexpr ShadowVertices() {}
constexpr ShadowVertices(std::vector<Point> vertices,
std::vector<uint16_t> indices,
std::vector<Scalar> gaussians)
: vertices_(std::move(vertices)),
indices_(std::move(indices)),
gaussians_(std::move(gaussians)) {}
/// The count of the unique (duplicates minimized) vertices in the mesh.
/// This number is also the count of gaussian coefficients in the mesh
/// since the two are assigned 1:1.
size_t GetVertexCount() const { return vertices_.size(); }
/// The count of the indices that define the mesh.
size_t GetIndexCount() const { return indices_.size(); }
const std::vector<Point>& GetVertices() const { return vertices_; }
const std::vector<uint16_t>& GetIndices() const { return indices_; }
const std::vector<Scalar>& GetGaussians() const { return gaussians_; }
/// True if and only if there was no shadow for the shape and therefore
/// no mesh to generate.
bool IsEmpty() const { return vertices_.empty(); }
std::optional<Rect> GetBounds() const;
GeometryResult GetPositionBuffer(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const;
private:
const std::vector<Point> vertices_;
const std::vector<uint16_t> indices_;
const std::vector<Scalar> gaussians_;
};
/// A class to compute and return the |ShadowVertices| for a path source
/// viewed under a given transform. The |occluder_height| is measured in
/// device pixels. The geometry of the |PathSource| is transformed by the
/// indicated matrix to produce a device space set of vertices, and the
/// shadow mesh is inset and outset by the indicated |occluder_height|
/// without any adjustment for the matrix. The results are un-transformed
/// and returned back iin the |ShadowVertices| in the original coordinate
/// system.
class ShadowPathGeometry {
public:
ShadowPathGeometry(Tessellator& tessellator,
const Matrix& matrix,
const PathSource& source,
Scalar occluder_height);
bool CanRender() const;
/// Returns true if this shadow has no effect, is not visible.
bool IsEmpty() const;
/// Returns a reference to the generated vertices, or null if the algorithm
/// failed to produce a mesh.
const std::shared_ptr<ShadowVertices>& GetShadowVertices() const;
/// Takes (returns the only copy of via std::move) the shadow vertices
/// or null if the algorithm failed to produce a mesh.
const std::shared_ptr<ShadowVertices> TakeShadowVertices();
/// Constructs a shadow mesh for the given |PathSource| at the given
/// |matrix| and with the indicated device-space |occluder_height|.
/// The tessellator is used to get a cached set of |Trigs| for the
/// radii associated with the mesh around various corners in the path.
static std::shared_ptr<ShadowVertices> MakeAmbientShadowVertices(
Tessellator& tessellator,
const PathSource& source,
Scalar occluder_height,
const Matrix& matrix);
private:
std::shared_ptr<ShadowVertices> shadow_vertices_;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_SHADOW_PATH_GEOMETRY_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
// 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.
#include <impeller/gaussian.glsl>
#include <impeller/types.glsl>
uniform FragInfo {
// shadow_color is the color supplied to DrawShadow. It will be modulated
// by the gaussian opacity of the shadow, computed from the coefficient
// in the mesh vertex data.
f16vec4 shadow_color;
}
frag_info;
// v_gaussian will contain the interpolated gaussian coefficient from the
// mesh per-vertex data. It determines where in the gaussian curve of the
// umbra and penumbra we are with 0.0 representing the outermost part of
// the penumbra and 1.0 representing the innermost umbra.
in float16_t v_gaussian;
out f16vec4 frag_color;
// A shader that modulates the shadow color by the gaussian integral
// value computed from the interpolated v_gaussian coefficient.
void main() {
frag_color =
frag_info.shadow_color * IPHalfFractionToFastGaussianCDF(v_gaussian);
}

View File

@ -0,0 +1,20 @@
// 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.
#include <impeller/types.glsl>
uniform FrameInfo {
mat4 mvp;
}
frame_info;
in vec2 position;
in float gaussian;
out float16_t v_gaussian;
void main() {
gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0);
v_gaussian = float16_t(gaussian);
}

View File

@ -83,6 +83,7 @@ impeller_component("geometry_unittests") {
"geometry_unittests.cc",
"matrix_unittests.cc",
"path_source_unittests.cc",
"point_unittests.cc",
"rational_unittests.cc",
"rect_unittests.cc",
"round_rect_unittests.cc",

View File

@ -6,6 +6,7 @@
#include "flutter/display_list/geometry/dl_path.h"
#include "flutter/display_list/geometry/dl_path_builder.h"
#include "impeller/entity/geometry/shadow_path_geometry.h"
#include "impeller/entity/geometry/stroke_path_geometry.h"
#include "impeller/tessellator/tessellator_libtess.h"
@ -33,6 +34,22 @@ flutter::DlPath CreateQuadratic(bool closed);
flutter::DlPath CreateRRect();
/// Create a rounded superellipse.
flutter::DlPath CreateRSuperellipse();
/// Create a clockwise triangle path.
flutter::DlPath CreateClockwiseTriangle();
/// Create a counter-clockwise triangle path.
flutter::DlPath CreateCounterClockwiseTriangle();
/// Create a clockwise rect path.
flutter::DlPath CreateClockwiseRect();
/// Create a counter-clockwise rect path.
flutter::DlPath CreateCounterClockwiseRect();
/// Create a clockwise multi-radii round rect path.
flutter::DlPath CreateClockwiseMultiRadiiRoundRect();
/// Create a counter-clockwise multi-radii round rect path.
flutter::DlPath CreateCounterClockwiseMultiRadiiRoundRect();
/// Create a clockwise polygonal path.
flutter::DlPath CreateClockwisePolygon();
/// Create a counter-clockwise polygonal path.
flutter::DlPath CreateCounterClockwisePolygon();
} // namespace
static TessellatorLibtess tess;
@ -85,6 +102,40 @@ static void BM_Convex(benchmark::State& state, Args&&... args) {
state.counters["TotalPointCount"] = point_count;
}
template <class... Args>
static void BM_ShadowPathVerticesImpeller(benchmark::State& state,
Args&&... args) {
auto args_tuple = std::make_tuple(std::move(args)...);
auto path = std::get<flutter::DlPath>(args_tuple);
auto height = std::get<Scalar>(args_tuple);
auto matrix = std::get<Matrix>(args_tuple);
Tessellator tessellator;
while (state.KeepRunning()) {
auto result = ShadowPathGeometry::MakeAmbientShadowVertices(
tessellator, path, height, matrix);
FML_CHECK(result != nullptr);
}
}
#define MAKE_SHADOW_BENCHMARK_CAPTURE(clockwise, shape, backend) \
BENCHMARK_CAPTURE(BM_ShadowPathVertices##backend, \
shadow_##clockwise##_##shape##_##backend, \
Create##clockwise##shape(), 20.0f, Matrix{})
#define MAKE_SHADOW_BENCHMARK_SHAPE_CAPTURE(shape, backend) \
MAKE_SHADOW_BENCHMARK_CAPTURE(Clockwise, shape, backend); \
MAKE_SHADOW_BENCHMARK_CAPTURE(CounterClockwise, shape, backend)
#define MAKE_SHADOW_BENCHMARK_CAPTURE_ALL_SHAPES(backend) \
MAKE_SHADOW_BENCHMARK_SHAPE_CAPTURE(Triangle, backend); \
MAKE_SHADOW_BENCHMARK_SHAPE_CAPTURE(Rect, backend); \
MAKE_SHADOW_BENCHMARK_SHAPE_CAPTURE(MultiRadiiRoundRect, backend); \
MAKE_SHADOW_BENCHMARK_SHAPE_CAPTURE(Polygon, backend)
MAKE_SHADOW_BENCHMARK_CAPTURE_ALL_SHAPES(Impeller);
#define MAKE_STROKE_PATH_BENCHMARK_CAPTURE(path, cap, join, closed) \
BENCHMARK_CAPTURE(BM_StrokePath, stroke_##path##_##cap##_##join, \
Create##path(closed), Cap::k##cap, Join::k##join)
@ -116,6 +167,146 @@ MAKE_STROKE_PATH_BENCHMARK_CAPTURE(RSuperellipse, Butt, Round, );
namespace {
flutter::DlPath CreateClockwiseTriangle() {
flutter::DlPathBuilder builder;
builder.MoveTo(flutter::DlPoint(100, 100));
builder.LineTo(flutter::DlPoint(300, 100));
builder.LineTo(flutter::DlPoint(200, 300));
builder.Close();
return builder.TakePath();
}
flutter::DlPath CreateCounterClockwiseTriangle() {
flutter::DlPathBuilder builder;
builder.MoveTo(flutter::DlPoint(100, 100));
builder.LineTo(flutter::DlPoint(200, 300));
builder.LineTo(flutter::DlPoint(300, 100));
builder.Close();
return builder.TakePath();
}
flutter::DlPath CreateClockwiseRect() {
flutter::DlPathBuilder builder;
builder.MoveTo(flutter::DlPoint(100, 100));
builder.LineTo(flutter::DlPoint(300, 100));
builder.LineTo(flutter::DlPoint(300, 300));
builder.LineTo(flutter::DlPoint(100, 300));
builder.Close();
return builder.TakePath();
}
flutter::DlPath CreateCounterClockwiseRect() {
flutter::DlPathBuilder builder;
builder.MoveTo(flutter::DlPoint(100, 100));
builder.LineTo(flutter::DlPoint(100, 300));
builder.LineTo(flutter::DlPoint(300, 300));
builder.LineTo(flutter::DlPoint(300, 100));
builder.Close();
return builder.TakePath();
}
class HorizontalPathFlipper : private flutter::DlPathReceiver {
public:
HorizontalPathFlipper(const flutter::DlPath& path, Scalar flip_coordinate)
: flip_coordinate_(flip_coordinate) {
path.Dispatch(*this);
}
flutter::DlPath TakePath() { return builder_.TakePath(); }
private:
const Scalar flip_coordinate_;
flutter::DlPathBuilder builder_;
flutter::DlPoint flip(flutter::DlPoint p) {
return flutter::DlPoint(flip_coordinate_ * 2 - p.x, p.y);
}
// |flutter::DlPathReceiver|
void MoveTo(const Point& p2, bool will_be_closed) override {
builder_.MoveTo(flip(p2));
}
// |flutter::DlPathReceiver|
void LineTo(const Point& p2) override { //
builder_.LineTo(flip(p2));
}
// |flutter::DlPathReceiver|
void QuadTo(const Point& cp, const Point& p2) override {
builder_.QuadraticCurveTo(flip(cp), flip(p2));
}
// |flutter::DlPathReceiver|
bool ConicTo(const Point& cp, const Point& p2, Scalar weight) override {
builder_.ConicCurveTo(flip(cp), flip(p2), weight);
return true;
}
// |flutter::DlPathReceiver|
void CubicTo(const Point& cp1, const Point& cp2, const Point& p2) override {
builder_.CubicCurveTo(flip(cp1), flip(cp2), flip(p2));
}
// |flutter::DlPathReceiver|
void Close() override {}
};
flutter::DlPath CreateClockwiseMultiRadiiRoundRect() {
// Upper left corner: 10 x 15
// Upper right corner: 15 x 10
// Bottom right corner: 16 x 20
// Bottom left corner: 20 x 16
flutter::DlPathBuilder builder;
builder.MoveTo(flutter::DlPoint(110, 100));
builder.LineTo(flutter::DlPoint(285, 100));
builder.ConicCurveTo(flutter::DlPoint(300, 100), flutter::DlPoint(300, 110),
kSqrt2);
builder.LineTo(flutter::DlPoint(300, 280));
builder.ConicCurveTo(flutter::DlPoint(300, 300), flutter::DlPoint(284, 300),
kSqrt2);
builder.LineTo(flutter::DlPoint(120, 300));
builder.ConicCurveTo(flutter::DlPoint(100, 300), flutter::DlPoint(100, 284),
kSqrt2);
builder.LineTo(flutter::DlPoint(100, 115));
builder.ConicCurveTo(flutter::DlPoint(100, 100), flutter::DlPoint(110, 100),
kSqrt2);
builder.Close();
return builder.TakePath();
}
flutter::DlPath CreateCounterClockwiseMultiRadiiRoundRect() {
flutter::DlPath clockwise_path = CreateClockwiseMultiRadiiRoundRect();
return HorizontalPathFlipper(clockwise_path, 200.0f).TakePath();
}
flutter::DlPath CreatePolygon(bool clockwise) {
int vertex_count = 40;
Scalar direction = clockwise ? 1.0f : -1.0f;
auto make_point = [](Scalar angle) {
return flutter::DlPoint(200 + 100 * std::cos(angle),
200 + 100 * std::sin(angle));
};
flutter::DlPathBuilder builder;
builder.MoveTo(make_point(0.0f));
for (int i = 1; i < vertex_count; i++) {
Scalar angle = (static_cast<Scalar>(i) / vertex_count) * k2Pi;
builder.LineTo(make_point(angle * direction));
}
builder.Close();
return builder.TakePath();
}
flutter::DlPath CreateClockwisePolygon() {
return CreatePolygon(true);
}
flutter::DlPath CreateCounterClockwisePolygon() {
return CreatePolygon(false);
}
flutter::DlPath CreateRRect() {
return flutter::DlPathBuilder{}
.AddRoundRect(

View File

@ -109,6 +109,39 @@ class EllipsePathSource : public PathSource {
const Rect bounds_;
};
/// A utility class to receive path segments from a source, transform them
/// by a matrix, and pass them along to a subsequent receiver.
class PathTransformer : public impeller::PathReceiver {
public:
PathTransformer(PathReceiver& receiver [[clang::lifetimebound]],
const impeller::Matrix& matrix [[clang::lifetimebound]])
: receiver_(receiver), matrix_(matrix) {}
void MoveTo(const Point& p2, bool will_be_closed) override {
receiver_.MoveTo(matrix_ * p2, will_be_closed);
}
void LineTo(const Point& p2) override { receiver_.LineTo(matrix_ * p2); }
void QuadTo(const Point& cp, const Point& p2) override {
receiver_.QuadTo(matrix_ * cp, matrix_ * p2);
}
bool ConicTo(const Point& cp, const Point& p2, Scalar weight) override {
return receiver_.ConicTo(matrix_ * cp, matrix_ * p2, weight);
}
void CubicTo(const Point& cp1, const Point& cp2, const Point& p2) override {
receiver_.CubicTo(matrix_ * cp1, matrix_ * cp2, matrix_ * p2);
}
void Close() override { receiver_.Close(); }
private:
PathReceiver& receiver_;
const impeller::Matrix& matrix_;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_GEOMETRY_PATH_SOURCE_H_

View File

@ -16,7 +16,8 @@
namespace impeller {
namespace testing {
using DlPathReceiverMock = flutter::testing::DlPathReceiverMock;
using ::flutter::testing::DlPathReceiverMock;
using ::testing::Return;
TEST(PathSourceTest, RectSourceTest) {
Rect rect = Rect::MakeLTRB(10, 15, 20, 30);
@ -251,5 +252,71 @@ TEST(PathSourceTest, DashedLinePathSourceInvalidOnRegion) {
source.Dispatch(receiver);
}
TEST(PathSourceTest, PathTransformerRectSourceTest) {
Matrix matrix =
Matrix::MakeTranslateScale({2.0f, 3.0f, 1.0f}, {1.5f, 4.25f, 0.0f});
Rect rect = Rect::MakeLTRB(10, 15, 20, 30);
RectPathSource source(rect);
EXPECT_TRUE(source.IsConvex());
EXPECT_EQ(source.GetFillType(), FillType::kNonZero);
EXPECT_EQ(source.GetBounds(), Rect::MakeLTRB(10, 15, 20, 30));
::testing::StrictMock<DlPathReceiverMock> mock_receiver;
PathTransformer receiver = PathTransformer(mock_receiver, matrix);
{
::testing::Sequence sequence;
EXPECT_CALL(mock_receiver, MoveTo(Point(21.5f, 49.25f), true));
EXPECT_CALL(mock_receiver, LineTo(Point(41.5f, 49.25f)));
EXPECT_CALL(mock_receiver, LineTo(Point(41.5f, 94.25f)));
EXPECT_CALL(mock_receiver, LineTo(Point(21.5f, 94.25f)));
EXPECT_CALL(mock_receiver, LineTo(Point(21.5f, 49.25f)));
EXPECT_CALL(mock_receiver, Close());
}
source.Dispatch(receiver);
}
TEST(PathSourceTest, PathTransformerAllSegmentsTest) {
Matrix matrix =
Matrix::MakeTranslateScale({2.0f, 3.0f, 1.0f}, {1.5f, 4.25f, 0.0f});
::testing::StrictMock<DlPathReceiverMock> mock_receiver;
PathTransformer receiver = PathTransformer(mock_receiver, matrix);
{
::testing::Sequence sequence;
EXPECT_CALL(mock_receiver, MoveTo(Point(21.5f, 49.25f), false));
EXPECT_CALL(mock_receiver, LineTo(Point(41.5f, 49.25f)));
EXPECT_CALL(mock_receiver, MoveTo(Point(221.5f, 349.25f), true));
EXPECT_CALL(mock_receiver,
QuadTo(Point(241.5f, 349.25f), Point(241.5f, 394.25f)));
EXPECT_CALL(mock_receiver,
ConicTo(Point(237.5f, 409.25f), Point(231.5f, 409.25f), 5))
.WillOnce(Return(true));
EXPECT_CALL(mock_receiver,
ConicTo(Point(225.5f, 409.25f), Point(221.5f, 394.25f), 6))
.WillOnce(Return(false));
EXPECT_CALL(mock_receiver,
CubicTo(Point(211.5f, 379.25f), Point(211.5f, 364.25f),
Point(221.5f, 349.25f)));
EXPECT_CALL(mock_receiver, Close());
}
receiver.MoveTo(Point(10, 15), false);
receiver.LineTo(Point(20, 15));
receiver.MoveTo(Point(110, 115), true);
receiver.QuadTo(Point(120, 115), Point(120, 130));
EXPECT_TRUE(receiver.ConicTo(Point(118, 135), Point(115, 135), 5));
EXPECT_FALSE(receiver.ConicTo(Point(112, 135), Point(110, 130), 6));
receiver.CubicTo(Point(105, 125), Point(105, 120), Point(110, 115));
receiver.Close();
}
} // namespace testing
} // namespace impeller

View File

@ -12,6 +12,7 @@
#include <string>
#include <type_traits>
#include "fml/logging.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/size.h"
#include "impeller/geometry/type_traits.h"
@ -201,9 +202,85 @@ struct TPoint {
return sqrt(GetDistanceSquared(p));
}
constexpr Type GetLengthSquared() const { return GetDistanceSquared({}); }
constexpr Type GetLengthSquared() const {
return static_cast<double>(x) * x + static_cast<double>(y) * y;
}
constexpr Type GetLength() const { return GetDistance({}); }
constexpr Type GetLength() const { return std::sqrt(GetLengthSquared()); }
/// Returns the distance (squared) from this point to the closest point on
/// the line segment p0 -> p1.
///
/// If the projection of this point onto the line defined by the two points
/// is between them, the distance (squared) to that point is returned.
/// Otherwise, we return the distance (squared) to the endpoint that is
/// closer to the projected point.
Type GetDistanceToSegmentSquared(TPoint p0, TPoint p1) const {
// Compute relative vectors to one endpoint of the segment (p0)
TPoint u = p1 - p0;
TPoint v = *this - p0;
// Compute the projection of (this point) onto p0->p1.
Scalar dot = u.Dot(v);
if (dot <= 0) {
// The projection lands outside the segment on the p0 side.
// The result is the (square of the) distance to p0 (length of v).
return v.GetLengthSquared();
}
// The dot product is the product of the length of the two vectors
// ||u|| and ||v|| and the cosine of the angle between them. The length
// of the v vector times the cosine is the same as the length of
// the projection of the v vector onto the u vector (consider a right
// triangle [(0,0), v, v_projected], the length of v multipled by the
// cosine is the length of v_projected).
//
// Thus the dot product is also the product of the u vector and the
// projected shadow of the v vector onto the u vector.
//
// So, if the dot product is larger than the square of the length of
// the u vector, then the v vector was projected onto the line beyond
// the end of the u vector and so we can use the distance formula to
// that endpoint as our result.
Scalar uLengthSquared = u.GetLengthSquared();
if (dot >= uLengthSquared) {
// The projection lands outside the segment on the p1 side.
// The result is the (square of the) distance to p1.
return GetDistanceSquared(p1);
}
// We must now compute the distance from this point to its projection
// on to the segment.
//
// We compute the cross product of the two vectors u and v which
// gives us the area of the parallelogram [(0,0), u, u+v, v]. That
// parallelogram area is also the product of the length of one of its
// sides and the height perpendicular to that side. We have the length
// of one side which is the length of the segment itself (squared) as
// uLengthSquared, so if we divide the parallelogram area (squared)
// by uLengthSquared then we will get its height (squared) relative to u.
//
// That height is also the distance from this point to the line segment.
Scalar cross = u.Cross(v);
// The cross product may currently be signed, but we will square it later.
// To get our height (squared), we want to compute:
// result^2 == h^2 == (cross * cross / uLengthSquared)
//
// We reorder the equation slightly to avoid infinities:
return (cross / uLengthSquared) * cross;
}
/// Returns the distance from this point to the closest point on the line
/// segment p0 -> p1.
///
/// If the projection of this point onto the line defined by the two points
/// is between them, the distance to that point is returned. Otherwise,
/// we return the distance to the endpoint that is closer to the projected
/// point.
constexpr Type GetDistanceToSegment(TPoint p0, TPoint p1) const {
return std::sqrt(GetDistanceToSegmentSquared(p0, p1));
}
constexpr TPoint Normalize() const {
const auto length = GetLength();
@ -217,6 +294,17 @@ struct TPoint {
constexpr Type Cross(const TPoint& p) const { return (x * p.y) - (y * p.x); }
/// Return the cross product representing the sign (turning direction) and
/// magnitude (sin of the angle) of the angle from p1 to p2 as viewed from
/// p0.
///
/// Equivalent to ((p1 - p0).Cross(p2 - p0)).
static constexpr Type Cross(const TPoint& p0,
const TPoint& p1,
const TPoint& p2) {
return (p1 - p0).Cross(p2 - p0);
}
constexpr Type Dot(const TPoint& p) const { return (x * p.x) + (y * p.y); }
constexpr TPoint Reflect(const TPoint& axis) const {
@ -229,6 +317,16 @@ struct TPoint {
return {x * cos_a - y * sin_a, x * sin_a + y * cos_a};
}
/// Return the perpendicular vector turning to the right (Clockwise)
/// in the logical coordinate system where X increases to the right and Y
/// increases downward.
constexpr TPoint PerpendicularRight() const { return {-y, x}; }
/// Return the perpendicular vector turning to the left (Counterclockwise)
/// in the logical coordinate system where X increases to the right and Y
/// increases downward.
constexpr TPoint PerpendicularLeft() const { return {y, -x}; }
constexpr Radians AngleTo(const TPoint& p) const {
return Radians{std::atan2(this->Cross(p), this->Dot(p))};
}

View File

@ -0,0 +1,385 @@
// 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.
#include "flutter/impeller/geometry/point.h"
#include "flutter/impeller/geometry/geometry_asserts.h"
#include "gtest/gtest.h"
namespace impeller {
namespace testing {
TEST(PointTest, Length) {
for (int i = 0; i < 21; i++) {
EXPECT_EQ(Point(i, 0).GetLengthSquared(), i * i) << "i: " << i;
EXPECT_EQ(Point(0, i).GetLengthSquared(), i * i) << "i: " << i;
EXPECT_EQ(Point(-i, 0).GetLengthSquared(), i * i) << "i: " << i;
EXPECT_EQ(Point(0, -i).GetLengthSquared(), i * i) << "i: " << i;
EXPECT_EQ(Point(i, 0).GetLength(), i) << "i: " << i;
EXPECT_EQ(Point(0, i).GetLength(), i) << "i: " << i;
EXPECT_EQ(Point(-i, 0).GetLength(), i) << "i: " << i;
EXPECT_EQ(Point(0, -i).GetLength(), i) << "i: " << i;
EXPECT_EQ(Point(i, i).GetLengthSquared(), 2 * i * i) << "i: " << i;
EXPECT_EQ(Point(-i, i).GetLengthSquared(), 2 * i * i) << "i: " << i;
EXPECT_EQ(Point(i, -i).GetLengthSquared(), 2 * i * i) << "i: " << i;
EXPECT_EQ(Point(-i, -i).GetLengthSquared(), 2 * i * i) << "i: " << i;
EXPECT_FLOAT_EQ(Point(i, i).GetLength(), kSqrt2 * i) << "i: " << i;
EXPECT_FLOAT_EQ(Point(-i, i).GetLength(), kSqrt2 * i) << "i: " << i;
EXPECT_FLOAT_EQ(Point(i, -i).GetLength(), kSqrt2 * i) << "i: " << i;
EXPECT_FLOAT_EQ(Point(-i, -i).GetLength(), kSqrt2 * i) << "i: " << i;
}
}
TEST(PointTest, Distance) {
for (int j = 0; j < 21; j++) {
for (int i = 0; i < 21; i++) {
{
Scalar d = i - j;
EXPECT_EQ(Point(i, 0).GetDistanceSquared(Point(j, 0)), d * d)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(0, i).GetDistanceSquared(Point(0, j)), d * d)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(j, 0).GetDistanceSquared(Point(i, 0)), d * d)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(0, j).GetDistanceSquared(Point(0, i)), d * d)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(i, 0).GetDistance(Point(j, 0)), std::abs(d))
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(0, i).GetDistance(Point(0, j)), std::abs(d))
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(j, 0).GetDistance(Point(i, 0)), std::abs(d))
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(0, j).GetDistance(Point(0, i)), std::abs(d))
<< "i: " << i << ", j: " << j;
}
{
Scalar d_squared = i * i + j * j;
EXPECT_EQ(Point(i, 0).GetDistanceSquared(Point(0, j)), d_squared)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(-i, 0).GetDistanceSquared(Point(0, j)), d_squared)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(i, 0).GetDistanceSquared(Point(0, -j)), d_squared)
<< "i: " << i << ", j: " << j;
EXPECT_EQ(Point(-i, 0).GetDistanceSquared(Point(0, -j)), d_squared)
<< "i: " << i << ", j: " << j;
Scalar d = std::sqrt(d_squared);
EXPECT_FLOAT_EQ(Point(i, 0).GetDistance(Point(0, j)), d)
<< "i: " << i << ", j: " << j;
EXPECT_FLOAT_EQ(Point(-i, 0).GetDistance(Point(0, j)), d)
<< "i: " << i << ", j: " << j;
EXPECT_FLOAT_EQ(Point(i, 0).GetDistance(Point(0, -j)), d)
<< "i: " << i << ", j: " << j;
EXPECT_FLOAT_EQ(Point(-i, 0).GetDistance(Point(0, -j)), d)
<< "i: " << i << ", j: " << j;
}
}
}
}
TEST(PointTest, PerpendicularLeft) {
EXPECT_EQ(Point(1, 0).PerpendicularLeft(), Point(0, -1));
EXPECT_EQ(Point(0, 1).PerpendicularLeft(), Point(1, 0));
EXPECT_EQ(Point(-1, 0).PerpendicularLeft(), Point(0, 1));
EXPECT_EQ(Point(0, -1).PerpendicularLeft(), Point(-1, 0));
EXPECT_EQ(Point(1, 1).PerpendicularLeft(), Point(1, -1));
EXPECT_EQ(Point(-1, 1).PerpendicularLeft(), Point(1, 1));
EXPECT_EQ(Point(-1, -1).PerpendicularLeft(), Point(-1, 1));
EXPECT_EQ(Point(1, -1).PerpendicularLeft(), Point(-1, -1));
}
TEST(PointTest, PerpendicularRight) {
EXPECT_EQ(Point(1, 0).PerpendicularRight(), Point(0, 1));
EXPECT_EQ(Point(0, 1).PerpendicularRight(), Point(-1, 0));
EXPECT_EQ(Point(-1, 0).PerpendicularRight(), Point(0, -1));
EXPECT_EQ(Point(0, -1).PerpendicularRight(), Point(1, 0));
EXPECT_EQ(Point(1, 1).PerpendicularRight(), Point(-1, 1));
EXPECT_EQ(Point(-1, 1).PerpendicularRight(), Point(-1, -1));
EXPECT_EQ(Point(-1, -1).PerpendicularRight(), Point(1, -1));
EXPECT_EQ(Point(1, -1).PerpendicularRight(), Point(1, 1));
}
namespace {
typedef std::pair<Scalar, Scalar> PtSegmentDistanceFunc(Point);
void TestPointToSegmentGroup(Point segment0,
Point segment1,
Point p0,
Point delta,
int count,
PtSegmentDistanceFunc calc_distance) {
for (int i = 0; i < count; i++) {
auto [distance, squared] = calc_distance(p0);
EXPECT_FLOAT_EQ(p0.GetDistanceToSegmentSquared(segment0, segment1), squared)
<< p0 << " => [" << segment0 << ", " << segment1 << "]";
EXPECT_FLOAT_EQ(p0.GetDistanceToSegmentSquared(segment1, segment0), squared)
<< p0 << " => [" << segment0 << ", " << segment1 << "]";
EXPECT_FLOAT_EQ(p0.GetDistanceToSegment(segment0, segment1), distance)
<< p0 << " => [" << segment0 << ", " << segment1 << "]";
EXPECT_FLOAT_EQ(p0.GetDistanceToSegment(segment1, segment0), distance)
<< p0 << " => [" << segment0 << ", " << segment1 << "]";
p0 += delta;
}
}
} // namespace
TEST(PointTest, PointToSegment) {
// Horizontal segment and points to the left of it on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({0,10} through {10,10})
{0, 10}, {1, 0}, 11,
// Distance computation
[](Point p) {
Scalar d = 10 - p.x;
return std::make_pair(d, d * d);
});
// Horizontal segment and points on the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({11,10} through {19, 10})
{11, 10}, {1, 0}, 9,
// Distance computation
[](Point p) { //
return std::make_pair(0.0f, 0.0f);
});
// Horizontal segment and points to the right of it on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({20,10} through {30,10})
{20, 10}, {1, 0}, 11,
// Distance computation
[](Point p) {
Scalar d = p.x - 20;
return std::make_pair(d, d * d);
});
// Vertical segment and points above the top of it on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({10,0} through {10,10})
{10, 0}, {0, 1}, 11,
// Distance computation
[](Point p) {
Scalar d = 10 - p.y;
return std::make_pair(d, d * d);
});
// Vertical segment and points on the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({10,11} through {10, 19})
{10, 11}, {0, 1}, 9,
// Distance computation
[](Point p) { //
return std::make_pair(0.0f, 0.0f);
});
// Vertical segment and points below the bottom of it on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({10,20} through {10,30})
{10, 20}, {0, 1}, 11,
// Distance computation
[](Point p) {
Scalar d = p.y - 20;
return std::make_pair(d, d * d);
});
// Horizontal segment and points 5 pixels above and to the left of it
// on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({0,5} through {10,5})
{0, 5}, {1, 0}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (10 - p.x) * (10 - p.x) + 25;
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Horizontal segment and points 5 pixels directly above the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({11,5} through {19, 5})
{11, 5}, {1, 0}, 9,
// Distance computation
[](Point p) { //
return std::make_pair(5.0f, 25.0f);
});
// Horizontal segment and points 5 pixels above and to the right of it
// on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 10},
// Starting point, delta, count ({20,5} through {30,5})
{20, 5}, {1, 0}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (p.x - 20) * (p.x - 20) + 25;
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Vertical segment and points 5 pixels to the left and above the segment
// on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({5,0} through {5,10})
{5, 0}, {0, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = 25 + (10 - p.y) * (10 - p.y);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Vertical segment and points 5 pixels directly to the left of the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({5,11} through {5,19,})
{5, 11}, {0, 1}, 9,
// Distance computation
[](Point p) { //
return std::make_pair(5.0f, 25.0f);
});
// Vertical segment and points 5 pixels to the left and below the segment
// on the same line.
TestPointToSegmentGroup(
// Segment
{10, 10}, {10, 20},
// Starting point, delta, count ({20,5} through {30,5})
{5, 20}, {0, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = 25 + (p.y - 20) * (p.y - 20);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points up and to the right of the top of the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({5,-5} through {15,5})
{5, -5}, {1, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (p.x - 10) * (p.x - 10) + (p.y - 10) * (p.y - 10);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points up and to the right of the segment itself.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({15,5} through {24,14})
{15, 5}, {1, 1}, 9,
// Distance computation
[](Point p) {
Scalar d_sq = 50.0f;
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points up and to the right of the bottom of the
// segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({25,15} through {35,25})
{25, 15}, {1, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (p.x - 20) * (p.x - 20) + (p.y - 20) * (p.y - 20);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points down and to the left of the top of the segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({-5,5} through {5,15})
{-5, 5}, {1, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (p.x - 10) * (p.x - 10) + (p.y - 10) * (p.y - 10);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points down and to the left of the segment itself.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({5,15} through {14,24})
{5, 15}, {1, 1}, 9,
// Distance computation
[](Point p) {
Scalar d_sq = 50.0f;
return std::make_pair(std::sqrt(d_sq), d_sq);
});
// Diagonal segment and points down and to the left of the bottom of the
// segment.
TestPointToSegmentGroup(
// Segment
{10, 10}, {20, 20},
// Starting point, delta, count ({15,25} through {25,35})
{15, 25}, {1, 1}, 11,
// Distance computation
[](Point p) {
Scalar d_sq = (p.x - 20) * (p.x - 20) + (p.y - 20) * (p.y - 20);
return std::make_pair(std::sqrt(d_sq), d_sq);
});
}
TEST(PointTest, CrossProductThreePoints) {
// Colinear
EXPECT_FLOAT_EQ(Point::Cross(Point(-1, 0), Point(0, 0), Point(1, 0)), 0);
EXPECT_FLOAT_EQ(Point::Cross(Point(1, 0), Point(0, 0), Point(-1, 0)), 0);
// Right turn
EXPECT_FLOAT_EQ(Point::Cross(Point(-1, 0), Point(0, 0), Point(0, 1)), 1);
EXPECT_FLOAT_EQ(Point::Cross(Point(-2, 0), Point(0, 0), Point(0, 2)), 4);
// Left turn
EXPECT_FLOAT_EQ(Point::Cross(Point(-1, 0), Point(0, 0), Point(0, -1)), -1);
EXPECT_FLOAT_EQ(Point::Cross(Point(-2, 0), Point(0, 0), Point(0, -2)), -4);
// Convenient values for a less obvious left turn.
// p1 - p0 == (0, 0) - (3, -4) == (-3, 4)
// p2 - p0 == (1, 2) - (3, -4) == (-2, 6)
// product of the magnitude of the 2 legs and the sin of their angle
// (||(-3, 4)||) * (||(-2, 6)||) * sin(angle)
// 5 * sqrt(40) * sin(angle)
// angle = arcsin(4 / 5) - arcsin(6 / sqrt(40)) ~= -18.4349
// sin(angle) ~= -0.316227766
// 5 * sqrt(40) * sin(angle) == -10
// The math is cleaner with the cross product:
// (-3 * 6) - (-2 * 4) == -18 - -8 == -10
EXPECT_FLOAT_EQ(Point::Cross(Point(3, -4), Point(0, 0), Point(1, 2)), -10);
}
} // namespace testing
} // namespace impeller

View File

@ -4,6 +4,7 @@
#include "flutter/impeller/tessellator/path_tessellator.h"
#include "flutter/impeller/geometry/path_source.h"
#include "flutter/impeller/geometry/wangs_formula.h"
namespace {
@ -313,4 +314,14 @@ void PathTessellator::PathToFilledVertices(const PathSource& source,
pruner.PathEnd();
}
void PathTessellator::PathToTransformedFilledVertices(const PathSource& source,
VertexWriter& writer,
const Matrix& matrix) {
PathFillWriter path_writer(writer, matrix.GetMaxBasisLengthXY());
PathPruner pruner(path_writer, false);
PathTransformer transformer(pruner, matrix);
source.Dispatch(transformer);
pruner.PathEnd();
}
} // namespace impeller

View File

@ -197,6 +197,10 @@ class PathTessellator {
static void PathToFilledVertices(const PathSource& source,
VertexWriter& writer,
Scalar scale);
static void PathToTransformedFilledVertices(const PathSource& source,
VertexWriter& writer,
const Matrix& matrix);
};
} // namespace impeller

View File

@ -6661,6 +6661,281 @@
}
}
},
"flutter/impeller/entity/gles/shadow_vertices.frag.gles": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/shadow_vertices.frag.gles",
"has_side_effects": false,
"has_uniform_computation": false,
"modifies_coverage": false,
"reads_color_buffer": false,
"type": "Fragment",
"uses_late_zs_test": false,
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"arith_total",
"arith_fma"
],
"longest_path_cycles": [
0.21875,
0.21875,
0.0625,
0.0625,
0.0,
0.125,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"varying",
"texture"
],
"shortest_path_bound_pipelines": [
"arith_total",
"arith_fma"
],
"shortest_path_cycles": [
0.21875,
0.21875,
0.03125,
0.0625,
0.0,
0.125,
0.0
],
"total_bound_pipelines": [
"arith_total",
"arith_fma"
],
"total_cycles": [
0.21875,
0.21875,
0.0625,
0.0625,
0.0,
0.125,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 8,
"work_registers_used": 19
}
}
},
"Mali-T880": {
"core": "Mali-T880",
"filename": "flutter/impeller/entity/gles/shadow_vertices.frag.gles",
"has_uniform_computation": false,
"type": "Fragment",
"variants": {
"Main": {
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"arithmetic"
],
"longest_path_cycles": [
3.299999952316284,
1.0,
0.0
],
"pipelines": [
"arithmetic",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"arithmetic"
],
"shortest_path_cycles": [
3.299999952316284,
1.0,
0.0
],
"total_bound_pipelines": [
"arithmetic"
],
"total_cycles": [
3.6666667461395264,
1.0,
0.0
]
},
"thread_occupancy": 100,
"uniform_registers_used": 1,
"work_registers_used": 2
}
}
}
},
"flutter/impeller/entity/gles/shadow_vertices.vert.gles": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/gles/shadow_vertices.vert.gles",
"has_uniform_computation": false,
"type": "Vertex",
"variants": {
"Position": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.140625,
0.140625,
0.0,
0.0,
2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 20,
"work_registers_used": 32
},
"Varying": {
"fp16_arithmetic": null,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 8,
"work_registers_used": 6
}
}
},
"Mali-T880": {
"core": "Mali-T880",
"filename": "flutter/impeller/entity/gles/shadow_vertices.vert.gles",
"has_uniform_computation": false,
"type": "Vertex",
"variants": {
"Main": {
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
2.640000104904175,
5.0,
0.0
],
"pipelines": [
"arithmetic",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
2.640000104904175,
5.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
2.6666667461395264,
5.0,
0.0
]
},
"thread_occupancy": 100,
"uniform_registers_used": 5,
"work_registers_used": 2
}
}
}
},
"flutter/impeller/entity/gles/solid_fill.frag.gles": {
"Mali-G78": {
"core": "Mali-G78",
@ -10343,6 +10618,191 @@
}
}
},
"flutter/impeller/entity/shadow_vertices.frag.vkspv": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/shadow_vertices.frag.vkspv",
"has_side_effects": false,
"has_uniform_computation": true,
"modifies_coverage": false,
"reads_color_buffer": false,
"type": "Fragment",
"uses_late_zs_test": false,
"uses_late_zs_update": false,
"variants": {
"Main": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"arith_total",
"arith_fma"
],
"longest_path_cycles": [
0.21875,
0.21875,
0.03125,
0.0625,
0.0,
0.125,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"varying",
"texture"
],
"shortest_path_bound_pipelines": [
"arith_total",
"arith_fma"
],
"shortest_path_cycles": [
0.21875,
0.21875,
0.03125,
0.0625,
0.0,
0.125,
0.0
],
"total_bound_pipelines": [
"arith_total",
"arith_fma"
],
"total_cycles": [
0.21875,
0.21875,
0.03125,
0.0625,
0.0,
0.125,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 8,
"work_registers_used": 6
}
}
}
},
"flutter/impeller/entity/shadow_vertices.vert.vkspv": {
"Mali-G78": {
"core": "Mali-G78",
"filename": "flutter/impeller/entity/shadow_vertices.vert.vkspv",
"has_uniform_computation": true,
"type": "Vertex",
"variants": {
"Position": {
"fp16_arithmetic": 0,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.125,
0.125,
0.0,
0.0,
2.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 28,
"work_registers_used": 32
},
"Varying": {
"fp16_arithmetic": null,
"has_stack_spilling": false,
"performance": {
"longest_path_bound_pipelines": [
"load_store"
],
"longest_path_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
],
"pipelines": [
"arith_total",
"arith_fma",
"arith_cvt",
"arith_sfu",
"load_store",
"texture"
],
"shortest_path_bound_pipelines": [
"load_store"
],
"shortest_path_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
],
"total_bound_pipelines": [
"load_store"
],
"total_cycles": [
0.0,
0.0,
0.0,
0.0,
3.0,
0.0
]
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
"uniform_registers_used": 20,
"work_registers_used": 6
}
}
}
},
"flutter/impeller/entity/solid_fill.frag.vkspv": {
"Mali-G78": {
"core": "Mali-G78",