diff --git a/engine/src/flutter/impeller/entity/contents/line_contents.cc b/engine/src/flutter/impeller/entity/contents/line_contents.cc index bf4be1d35f..4995c1aead 100644 --- a/engine/src/flutter/impeller/entity/contents/line_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/line_contents.cc @@ -47,10 +47,11 @@ std::shared_ptr CreateCurveTexture( return CreateTexture(texture_descriptor, curve_data, context, "LineCurve"); } -GeometryResult CreateGeometry(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Geometry* geometry) { +std::pair CreateGeometry( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Geometry* geometry) { using PerVertexData = LineVertexShader::PerVertexData; const LineGeometry* line_geometry = static_cast(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 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 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 LineContents::GetCoverage(const Entity& entity) const { @@ -185,20 +199,39 @@ std::vector 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& 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::CalculatePerVertex(LineVertexShader::PerVertexData* per_vertex, + const LineGeometry* geometry, + const Matrix& entity_transform) { + Scalar scale = entity_transform.GetMaxBasisLengthXY(); + std::array 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 diff --git a/engine/src/flutter/impeller/entity/contents/line_contents.h b/engine/src/flutter/impeller/entity/contents/line_contents.h index 61d7cdb260..6f5fa02df4 100644 --- a/engine/src/flutter/impeller/entity/contents/line_contents.h +++ b/engine/src/flutter/impeller/entity/contents/line_contents.h @@ -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 CalculatePerVertex( LineVertexShader::PerVertexData* per_vertex, const LineGeometry* geometry, const Matrix& entity_transform); diff --git a/engine/src/flutter/impeller/entity/contents/line_contents_unittests.cc b/engine/src/flutter/impeller/entity/contents/line_contents_unittests.cc index cca5cc82af..da2797df85 100644 --- a/engine/src/flutter/impeller/entity/contents/line_contents_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/line_contents_unittests.cc @@ -64,15 +64,21 @@ TEST(LineContents, CalculatePerVertex) { /*cap=*/Cap::kButt); Matrix transform; - fml::Status status = + fml::StatusOr 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( + /*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 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 diff --git a/engine/src/flutter/impeller/entity/shaders/line.frag b/engine/src/flutter/impeller/entity/shaders/line.frag index 23baa9506d..a7da54bf6a 100644 --- a/engine/src/flutter/impeller/entity/shaders/line.frag +++ b/engine/src/flutter/impeller/entity/shaders/line.frag @@ -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); }