Started clamping scaled antialias lines size (#166149)

fixes https://github.com/flutter/flutter/issues/165994

Now vertices and e value calculations are driven by the clamping that is
happening in ComputeCorners.

## 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].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
gaaclarke
2025-03-28 15:24:40 -07:00
committed by GitHub
parent c068f67437
commit 713d906125
4 changed files with 128 additions and 35 deletions

View File

@@ -47,10 +47,11 @@ std::shared_ptr<Texture> CreateCurveTexture(
return CreateTexture(texture_descriptor, curve_data, context, "LineCurve");
}
GeometryResult CreateGeometry(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Geometry* geometry) {
std::pair<LineContents::EffectiveLineParameters, GeometryResult> CreateGeometry(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Geometry* geometry) {
using PerVertexData = LineVertexShader::PerVertexData;
const LineGeometry* line_geometry =
static_cast<const LineGeometry*>(geometry);
@@ -59,7 +60,8 @@ GeometryResult CreateGeometry(const ContentContext& renderer,
auto& host_buffer = renderer.GetTransientsBuffer();
size_t count = 4;
fml::Status calculate_status;
fml::StatusOr<LineContents::EffectiveLineParameters> calculate_status =
LineContents::EffectiveLineParameters{.width = 0, .radius = 0};
BufferView vertex_buffer = host_buffer.Emplace(
count * sizeof(PerVertexData), alignof(PerVertexData),
[line_geometry, &transform, &calculate_status](uint8_t* buffer) {
@@ -68,19 +70,24 @@ GeometryResult CreateGeometry(const ContentContext& renderer,
vertices, line_geometry, transform);
});
if (!calculate_status.ok()) {
return kEmptyResult;
return std::make_pair(
LineContents::EffectiveLineParameters{
.width = line_geometry->GetWidth(),
.radius = LineContents::kSampleRadius},
kEmptyResult);
}
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
{
.vertex_buffer = vertex_buffer,
.vertex_count = count,
.index_type = IndexType::kNone,
},
.transform = entity.GetShaderTransform(pass),
};
return std::make_pair(calculate_status.value(),
GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
{
.vertex_buffer = vertex_buffer,
.vertex_count = count,
.index_type = IndexType::kNone,
},
.transform = entity.GetShaderTransform(pass),
});
}
struct LineInfo {
@@ -111,7 +118,6 @@ LineInfo CalculateLineInfo(Point p0, Point p1, Scalar width, Scalar radius) {
1.0 + k * (p1.x * p1.x + p1.y * p1.y - p0.x * p1.x - p0.y * p1.y)),
};
}
} // namespace
const Scalar LineContents::kSampleRadius = 1.f;
@@ -136,6 +142,10 @@ bool LineContents::Render(const ContentContext& renderer,
frag_info.color = color_;
Scalar scale = entity.GetTransform().GetMaxBasisLengthXY();
auto geometry_result =
CreateGeometry(renderer, entity, pass, geometry_.get());
std::shared_ptr<Texture> curve_texture = CreateCurveTexture(
geometry_->GetWidth(), kSampleRadius, scale, renderer.GetContext());
@@ -161,7 +171,11 @@ bool LineContents::Render(const ContentContext& renderer,
return true;
},
/*force_stencil=*/false,
/*create_geom_callback=*/CreateGeometry);
/*create_geom_callback=*/
[geometry_result = std::move(geometry_result)](
const ContentContext& renderer, const Entity& entity,
RenderPass& pass,
const Geometry* geometry) { return geometry_result.second; });
}
std::optional<Rect> LineContents::GetCoverage(const Entity& entity) const {
@@ -185,20 +199,39 @@ std::vector<uint8_t> LineContents::CreateCurveData(Scalar width,
return curve_data;
}
fml::Status LineContents::CalculatePerVertex(
LineVertexShader::PerVertexData* per_vertex,
const LineGeometry* geometry,
const Matrix& entity_transform) {
Point corners[4];
namespace {
void ExpandLine(std::array<Point, 4>& corners, Point expansion) {
Point along = (corners[1] - corners[0]).Normalize();
Point across = (corners[2] - corners[0]).Normalize();
corners[0] += -1 * (across * expansion.x) + -1 * (along * expansion.y);
corners[1] += -1 * (across * expansion.x) + (along * expansion.y);
corners[2] += (across * expansion.x) + -1 * (along * expansion.y);
corners[3] += (across * expansion.x) + (along * expansion.y);
}
} // namespace
fml::StatusOr<LineContents::EffectiveLineParameters>
LineContents::CalculatePerVertex(LineVertexShader::PerVertexData* per_vertex,
const LineGeometry* geometry,
const Matrix& entity_transform) {
Scalar scale = entity_transform.GetMaxBasisLengthXY();
std::array<Point, 4> corners;
// Make sure we get kSampleRadius pixels to sample from.
Scalar expand_size = std::max(kSampleRadius / scale, kSampleRadius);
if (!LineGeometry::ComputeCorners(
corners, entity_transform,
corners.data(), entity_transform,
/*extend_endpoints=*/geometry->GetCap() != Cap::kButt,
geometry->GetP0(), geometry->GetP1(),
geometry->GetWidth() + kSampleRadius * 2.0)) {
geometry->GetP0(), geometry->GetP1(), geometry->GetWidth())) {
return fml::Status(fml::StatusCode::kAborted, "No valid corners");
}
LineInfo line_info = CalculateLineInfo(geometry->GetP0(), geometry->GetP1(),
geometry->GetWidth(), kSampleRadius);
Scalar effective_line_width = std::fabsf((corners[2] - corners[0]).y);
ExpandLine(corners, Point(expand_size, expand_size));
Scalar padded_line_width = std::fabsf((corners[2] - corners[0]).y);
Scalar effective_sample_radius =
(padded_line_width - effective_line_width) / 2.f;
LineInfo line_info =
CalculateLineInfo(geometry->GetP0(), geometry->GetP1(),
effective_line_width, effective_sample_radius);
for (auto& corner : corners) {
*per_vertex++ = {
.position = corner,
@@ -209,6 +242,7 @@ fml::Status LineContents::CalculatePerVertex(
};
}
return {};
return EffectiveLineParameters{.width = effective_line_width,
.radius = effective_sample_radius};
}
} // namespace impeller

View File

@@ -19,7 +19,15 @@ class LineContents : public Contents {
Scalar radius,
Scalar scale);
static fml::Status CalculatePerVertex(
struct EffectiveLineParameters {
Scalar width;
Scalar radius;
};
/// Calculates the values needed for the vertex shader, per vertex.
/// Returns the effective line parameters that are used. These differ from the
/// ones provided by `geometry` when the line gets clamped for being too thin.
static fml::StatusOr<EffectiveLineParameters> CalculatePerVertex(
LineVertexShader::PerVertexData* per_vertex,
const LineGeometry* geometry,
const Matrix& entity_transform);

View File

@@ -64,15 +64,21 @@ TEST(LineContents, CalculatePerVertex) {
/*cap=*/Cap::kButt);
Matrix transform;
fml::Status status =
fml::StatusOr<LineContents::EffectiveLineParameters> status =
LineContents::CalculatePerVertex(per_vertex, geometry.get(), transform);
Scalar offset =
(LineContents::kSampleRadius * 2.0 + geometry->GetWidth()) / 2.f;
ASSERT_TRUE(status.ok());
EXPECT_POINT_NEAR(per_vertex[0].position, Point(100, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[1].position, Point(200, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[2].position, Point(100, 100 - offset));
EXPECT_POINT_NEAR(per_vertex[3].position, Point(200, 100 - offset));
EXPECT_EQ(status.value().width, 5.f);
EXPECT_EQ(status.value().radius, LineContents::kSampleRadius);
EXPECT_POINT_NEAR(per_vertex[0].position,
Point(100 - LineContents::kSampleRadius, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[1].position,
Point(200 + LineContents::kSampleRadius, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[2].position,
Point(100 - LineContents::kSampleRadius, 100 - offset));
EXPECT_POINT_NEAR(per_vertex[3].position,
Point(200 + LineContents::kSampleRadius, 100 - offset));
for (int i = 1; i < 4; ++i) {
EXPECT_VECTOR3_NEAR(per_vertex[0].e0, per_vertex[i].e0) << i;
@@ -112,5 +118,45 @@ TEST(LineContents, CreateCurveDataScaled) {
EXPECT_NEAR(data[3] / 255.f, 1.f, kEhCloseEnough);
}
// This scales the line to be less than 1 pixel.
TEST(LineContents, CalculatePerVertexLimit) {
LineVertexShader::PerVertexData per_vertex[4];
Scalar scale = 0.05;
auto geometry = std::make_unique<LineGeometry>(
/*p0=*/Point{100, 100}, //
/*p1=*/Point{200, 100}, //
/*width=*/10.f, //
/*cap=*/Cap::kButt);
Matrix transform = Matrix::MakeTranslation({100, 100, 1.0}) *
Matrix::MakeScale({scale, scale, 1.0}) *
Matrix::MakeTranslation({-100, -100, 1.0});
fml::StatusOr<LineContents::EffectiveLineParameters> status =
LineContents::CalculatePerVertex(per_vertex, geometry.get(), transform);
Scalar one_radius_size = std::max(LineContents::kSampleRadius / scale,
LineContents::kSampleRadius);
Scalar one_px_size = 1.f / scale;
Scalar offset = one_px_size / 2.f + one_radius_size;
ASSERT_TRUE(status.ok());
EXPECT_NEAR(status.value().width, 20.f, kEhCloseEnough);
EXPECT_NEAR(status.value().radius, one_px_size * LineContents::kSampleRadius,
kEhCloseEnough);
EXPECT_POINT_NEAR(per_vertex[0].position,
Point(100 - one_radius_size, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[1].position,
Point(200 + one_radius_size, 100 + offset));
EXPECT_POINT_NEAR(per_vertex[2].position,
Point(100 - one_radius_size, 100 - offset));
EXPECT_POINT_NEAR(per_vertex[3].position,
Point(200 + one_radius_size, 100 - offset));
EXPECT_NEAR(CalculateLine(per_vertex[0], Point(150, 100)), 1.f,
kEhCloseEnough);
// EXPECT_NEAR(CalculateLine(per_vertex[0], Point(150, 100 +
// one_px_size)), 1.f,
// kEhCloseEnough);
}
} // namespace testing
} // namespace impeller

View File

@@ -42,5 +42,10 @@ float CalculateLine() {
void main() {
float line = CalculateLine();
frag_color = vec4(frag_info.color.xyz, line);
//////////////////////////////////////////////////////////////////////////////
// This is a nice way to visually debug this shader:
// frag_color =
// vec4(mix(vec3(1, 0,0), frag_info.color.xyz, line), 1.0);
//////////////////////////////////////////////////////////////////////////////
frag_color = IPPremultiply(frag_color);
}