From 72d57eb840254a30ab01a17c6a49f3fc3be93b2e Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 15 Nov 2023 18:51:16 -0800 Subject: [PATCH 1/9] [Impeller] Add direct tesselation of circles for DrawCircle and Round end caps --- ci/licenses_golden/excluded_files | 1 + ci/licenses_golden/licenses_flutter | 8 + impeller/aiks/aiks_unittests.cc | 26 +++ impeller/aiks/canvas.cc | 40 ++--- impeller/entity/BUILD.gn | 5 + .../entity/geometry/circle_tessellator.cc | 130 +++++++++++++++ impeller/entity/geometry/circle_tessellator.h | 138 ++++++++++++++++ .../geometry/circle_tessellator_unittests.cc | 151 ++++++++++++++++++ impeller/entity/geometry/ellipse_geometry.cc | 103 ++++++++++++ impeller/entity/geometry/ellipse_geometry.h | 68 ++++++++ impeller/entity/geometry/geometry.cc | 5 + impeller/entity/geometry/geometry.h | 2 + impeller/entity/geometry/line_geometry.cc | 143 +++++++++++++---- impeller/entity/geometry/line_geometry.h | 11 ++ .../entity/geometry/point_field_geometry.cc | 63 ++++---- 15 files changed, 807 insertions(+), 87 deletions(-) create mode 100644 impeller/entity/geometry/circle_tessellator.cc create mode 100644 impeller/entity/geometry/circle_tessellator.h create mode 100644 impeller/entity/geometry/circle_tessellator_unittests.cc create mode 100644 impeller/entity/geometry/ellipse_geometry.cc create mode 100644 impeller/entity/geometry/ellipse_geometry.h diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 4a11ee1be417e..7e84b168a753c 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -147,6 +147,7 @@ ../../../flutter/impeller/entity/contents/vertices_contents_unittests.cc ../../../flutter/impeller/entity/entity_pass_target_unittests.cc ../../../flutter/impeller/entity/entity_unittests.cc +../../../flutter/impeller/entity/geometry/circle_tessellator_unittests.cc ../../../flutter/impeller/entity/geometry/geometry_unittests.cc ../../../flutter/impeller/entity/render_target_cache_unittests.cc ../../../flutter/impeller/fixtures diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 22d3e68741eb6..ea9c781a691ee 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5099,8 +5099,12 @@ ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutte ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_playground.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_playground.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/geometry.cc + ../../../flutter/LICENSE @@ -7891,8 +7895,12 @@ FILE: ../../../flutter/impeller/entity/entity_pass_target.cc FILE: ../../../flutter/impeller/entity/entity_pass_target.h FILE: ../../../flutter/impeller/entity/entity_playground.cc FILE: ../../../flutter/impeller/entity/entity_playground.h +FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc +FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.h FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.h +FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc +FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.h FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h FILE: ../../../flutter/impeller/entity/geometry/geometry.cc diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 81c5ed7aaf48b..da402f0d30cce 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2060,6 +2060,32 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, FillCirclesRenderCorrectly) { + Canvas canvas; + canvas.Scale(GetContentScale()); + Paint paint; + const int color_count = 3; + Color colors[color_count] = { + Color::Blue(), + Color::Green(), + Color::Crimson(), + }; + + int c_index = 0; + int radius = 600; + while (radius > 0) { + paint.color = colors[(c_index++) % color_count]; + canvas.DrawCircle({10, 10}, radius, paint); + if (radius > 30) { + radius -= 10; + } else { + radius -= 2; + } + } + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, GradientStrokesRenderCorrectly) { // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 auto callback = [&](AiksContext& renderer) -> std::optional { diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 906287c61c8f5..02b215e41c274 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -232,17 +232,6 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, } void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) { - if (paint.stroke_cap == Cap::kRound) { - auto path = PathBuilder{} - .AddLine((p0), (p1)) - .SetConvexity(Convexity::kConvex) - .TakePath(); - Paint stroke_paint = paint; - stroke_paint.style = Paint::Style::kStroke; - DrawPath(path, stroke_paint); - return; - } - Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetClipDepth(GetClipDepth()); @@ -298,20 +287,33 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) { } void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { + if (paint.style == Paint::Style::kStroke) { + auto circle_path = + PathBuilder{} + .AddCircle(center, radius) + .SetConvexity(Convexity::kConvex) + .SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius, + center.x + radius, center.y + radius)) + .TakePath(); + DrawPath(circle_path, paint); + return; + } + Size half_size(radius, radius); if (AttemptDrawBlurredRRect( Rect::MakeOriginSize(center - half_size, half_size * 2), radius, paint)) { return; } - auto circle_path = - PathBuilder{} - .AddCircle(center, radius) - .SetConvexity(Convexity::kConvex) - .SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius, - center.x + radius, center.y + radius)) - .TakePath(); - DrawPath(circle_path, paint); + + Entity entity; + entity.SetTransform(GetCurrentTransform()); + entity.SetClipDepth(GetClipDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters( + paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius)))); + + GetCurrentPass().AddEntity(entity); } void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) { diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index d5841d47fd120..9934b1d9938dd 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -191,8 +191,12 @@ impeller_component("entity") { "entity_pass_delegate.h", "entity_pass_target.cc", "entity_pass_target.h", + "geometry/circle_tessellator.cc", + "geometry/circle_tessellator.h", "geometry/cover_geometry.cc", "geometry/cover_geometry.h", + "geometry/ellipse_geometry.cc", + "geometry/ellipse_geometry.h", "geometry/fill_path_geometry.cc", "geometry/fill_path_geometry.h", "geometry/geometry.cc", @@ -266,6 +270,7 @@ impeller_component("entity_unittests") { "entity_playground.cc", "entity_playground.h", "entity_unittests.cc", + "geometry/circle_tessellator_unittests.cc", "geometry/geometry_unittests.cc", "render_target_cache_unittests.cc", ] diff --git a/impeller/entity/geometry/circle_tessellator.cc b/impeller/entity/geometry/circle_tessellator.cc new file mode 100644 index 0000000000000..a62b5ea8c7288 --- /dev/null +++ b/impeller/entity/geometry/circle_tessellator.cc @@ -0,0 +1,130 @@ +// 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/entity/geometry/circle_tessellator.h" + +#include "flutter/fml/logging.h" + +namespace impeller { + +std::vector CircleTessellator::trigs_[MAX_DIVISIONS_ + 1]; + +size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { + // Note: these values are approximated based on the values returned from + // the decomposition of 4 cubics performed by Path::CreatePolyline. + if (pixel_radius < 1.0) { + return 1; + } + if (pixel_radius < 2.0) { + return 2; + } + if (pixel_radius < 12.0) { + return 6; + } + if (pixel_radius <= 36.0) { + return 9; + } + pixel_radius /= 4; + if (pixel_radius > (MAX_DIVISIONS_ - 1)) { + return MAX_DIVISIONS_; + } + return static_cast(ceil(pixel_radius)); +} + +const std::vector& CircleTessellator::GetTrigForDivisions( + size_t divisions) { + FML_DCHECK(divisions > 0 && divisions <= MAX_DIVISIONS_); + std::vector& trigs = trigs_[divisions]; + + if (trigs.empty()) { + double angle_scale = kPiOver2 / divisions; + + trigs.emplace_back(1.0, 0.0); + for (size_t i = 1; i < divisions; i++) { + trigs.emplace_back(Radians(i * angle_scale)); + } + trigs.emplace_back(0.0, 1.0); + + FML_DCHECK(trigs.size() == divisions + 1); + } + + return trigs; +} + +void CircleTessellator::ExtendRelativeQuadrantToAbsoluteCircle( + std::vector& points, + const Point& center) { + auto quadrant_points = points.size(); + + // The 1st quadrant points are reversed in order, reflected around + // the Y axis, and translated to become absolute 2nd quadrant points. + for (size_t i = 1; i <= quadrant_points; i++) { + auto point = points[quadrant_points - i]; + points.emplace_back(center.x + point.x, center.y - point.y); + } + + // The 1st quadrant points are reflected around the X & Y axes + // and translated to become absolute 3rd quadrant points. + for (size_t i = 0; i < quadrant_points; i++) { + auto point = points[i]; + points.emplace_back(center.x - point.x, center.y - point.y); + } + + // The 1st quadrant points are reversed in order, reflected around + // the X axis and translated to become absolute 4th quadrant points. + // The 1st quadrant points are also translated to the center point as + // well since this is the last time we will use them. + for (size_t i = 1; i <= quadrant_points; i++) { + auto point = points[quadrant_points - i]; + points.emplace_back(center.x - point.x, center.y + point.y); + + // This is the last loop where we need the first quadrant to be + // relative so we convert them to absolute as we go. + points[quadrant_points - i] = center + point; + } +} + +void CircleTessellator::FillQuadrantTriangles(std::vector& points, + const Point& center, + const Point& start_vector, + const Point& end_vector) const { + // We only deal with circles for now + FML_DCHECK(start_vector.GetLength() - end_vector.GetLength() < + kEhCloseEnough); + // And only for perpendicular vectors + FML_DCHECK(start_vector.Dot(end_vector) < kEhCloseEnough); + + auto trigs = GetTrigForDivisions(quadrant_divisions_); + + auto prev = center + (trigs[0].cos * start_vector + // + trigs[0].sin * end_vector); + for (size_t i = 1; i < trigs.size(); i++) { + points.emplace_back(center); + points.emplace_back(prev); + prev = center + (trigs[i].cos * start_vector + // + trigs[i].sin * end_vector); + points.emplace_back(prev); + } +} + +std::vector CircleTessellator::GetCircleTriangles(const Point& center, + Scalar radius) const { + std::vector points = std::vector(); + const size_t quadrant_points = quadrant_divisions_ * 3; + points.reserve(quadrant_points * 4); + + // Start with the quadrant top-center to right-center using coordinates + // relative to the (0, 0). The coordinates will be made absolute relative + // to the center during the extend method below. + FillQuadrantTriangles(points, {}, {0, -radius}, {radius, 0}); + FML_DCHECK(points.size() == quadrant_points); + + ExtendRelativeQuadrantToAbsoluteCircle(points, center); + + FML_DCHECK(points.size() == quadrant_points * 4); + + return points; +} + +} // namespace impeller diff --git a/impeller/entity/geometry/circle_tessellator.h b/impeller/entity/geometry/circle_tessellator.h new file mode 100644 index 0000000000000..9853064f27acc --- /dev/null +++ b/impeller/entity/geometry/circle_tessellator.h @@ -0,0 +1,138 @@ +// 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. + +#pragma once + +#include + +#include "flutter/impeller/geometry/matrix.h" +#include "flutter/impeller/geometry/point.h" +#include "flutter/impeller/geometry/scalar.h" + +namespace impeller { + +/// @brief A structure to store the sine and cosine of an angle. +struct Trig { + /// Construct a Trig object from a given angle in radians. + Trig(Radians r) : cos(std::cos(r.radians)), sin(std::sin(r.radians)) {} + + /// Construct a Trig object from the given cosine and sine values. + Trig(double cos, double sin) : cos(cos), sin(sin) {} + + double cos; + double sin; +}; + +/// @brief A utility class to compute the number of divisions for a circle +/// given a transform-adjusted pixel radius and methods for generating +/// a tessellated set of triangles for a quarter or full circle. +/// +/// A helper constructor is provided which can compute the device +/// pixel radius size for a geometry-space radius when viewed under +/// a specified geometry-to-device transform. +/// +/// The object should be constructed with the expected radius of the +/// circle in pixels, but can then be used to generate a triangular +/// tessellation with the indicated number of divisions for any +/// radius after that. Since the coordinate space in which the +/// circle being tessellated is not necessarily device pixel space, +/// the radius supplied during tessellation might not match the +/// pixel radius supplied during construction, but the two values +/// should be related by the transform in place when the tessellated +/// triangles are rendered for maximum tessellation fidelity. +class CircleTessellator { + public: + /// @brief Constructs a CircleDivider that produces enough segments to + /// reasonably approximate a circle with a specified radius + /// in pixels. + constexpr explicit CircleTessellator(Scalar pixel_radius) + : quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {} + + /// @brief Constructs a CircleDivider that produces enough segments to + /// reasonably approximate a circle with a specified |radius| + /// when viewed under the specified |transform|. + constexpr CircleTessellator(const Matrix& transform, Scalar radius) + : CircleTessellator(transform.GetMaxBasisLength() * radius) {} + + ~CircleTessellator() = default; + + /// @brief Return the number of divisions computed by the algorithm for + /// a single quarter circle. + size_t GetQuadrantDivisionCount() const { return quadrant_divisions_; } + + /// @brief Return the number of vertices that will be generated to + /// tessellate a single quarter circle. + size_t GetQuadrantVertexCount() const { return quadrant_divisions_ * 3; } + + /// @brief Return the number of divisions computed by the algorithm for + /// a full circle. + size_t GetCircleDivisionCount() const { return quadrant_divisions_ * 4; } + + /// @brief Return the number of vertices that will be generated to + /// tessellate a full circle. + size_t GetCircleVertexCount() const { return quadrant_divisions_ * 12; } + + /// @brief Compute the points of a triangular tesselation of the full + /// circle of the given radius and center. + /// + /// @return the list of points on the polygonal approximation. + std::vector GetCircleTriangles(const Point& center, + Scalar radius) const; + + /// @brief Adds entries in an existing vector of |vertices| to represent + /// the triangular tessellation of the quarter circle that sweeps + /// from the |start_vector| to the |end_vector| around an origin + /// specified by |center|. The length of the start and end vectors + /// controls the size of the quarter circle. + /// + /// The axes must be of the same length and perpendicular. The new + /// points will be appended to the end of the vector. + void FillQuadrantTriangles(std::vector& vertices, + const Point& center, + const Point& start_vector, + const Point& end_vector) const; + + private: + const size_t quadrant_divisions_; + + CircleTessellator(const CircleTessellator&) = delete; + + CircleTessellator& operator=(const CircleTessellator&) = delete; + + /// @brief Compute the number of vertices to divide each quadrant of + /// the circle into based on the expected pixel space radius. + /// + /// @return the number of vertices. + static size_t ComputeQuadrantDivisions(Scalar pixel_radius); + + /// @brief Compute the sine and cosine for each angle in the number of + /// divisions [0, divisions] of a quarter circle and return the + /// values in a vector of trig objects. + /// + /// Note that since the 0th division is included, the vector will + /// contain (divisions + 1) values. + /// + /// @return The vector of (divisions + 1) trig values. + static const std::vector& GetTrigForDivisions(size_t divisions); + + /// @brief Extend a list of |points| in the vector containing relative + /// coordinates for the first quadrant (top-center to right-center) + /// into the remaining 3 quadrants and adjust them to be relative + /// to the supplied |center|. + /// + /// The incoming coordinates are assumed to be relative to a + /// center point of (0, 0) and the method will duplicate and + /// reflect them around that origin to fill in the remaining + /// 3 quadrants. As the method works, it will also adjust every + /// point (including the pre-existing 1st quadrant points) to + /// be relative to the new center. + static void ExtendRelativeQuadrantToAbsoluteCircle(std::vector& points, + const Point& center = {}); + + static constexpr int MAX_DIVISIONS_ = 35; + + static std::vector trigs_[MAX_DIVISIONS_ + 1]; +}; + +} // namespace impeller diff --git a/impeller/entity/geometry/circle_tessellator_unittests.cc b/impeller/entity/geometry/circle_tessellator_unittests.cc new file mode 100644 index 0000000000000..1124d9aad64bd --- /dev/null +++ b/impeller/entity/geometry/circle_tessellator_unittests.cc @@ -0,0 +1,151 @@ +// 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 "fml/logging.h" +#include "gtest/gtest.h" + +#include "flutter/impeller/entity/geometry/circle_tessellator.h" +#include "flutter/impeller/geometry/geometry_asserts.h" + +namespace impeller { +namespace testing { + +TEST(CircleTessellator, TrigAngles) { + { + Trig trig(Degrees(0.0)); + EXPECT_EQ(trig.cos, 1.0); + EXPECT_EQ(trig.sin, 0.0); + } + + { + Trig trig(Radians(0.0)); + EXPECT_EQ(trig.cos, 1.0); + EXPECT_EQ(trig.sin, 0.0); + } + + { + Trig trig(Degrees(30.0)); + EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); + EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 6.0)); + EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); + EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); + } + + { + Trig trig(Degrees(60.0)); + EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); + EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 3.0)); + EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); + EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); + } + + { + Trig trig(Degrees(90.0)); + EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); + EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 2.0)); + EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); + EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); + } +} + +TEST(CircleTessellator, DivisionVertexCounts) { + auto test = [](Scalar pixel_radius, size_t quadrant_divisions) { + CircleTessellator tessellator(pixel_radius); + + EXPECT_EQ(tessellator.GetQuadrantDivisionCount(), quadrant_divisions) + << "pixel radius = " << pixel_radius; + EXPECT_EQ(tessellator.GetCircleDivisionCount(), quadrant_divisions * 4) + << "pixel radius = " << pixel_radius; + + EXPECT_EQ(tessellator.GetQuadrantVertexCount(), quadrant_divisions * 3) + << "pixel radius = " << pixel_radius; + EXPECT_EQ(tessellator.GetCircleVertexCount(), quadrant_divisions * 12) + << "pixel radius = " << pixel_radius; + }; + + test(0.0, 1u); + test(0.9, 1u); + test(1.0, 2u); + test(1.9, 2u); + test(2.0, 6u); + test(11.9, 6u); + test(12.0, 9u); + test(35.9, 9u); + for (int i = 36; i < 140; i += 4) { + test(i, i / 4); + test(i + .1, i / 4 + 1); + } + for (int i = 140; i <= 1000; i++) { + test(i, 35u); + } +} + +TEST(CircleTessellator, TessellationVertices) { + auto test = [](Scalar pixel_radius, Point center, Scalar radius) { + CircleTessellator tessellator(pixel_radius); + + auto divisions = tessellator.GetCircleDivisionCount(); + auto points = tessellator.GetCircleTriangles(center, radius); + ASSERT_EQ(points.size(), divisions * 3); + ASSERT_EQ(divisions % 4, 0u); + + Point expect_center = center; + Point expect_prev = center - Point(0, radius); + for (size_t i = 0; i < divisions; i++) { + double angle = (k2Pi * (i + 1)) / divisions - kPiOver2; + Point expect_next = + center + Point(cos(angle) * radius, sin(angle) * radius); + auto quadrant = i / (divisions / 4); + Point test_center, test_prev, test_next; + switch (quadrant) { + case 0: + case 2: + // Quadrants 0 and 2 are ordered (center, prev, next); + test_center = points[i * 3 + 0]; + test_prev = points[i * 3 + 1]; + test_next = points[i * 3 + 2]; + break; + + case 1: + case 3: + // Quadrants 1 and 3 are ordered (prev, next, center); + test_center = points[i * 3 + 2]; + test_prev = points[i * 3 + 0]; + test_next = points[i * 3 + 1]; + break; + } + EXPECT_EQ(test_center, expect_center) // + << "point " << i << " out of " << divisions // + << ", center = " << center << ", radius = " << radius; + EXPECT_POINT_NEAR(test_prev, expect_prev) // + << "point " << i << " out of " << divisions // + << ", center = " << center << ", radius = " << radius; + EXPECT_POINT_NEAR(test_next, expect_next) // + << "point " << i << " out of " << divisions // + << ", center = " << center << ", radius = " << radius; + expect_prev = expect_next; + } + EXPECT_POINT_NEAR(expect_prev, center - Point(0, radius)); + }; + + test(2.0, {}, 2.0); + test(2.0, {10, 10}, 2.0); + test(1000.0, {}, 2.0); + test(2.0, {}, 1000.0); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc new file mode 100644 index 0000000000000..72e3a3dba451c --- /dev/null +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -0,0 +1,103 @@ +// 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/entity/geometry/ellipse_geometry.h" + +#include "flutter/impeller/entity/geometry/circle_tessellator.h" + +namespace impeller { + +EllipseGeometry::EllipseGeometry(Point center, Scalar radius) + : center_(center), radius_(radius) { + FML_DCHECK(radius >= 0); +} + +GeometryResult EllipseGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto& host_buffer = pass.GetTransientsBuffer(); + + CircleTessellator divider(entity.GetTransform(), radius_); + auto points = divider.GetCircleTriangles(center_, radius_); + + return GeometryResult{ + .type = PrimitiveType::kTriangle, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace( + points.data(), points.size() * sizeof(Point), alignof(float)), + .vertex_count = points.size(), + .index_type = IndexType::kNone, + }, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransform(), + .prevent_overdraw = false, + }; +} + +// |Geometry| +GeometryResult EllipseGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto& host_buffer = pass.GetTransientsBuffer(); + + CircleTessellator divider(entity.GetTransform(), radius_); + auto points = divider.GetCircleTriangles(center_, radius_); + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + + std::vector data(points.size() * 2); + for (auto i = 0u, j = 0u; j < points.size(); i += 2, j++) { + data[i] = points[j]; + data[i + 1] = uv_transform * points[j]; + } + + return GeometryResult{ + .type = PrimitiveType::kTriangle, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace( + data.data(), data.size() * sizeof(Point), alignof(float)), + .vertex_count = points.size(), + .index_type = IndexType::kNone, + }, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransform(), + .prevent_overdraw = false, + }; +} + +GeometryVertexType EllipseGeometry::GetVertexType() const { + return GeometryVertexType::kPosition; +} + +std::optional EllipseGeometry::GetCoverage( + const Matrix& transform) const { + Point corners[4]{ + {center_.x, center_.y - radius_}, + {center_.x + radius_, center_.y}, + {center_.x, center_.y + radius_}, + {center_.x - radius_, center_.y}, + }; + + for (int i = 0; i < 4; i++) { + corners[i] = transform * corners[i]; + } + return Rect::MakePointBounds(std::begin(corners), std::end(corners)); +} + +bool EllipseGeometry::CoversArea(const Matrix& transform, + const Rect& rect) const { + return false; +} + +bool EllipseGeometry::IsAxisAlignedRect() const { + return false; +} + +} // namespace impeller diff --git a/impeller/entity/geometry/ellipse_geometry.h b/impeller/entity/geometry/ellipse_geometry.h new file mode 100644 index 0000000000000..ecc56331b70ae --- /dev/null +++ b/impeller/entity/geometry/ellipse_geometry.h @@ -0,0 +1,68 @@ +// 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. + +#pragma once + +#include "impeller/entity/geometry/geometry.h" + +namespace impeller { + +class EllipseGeometry final : public Geometry { + public: + explicit EllipseGeometry(Point center, Scalar radius); + + ~EllipseGeometry() = default; + + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + + // |Geometry| + bool IsAxisAlignedRect() const override; + + private: + // Computes the 4 corners of a rectangle that defines the line and + // possibly extended endpoints which will be rendered under the given + // transform, and returns true if such a rectangle is defined. + // + // The coordinates will be generated in the original coordinate system + // of the line end points and the transform will only be used to determine + // the minimum line width. + // + // For kButt and kSquare end caps the ends should always be exteded as + // per that decoration, but for kRound caps the ends might be extended + // if the goal is to get a conservative bounds and might not be extended + // if the calling code is planning to draw the round caps on the ends. + // + // @return true if the transform and width were not degenerate + bool ComputeCorners(Point corners[4], + const Matrix& transform, + bool extend_endpoints) const; + + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + // |Geometry| + GeometryVertexType GetVertexType() const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + Point center_; + Scalar radius_; + + EllipseGeometry(const EllipseGeometry&) = delete; + + EllipseGeometry& operator=(const EllipseGeometry&) = delete; +}; + +} // namespace impeller diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index 9f787b651e481..4b1a32a6e3fe1 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -8,6 +8,7 @@ #include #include "impeller/entity/geometry/cover_geometry.h" +#include "impeller/entity/geometry/ellipse_geometry.h" #include "impeller/entity/geometry/fill_path_geometry.h" #include "impeller/entity/geometry/line_geometry.h" #include "impeller/entity/geometry/point_field_geometry.h" @@ -118,6 +119,10 @@ std::shared_ptr Geometry::MakeLine(Point p0, return std::make_shared(p0, p1, width, cap); } +std::shared_ptr Geometry::MakeCircle(Point center, Scalar radius) { + return std::make_shared(center, radius); +} + bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const { return false; } diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 307ce315d1af7..2d1dac945e28e 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -68,6 +68,8 @@ class Geometry { Scalar width, Cap cap); + static std::shared_ptr MakeCircle(Point center, Scalar radius); + static std::shared_ptr MakePointField(std::vector points, Scalar radius, bool round); diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 6682e56af17a4..03a61b1f5206e 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -4,39 +4,53 @@ #include "impeller/entity/geometry/line_geometry.h" +#include "flutter/impeller/entity/geometry/circle_tessellator.h" + namespace impeller { LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) : p0_(p0), p1_(p1), width_(width), cap_(cap) { - // Some of the code below is prepared to deal with things like coverage - // of a line with round caps, but more work is needed to deal with drawing - // the round end caps FML_DCHECK(width >= 0); - FML_DCHECK(cap != Cap::kRound); } -bool LineGeometry::ComputeCorners(Point corners[4], - const Matrix& transform, - bool extend_endpoints) const { +Scalar LineGeometry::ComputeHalfWidth(const Matrix& transform) const { auto determinant = transform.GetDeterminant(); if (determinant == 0) { - return false; + return 0.0f; } Scalar min_size = 1.0f / sqrt(std::abs(determinant)); - Scalar stroke_half_width = std::max(width_, min_size) * 0.5f; + return std::max(width_, min_size) * 0.5f; +} + +Point LineGeometry::ComputeAlongVector(const Matrix& transform, + bool allow_zero_length) const { + Scalar stroke_half_width = ComputeHalfWidth(transform); + if (stroke_half_width < kEhCloseEnough) { + return {}; + } Point along = p1_ - p0_; Scalar length = along.GetLength(); if (length < kEhCloseEnough) { - if (!extend_endpoints) { + if (!allow_zero_length) { // We won't enclose any pixels unless the endpoints are extended - return false; + return {}; } - along = {stroke_half_width, 0}; + return {stroke_half_width, 0}; } else { - along *= stroke_half_width / length; + return along * stroke_half_width / length; } +} + +bool LineGeometry::ComputeCorners(Point corners[4], + const Matrix& transform, + bool extend_endpoints) const { + Point along = ComputeAlongVector(transform, extend_endpoints); + if (along.IsZero()) { + return false; + } + Point across = {along.y, -along.x}; corners[0] = p0_ - across; corners[1] = p1_ - across; @@ -51,23 +65,76 @@ bool LineGeometry::ComputeCorners(Point corners[4], return true; } +PrimitiveType LineGeometry::FillRectCapVertices(std::vector& vertices, + const Matrix& transform) const { + Point corners[4]; + if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { + vertices.reserve(4); + vertices.push_back(corners[0]); + vertices.push_back(corners[1]); + vertices.push_back(corners[2]); + vertices.push_back(corners[3]); + } + return PrimitiveType::kTriangleStrip; +} + +PrimitiveType LineGeometry::FillRoundCapVertices( + std::vector& vertices, + const Matrix& transform) const { + Point along = ComputeAlongVector(transform, true); + if (!along.IsZero()) { + Point across = {along.y, -along.x}; + + CircleTessellator divider(transform, along.GetLength()); + + size_t line_vertex_count = (p0_ != p1_) ? 6 : 0; + auto point_count = divider.GetQuadrantVertexCount() * 4 + line_vertex_count; + vertices.reserve(point_count); + divider.FillQuadrantTriangles(vertices, p0_, -across, -along); + divider.FillQuadrantTriangles(vertices, p0_, -along, across); + if (p0_ != p1_) { + // This would require fewer vertices with a triangle strip, but the + // round caps cannot use that mode so we have to list 2 full triangles. + vertices.emplace_back(p0_ + across); + vertices.emplace_back(p1_ + across); + vertices.emplace_back(p0_ - across); + vertices.emplace_back(p1_ + across); + vertices.emplace_back(p0_ - across); + vertices.emplace_back(p1_ - across); + } + divider.FillQuadrantTriangles(vertices, p1_, across, along); + divider.FillQuadrantTriangles(vertices, p1_, along, -across); + FML_DCHECK(vertices.size() == point_count); + } + + return PrimitiveType::kTriangle; +} + GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - auto& host_buffer = pass.GetTransientsBuffer(); + std::vector vertices; + PrimitiveType triangle_type; - Point corners[4]; - if (!ComputeCorners(corners, entity.GetTransform(), cap_ == Cap::kSquare)) { + if (cap_ == Cap::kRound) { + triangle_type = FillRoundCapVertices(vertices, entity.GetTransform()); + } else { + triangle_type = FillRectCapVertices(vertices, entity.GetTransform()); + } + if (vertices.empty()) { return {}; } + static_assert(sizeof(Point) == 2 * sizeof(float)); + static_assert(alignof(Point) == alignof(float)); return GeometryResult{ - .type = PrimitiveType::kTriangleStrip, + .type = triangle_type, .vertex_buffer = { - .vertex_buffer = host_buffer.Emplace(corners, 8 * sizeof(float), - alignof(float)), - .vertex_count = 4, + .vertex_buffer = pass.GetTransientsBuffer().Emplace( + vertices.data(), vertices.size() * sizeof(Point), + alignof(Point)), + .vertex_count = vertices.size(), .index_type = IndexType::kNone, }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * @@ -82,28 +149,36 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - auto& host_buffer = pass.GetTransientsBuffer(); + std::vector vertices; + PrimitiveType triangle_type; - auto uv_transform = - texture_coverage.GetNormalizingTransform() * effect_transform; - Point corners[4]; - if (!ComputeCorners(corners, entity.GetTransform(), cap_ == Cap::kSquare)) { + if (cap_ == Cap::kRound) { + triangle_type = FillRoundCapVertices(vertices, entity.GetTransform()); + } else { + triangle_type = FillRectCapVertices(vertices, entity.GetTransform()); + } + if (vertices.empty()) { return {}; } - std::vector data(8); - for (auto i = 0u, j = 0u; i < 8; i += 2, j++) { - data[i] = corners[j]; - data[i + 1] = uv_transform * corners[j]; + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + + std::vector data(vertices.size() * 2); + for (auto i = 0u, j = 0u; i < data.size(); i += 2, j++) { + data[i] = vertices[j]; + data[i + 1] = uv_transform * vertices[j]; } + static_assert(sizeof(Point) == 2 * sizeof(float)); + static_assert(alignof(Point) == alignof(float)); return GeometryResult{ - .type = PrimitiveType::kTriangleStrip, + .type = triangle_type, .vertex_buffer = { - .vertex_buffer = host_buffer.Emplace( - data.data(), 16 * sizeof(float), alignof(float)), - .vertex_count = 4, + .vertex_buffer = pass.GetTransientsBuffer().Emplace( + data.data(), data.size() * sizeof(Point), alignof(Point)), + .vertex_count = vertices.size(), .index_type = IndexType::kNone, }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * @@ -137,7 +212,7 @@ bool LineGeometry::CoversArea(const Matrix& transform, const Rect& rect) const { } bool LineGeometry::IsAxisAlignedRect() const { - return p0_.x == p1_.x || p0_.y == p1_.y; + return cap_ != Cap::kRound && (p0_.x == p1_.x || p0_.y == p1_.y); } } // namespace impeller diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index fe37de6a62f56..1fa06eba0fcf8 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -40,11 +40,22 @@ class LineGeometry final : public Geometry { const Matrix& transform, bool extend_endpoints) const; + Point ComputeAlongVector(const Matrix& transform, + bool allow_zero_length) const; + + Scalar ComputeHalfWidth(const Matrix& transform) const; + // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; + PrimitiveType FillRoundCapVertices(std::vector& vertices, + const Matrix& transform) const; + + PrimitiveType FillRectCapVertices(std::vector& vertices, + const Matrix& transform) const; + // |Geometry| GeometryVertexType GetVertexType() const override; diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index c1f08c501e2dc..c22b58122430f 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -4,6 +4,7 @@ #include "impeller/entity/geometry/point_field_geometry.h" +#include "flutter/impeller/entity/geometry/circle_tessellator.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/compute_command.h" @@ -18,7 +19,8 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (renderer.GetDeviceCapabilities().SupportsCompute()) { + if (renderer.GetDeviceCapabilities().SupportsCompute() && false) { + FML_LOG(ERROR) << "Using compute"; return GetPositionBufferGPU(renderer, entity, pass); } auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass); @@ -42,7 +44,8 @@ GeometryResult PointFieldGeometry::GetPositionUVBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (renderer.GetDeviceCapabilities().SupportsCompute()) { + if (renderer.GetDeviceCapabilities().SupportsCompute() && false) { + FML_LOG(ERROR) << "Using compute"; return GetPositionBufferGPU(renderer, entity, pass, texture_coverage, effect_transform); } @@ -79,44 +82,36 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, Scalar min_size = 1.0f / sqrt(std::abs(determinant)); Scalar radius = std::max(radius_, min_size); - auto vertices_per_geom = ComputeCircleDivisions( - entity.GetTransform().GetMaxBasisLength() * radius, round_); - auto points_per_circle = 3 + (vertices_per_geom - 3) * 3; - auto total = points_per_circle * points_.size(); - auto radian_start = round_ ? 0.0f : 0.785398f; - auto radian_step = k2Pi / vertices_per_geom; - VertexBufferBuilder vtx_builder; - vtx_builder.Reserve(total); - - /// Precompute all relative points and angles for a fixed geometry size. - auto elapsed_angle = radian_start; - std::vector angle_table(vertices_per_geom); - for (auto i = 0u; i < vertices_per_geom; i++) { - angle_table[i] = Point(cos(elapsed_angle), sin(elapsed_angle)) * radius; - elapsed_angle += radian_step; - } - for (auto i = 0u; i < points_.size(); i++) { - auto center = points_[i]; + if (round_) { + CircleTessellator divider(entity.GetTransform(), radius); - auto origin = center + angle_table[0]; - vtx_builder.AppendVertex({origin}); + // Get polygon relative to {0, 0} so we can translate it to each point + // in turn. + auto vertices = divider.GetCircleTriangles({}, radius); + FML_DCHECK(vertices.size() == divider.GetCircleVertexCount()); + vtx_builder.Reserve(vertices.size() * points_.size()); - auto pt1 = center + angle_table[1]; - vtx_builder.AppendVertex({pt1}); - - auto pt2 = center + angle_table[2]; - vtx_builder.AppendVertex({pt2}); - - for (auto j = 0u; j < vertices_per_geom - 3; j++) { - vtx_builder.AppendVertex({origin}); - vtx_builder.AppendVertex({pt2}); - - pt2 = center + angle_table[j + 3]; - vtx_builder.AppendVertex({pt2}); + for (auto& center : points_) { + for (auto& vertex : vertices) { + vtx_builder.AppendVertex({center + vertex}); + } + } + } else { + vtx_builder.Reserve(6 * points_.size()); + for (auto& point : points_) { + // Upper left -> Upper right -> Lower right (-> Upper left) + vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); + vtx_builder.AppendVertex({{point.x + radius, point.y - radius}}); + vtx_builder.AppendVertex({{point.x + radius, point.y + radius}}); + // Upper left -> Lower right -> Lower left (-> Upper left) + vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); + vtx_builder.AppendVertex({{point.x + radius, point.y + radius}}); + vtx_builder.AppendVertex({{point.x - radius, point.y + radius}}); } } + return vtx_builder; } From 63ea32b3a80e5dde71fdbc566f8ea9ddaf0e53b1 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 17 Nov 2023 03:17:52 -0800 Subject: [PATCH 2/9] switch to triangle strips for circles and simplify tessellation API --- .../entity/geometry/circle_tessellator.cc | 110 +++++++--------- impeller/entity/geometry/circle_tessellator.h | 108 ++++++++-------- .../geometry/circle_tessellator_unittests.cc | 115 ++++++++--------- impeller/entity/geometry/ellipse_geometry.cc | 35 ++--- impeller/entity/geometry/line_geometry.cc | 121 ++++++++---------- impeller/entity/geometry/line_geometry.h | 10 +- .../entity/geometry/point_field_geometry.cc | 42 ++++-- 7 files changed, 252 insertions(+), 289 deletions(-) diff --git a/impeller/entity/geometry/circle_tessellator.cc b/impeller/entity/geometry/circle_tessellator.cc index a62b5ea8c7288..94f9735b70b0f 100644 --- a/impeller/entity/geometry/circle_tessellator.cc +++ b/impeller/entity/geometry/circle_tessellator.cc @@ -52,79 +52,57 @@ const std::vector& CircleTessellator::GetTrigForDivisions( return trigs; } -void CircleTessellator::ExtendRelativeQuadrantToAbsoluteCircle( - std::vector& points, - const Point& center) { - auto quadrant_points = points.size(); - - // The 1st quadrant points are reversed in order, reflected around - // the Y axis, and translated to become absolute 2nd quadrant points. - for (size_t i = 1; i <= quadrant_points; i++) { - auto point = points[quadrant_points - i]; - points.emplace_back(center.x + point.x, center.y - point.y); - } +void CircleTessellator::GenerateCircleTriangleStrip( + const TessellatedPointProc& proc, + const Point& center, + Scalar radius) const { + auto trigs = GetTrigForDivisions(quadrant_divisions_); - // The 1st quadrant points are reflected around the X & Y axes - // and translated to become absolute 3rd quadrant points. - for (size_t i = 0; i < quadrant_points; i++) { - auto point = points[i]; - points.emplace_back(center.x - point.x, center.y - point.y); + for (auto& trig : trigs) { + auto offset = trig * radius; + proc({center.x - offset.x, center.y + offset.y}); + proc({center.x - offset.x, center.y - offset.y}); } - - // The 1st quadrant points are reversed in order, reflected around - // the X axis and translated to become absolute 4th quadrant points. - // The 1st quadrant points are also translated to the center point as - // well since this is the last time we will use them. - for (size_t i = 1; i <= quadrant_points; i++) { - auto point = points[quadrant_points - i]; - points.emplace_back(center.x - point.x, center.y + point.y); - - // This is the last loop where we need the first quadrant to be - // relative so we convert them to absolute as we go. - points[quadrant_points - i] = center + point; + // The second half of the circle should be iterated in reverse, but + // we can instead iterate forward and swap the x/y values of the + // offset as the angles should be symmetric and thus should generate + // symmetrically reversed offset vectors. + for (auto& trig : trigs) { + auto offset = trig * radius; + proc({center.x + offset.y, center.y + offset.x}); + proc({center.x + offset.y, center.y - offset.x}); } } -void CircleTessellator::FillQuadrantTriangles(std::vector& points, - const Point& center, - const Point& start_vector, - const Point& end_vector) const { - // We only deal with circles for now - FML_DCHECK(start_vector.GetLength() - end_vector.GetLength() < - kEhCloseEnough); - // And only for perpendicular vectors - FML_DCHECK(start_vector.Dot(end_vector) < kEhCloseEnough); - +void CircleTessellator::GenerateRoundCapLineTriangleStrip( + const TessellatedPointProc& proc, + const Point& p0, + const Point& p1, + Scalar radius) const { auto trigs = GetTrigForDivisions(quadrant_divisions_); - - auto prev = center + (trigs[0].cos * start_vector + // - trigs[0].sin * end_vector); - for (size_t i = 1; i < trigs.size(); i++) { - points.emplace_back(center); - points.emplace_back(prev); - prev = center + (trigs[i].cos * start_vector + // - trigs[i].sin * end_vector); - points.emplace_back(prev); + auto along = p1 - p0; + auto length = along.GetLength(); + if (length < kEhCloseEnough) { + return GenerateCircleTriangleStrip(proc, p0, radius); + } + along *= radius / length; + auto across = Point(-along.y, along.x); + + for (auto& trig : trigs) { + auto relative_across = across * trig.cos; + auto relative_along = along * trig.sin; + proc({p0 + relative_across - relative_along}); + proc({p1 + relative_across + relative_along}); + } + // The second half of the round caps should be iterated in reverse, but + // we can instead iterate forward and swap the sin/cos values as they + // should be symmetric. + for (auto& trig : trigs) { + auto relative_across = across * trig.sin; + auto relative_along = along * trig.cos; + proc({p0 - relative_across - relative_along}); + proc({p1 - relative_across + relative_along}); } -} - -std::vector CircleTessellator::GetCircleTriangles(const Point& center, - Scalar radius) const { - std::vector points = std::vector(); - const size_t quadrant_points = quadrant_divisions_ * 3; - points.reserve(quadrant_points * 4); - - // Start with the quadrant top-center to right-center using coordinates - // relative to the (0, 0). The coordinates will be made absolute relative - // to the center during the extend method below. - FillQuadrantTriangles(points, {}, {0, -radius}, {radius, 0}); - FML_DCHECK(points.size() == quadrant_points); - - ExtendRelativeQuadrantToAbsoluteCircle(points, center); - - FML_DCHECK(points.size() == quadrant_points * 4); - - return points; } } // namespace impeller diff --git a/impeller/entity/geometry/circle_tessellator.h b/impeller/entity/geometry/circle_tessellator.h index 9853064f27acc..81c5690db6e0c 100644 --- a/impeller/entity/geometry/circle_tessellator.h +++ b/impeller/entity/geometry/circle_tessellator.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "flutter/impeller/geometry/matrix.h" @@ -22,33 +23,37 @@ struct Trig { double cos; double sin; + + Vector2 operator*(Scalar radius) const { + return Vector2(cos * radius, sin * radius); + } + + Vector2 interpolate(Vector2 start_vector, Vector2 end_vector) { + return start_vector * cos + end_vector * sin; + } }; +using TessellatedPointProc = std::function; + /// @brief A utility class to compute the number of divisions for a circle /// given a transform-adjusted pixel radius and methods for generating /// a tessellated set of triangles for a quarter or full circle. /// -/// A helper constructor is provided which can compute the device -/// pixel radius size for a geometry-space radius when viewed under -/// a specified geometry-to-device transform. +/// The constructor will compute the device pixel radius size for +/// the specified geometry-space |radius| when viewed under +/// a specified geometry-to-device |transform|. /// -/// The object should be constructed with the expected radius of the -/// circle in pixels, but can then be used to generate a triangular -/// tessellation with the indicated number of divisions for any +/// The object should be constructed with the expected transform and +/// radius of the circle, but can then be used to generate a triangular +/// tessellation with the computed number of divisions for any /// radius after that. Since the coordinate space in which the /// circle being tessellated is not necessarily device pixel space, /// the radius supplied during tessellation might not match the -/// pixel radius supplied during construction, but the two values +/// pixel radius computed during construction, but the two values /// should be related by the transform in place when the tessellated /// triangles are rendered for maximum tessellation fidelity. class CircleTessellator { public: - /// @brief Constructs a CircleDivider that produces enough segments to - /// reasonably approximate a circle with a specified radius - /// in pixels. - constexpr explicit CircleTessellator(Scalar pixel_radius) - : quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {} - /// @brief Constructs a CircleDivider that produces enough segments to /// reasonably approximate a circle with a specified |radius| /// when viewed under the specified |transform|. @@ -62,40 +67,47 @@ class CircleTessellator { size_t GetQuadrantDivisionCount() const { return quadrant_divisions_; } /// @brief Return the number of vertices that will be generated to - /// tessellate a single quarter circle. - size_t GetQuadrantVertexCount() const { return quadrant_divisions_ * 3; } - - /// @brief Return the number of divisions computed by the algorithm for - /// a full circle. - size_t GetCircleDivisionCount() const { return quadrant_divisions_ * 4; } - - /// @brief Return the number of vertices that will be generated to - /// tessellate a full circle. - size_t GetCircleVertexCount() const { return quadrant_divisions_ * 12; } - - /// @brief Compute the points of a triangular tesselation of the full - /// circle of the given radius and center. + /// tessellate a full circle with a triangle strip. + /// + /// This value can be used to pre-allocate space in a vector + /// to hold the vertices that will be produced by the + /// |GenerateCircleTriangleStrip| and + /// |GenerateRoundCapLineTriangleStrip| methods. + size_t GetCircleVertexCount() const { return (quadrant_divisions_ + 1) * 4; } + + /// @brief Generate the vertices for a triangle strip that covers the + /// circle at a given |radius| from a given |center|, delivering + /// the computed coordinates to the supplied |proc|. /// - /// @return the list of points on the polygonal approximation. - std::vector GetCircleTriangles(const Point& center, - Scalar radius) const; - - /// @brief Adds entries in an existing vector of |vertices| to represent - /// the triangular tessellation of the quarter circle that sweeps - /// from the |start_vector| to the |end_vector| around an origin - /// specified by |center|. The length of the start and end vectors - /// controls the size of the quarter circle. + /// This procedure will generate no more than the number of + /// vertices returned by |GetCircleVertexCount| in an order + /// appropriate for rendering as a triangle strip. + void GenerateCircleTriangleStrip(const TessellatedPointProc& proc, + const Point& center, + Scalar radius) const; + + /// @brief Generate the vertices for a triangle strip that covers the + /// line from |p0| to |p1| with round caps of the specified + /// |radius|, delivering the computed coordinates to the supplied + /// |proc|. /// - /// The axes must be of the same length and perpendicular. The new - /// points will be appended to the end of the vector. - void FillQuadrantTriangles(std::vector& vertices, - const Point& center, - const Point& start_vector, - const Point& end_vector) const; + /// This procedure will generate no more than the number of + /// vertices returned by |GetCircleVertexCount| in an order + /// appropriate for rendering as a triangle strip. + void GenerateRoundCapLineTriangleStrip(const TessellatedPointProc& proc, + const Point& p0, + const Point& p1, + Scalar radius) const; private: const size_t quadrant_divisions_; + /// @brief Constructs a CircleDivider that produces enough segments to + /// reasonably approximate a circle with a specified radius + /// in pixels. + constexpr explicit CircleTessellator(Scalar pixel_radius) + : quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {} + CircleTessellator(const CircleTessellator&) = delete; CircleTessellator& operator=(const CircleTessellator&) = delete; @@ -116,20 +128,6 @@ class CircleTessellator { /// @return The vector of (divisions + 1) trig values. static const std::vector& GetTrigForDivisions(size_t divisions); - /// @brief Extend a list of |points| in the vector containing relative - /// coordinates for the first quadrant (top-center to right-center) - /// into the remaining 3 quadrants and adjust them to be relative - /// to the supplied |center|. - /// - /// The incoming coordinates are assumed to be relative to a - /// center point of (0, 0) and the method will duplicate and - /// reflect them around that origin to fill in the remaining - /// 3 quadrants. As the method works, it will also adjust every - /// point (including the pre-existing 1st quadrant points) to - /// be relative to the new center. - static void ExtendRelativeQuadrantToAbsoluteCircle(std::vector& points, - const Point& center = {}); - static constexpr int MAX_DIVISIONS_ = 35; static std::vector trigs_[MAX_DIVISIONS_ + 1]; diff --git a/impeller/entity/geometry/circle_tessellator_unittests.cc b/impeller/entity/geometry/circle_tessellator_unittests.cc index 1124d9aad64bd..47a15baf3d8a1 100644 --- a/impeller/entity/geometry/circle_tessellator_unittests.cc +++ b/impeller/entity/geometry/circle_tessellator_unittests.cc @@ -62,83 +62,68 @@ TEST(CircleTessellator, TrigAngles) { } TEST(CircleTessellator, DivisionVertexCounts) { - auto test = [](Scalar pixel_radius, size_t quadrant_divisions) { - CircleTessellator tessellator(pixel_radius); + auto test = [](const Matrix& matrix, Scalar radius, + size_t quadrant_divisions) { + CircleTessellator tessellator(matrix, radius); EXPECT_EQ(tessellator.GetQuadrantDivisionCount(), quadrant_divisions) - << "pixel radius = " << pixel_radius; - EXPECT_EQ(tessellator.GetCircleDivisionCount(), quadrant_divisions * 4) - << "pixel radius = " << pixel_radius; - - EXPECT_EQ(tessellator.GetQuadrantVertexCount(), quadrant_divisions * 3) - << "pixel radius = " << pixel_radius; - EXPECT_EQ(tessellator.GetCircleVertexCount(), quadrant_divisions * 12) - << "pixel radius = " << pixel_radius; + << "transform = " << matrix << ", radius = " << radius; + + EXPECT_EQ(tessellator.GetCircleVertexCount(), (quadrant_divisions + 1) * 4) + << "transform = " << matrix << ", radius = " << radius; }; - test(0.0, 1u); - test(0.9, 1u); - test(1.0, 2u); - test(1.9, 2u); - test(2.0, 6u); - test(11.9, 6u); - test(12.0, 9u); - test(35.9, 9u); + test({}, 0.0, 1u); + test({}, 0.9, 1u); + test({}, 1.0, 2u); + test({}, 1.9, 2u); + test(Matrix::MakeScale(Vector2(2.0, 2.0)), 0.95, 2u); + test({}, 2.0, 6u); + test(Matrix::MakeScale(Vector2(2.0, 2.0)), 1.0, 6u); + test({}, 11.9, 6u); + test({}, 12.0, 9u); + test({}, 35.9, 9u); for (int i = 36; i < 140; i += 4) { - test(i, i / 4); - test(i + .1, i / 4 + 1); + test({}, i, i / 4); + test({}, i + .1, i / 4 + 1); } for (int i = 140; i <= 1000; i++) { - test(i, 35u); + test({}, i, 35u); } } -TEST(CircleTessellator, TessellationVertices) { +TEST(CircleTessellator, CircleTessellationVertices) { auto test = [](Scalar pixel_radius, Point center, Scalar radius) { - CircleTessellator tessellator(pixel_radius); - - auto divisions = tessellator.GetCircleDivisionCount(); - auto points = tessellator.GetCircleTriangles(center, radius); - ASSERT_EQ(points.size(), divisions * 3); - ASSERT_EQ(divisions % 4, 0u); - - Point expect_center = center; - Point expect_prev = center - Point(0, radius); - for (size_t i = 0; i < divisions; i++) { - double angle = (k2Pi * (i + 1)) / divisions - kPiOver2; - Point expect_next = - center + Point(cos(angle) * radius, sin(angle) * radius); - auto quadrant = i / (divisions / 4); - Point test_center, test_prev, test_next; - switch (quadrant) { - case 0: - case 2: - // Quadrants 0 and 2 are ordered (center, prev, next); - test_center = points[i * 3 + 0]; - test_prev = points[i * 3 + 1]; - test_next = points[i * 3 + 2]; - break; - - case 1: - case 3: - // Quadrants 1 and 3 are ordered (prev, next, center); - test_center = points[i * 3 + 2]; - test_prev = points[i * 3 + 0]; - test_next = points[i * 3 + 1]; - break; - } - EXPECT_EQ(test_center, expect_center) // - << "point " << i << " out of " << divisions // - << ", center = " << center << ", radius = " << radius; - EXPECT_POINT_NEAR(test_prev, expect_prev) // - << "point " << i << " out of " << divisions // - << ", center = " << center << ", radius = " << radius; - EXPECT_POINT_NEAR(test_next, expect_next) // - << "point " << i << " out of " << divisions // - << ", center = " << center << ", radius = " << radius; - expect_prev = expect_next; + CircleTessellator tessellator({}, pixel_radius); + + auto vertex_count = tessellator.GetCircleVertexCount(); + auto vertices = std::vector(); + tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + vertices.push_back(p); + }, + center, radius); + ASSERT_EQ(vertices.size(), vertex_count); + ASSERT_EQ(vertex_count % 4, 0u); + + auto quadrant_count = vertex_count / 4; + for (size_t i = 0; i < quadrant_count; i++) { + double angle = kPiOver2 * i / (quadrant_count - 1); + double rsin = sin(angle) * radius; + double rcos = cos(angle) * radius; + EXPECT_POINT_NEAR(vertices[i * 2], + Point(center.x - rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[i * 2 + 1], + Point(center.x - rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1], + Point(center.x + rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2], + Point(center.x + rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; } - EXPECT_POINT_NEAR(expect_prev, center - Point(0, radius)); }; test(2.0, {}, 2.0); diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 72e3a3dba451c..ef3485e907b16 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -19,16 +19,19 @@ GeometryResult EllipseGeometry::GetPositionBuffer( RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); - CircleTessellator divider(entity.GetTransform(), radius_); - auto points = divider.GetCircleTriangles(center_, radius_); + CircleTessellator tessellator(entity.GetTransform(), radius_); + auto vertices = std::vector(tessellator.GetCircleVertexCount()); + tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { vertices.push_back(p); }, center_, radius_); return GeometryResult{ - .type = PrimitiveType::kTriangle, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = host_buffer.Emplace( - points.data(), points.size() * sizeof(Point), alignof(float)), - .vertex_count = points.size(), + vertices.data(), vertices.size() * sizeof(Point), + alignof(float)), + .vertex_count = vertices.size(), .index_type = IndexType::kNone, }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * @@ -46,24 +49,26 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); - CircleTessellator divider(entity.GetTransform(), radius_); - auto points = divider.GetCircleTriangles(center_, radius_); auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; - std::vector data(points.size() * 2); - for (auto i = 0u, j = 0u; j < points.size(); i += 2, j++) { - data[i] = points[j]; - data[i + 1] = uv_transform * points[j]; - } + CircleTessellator tessellator(entity.GetTransform(), radius_); + auto vertices = std::vector(tessellator.GetCircleVertexCount()); + tessellator.GenerateCircleTriangleStrip( + [&vertices, &uv_transform](const Point& p) { + vertices.push_back(p); + vertices.push_back(uv_transform * p); + }, + center_, radius_); return GeometryResult{ - .type = PrimitiveType::kTriangle, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = host_buffer.Emplace( - data.data(), data.size() * sizeof(Point), alignof(float)), - .vertex_count = points.size(), + vertices.data(), vertices.size() * sizeof(Point), + alignof(float)), + .vertex_count = vertices.size() / 2, .index_type = IndexType::kNone, }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 03a61b1f5206e..78dd621c6c9e3 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -23,14 +23,14 @@ Scalar LineGeometry::ComputeHalfWidth(const Matrix& transform) const { return std::max(width_, min_size) * 0.5f; } -Point LineGeometry::ComputeAlongVector(const Matrix& transform, - bool allow_zero_length) const { +Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, + bool allow_zero_length) const { Scalar stroke_half_width = ComputeHalfWidth(transform); if (stroke_half_width < kEhCloseEnough) { return {}; } - Point along = p1_ - p0_; + auto along = p1_ - p0_; Scalar length = along.GetLength(); if (length < kEhCloseEnough) { if (!allow_zero_length) { @@ -46,12 +46,12 @@ Point LineGeometry::ComputeAlongVector(const Matrix& transform, bool LineGeometry::ComputeCorners(Point corners[4], const Matrix& transform, bool extend_endpoints) const { - Point along = ComputeAlongVector(transform, extend_endpoints); + auto along = ComputeAlongVector(transform, extend_endpoints); if (along.IsZero()) { return false; } - Point across = {along.y, -along.x}; + auto across = Vector2(along.y, -along.x); corners[0] = p0_ - across; corners[1] = p1_ - across; corners[2] = p0_ + across; @@ -65,61 +65,30 @@ bool LineGeometry::ComputeCorners(Point corners[4], return true; } -PrimitiveType LineGeometry::FillRectCapVertices(std::vector& vertices, - const Matrix& transform) const { - Point corners[4]; - if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { - vertices.reserve(4); - vertices.push_back(corners[0]); - vertices.push_back(corners[1]); - vertices.push_back(corners[2]); - vertices.push_back(corners[3]); - } - return PrimitiveType::kTriangleStrip; -} - -PrimitiveType LineGeometry::FillRoundCapVertices( - std::vector& vertices, - const Matrix& transform) const { - Point along = ComputeAlongVector(transform, true); - if (!along.IsZero()) { - Point across = {along.y, -along.x}; - - CircleTessellator divider(transform, along.GetLength()); - - size_t line_vertex_count = (p0_ != p1_) ? 6 : 0; - auto point_count = divider.GetQuadrantVertexCount() * 4 + line_vertex_count; - vertices.reserve(point_count); - divider.FillQuadrantTriangles(vertices, p0_, -across, -along); - divider.FillQuadrantTriangles(vertices, p0_, -along, across); - if (p0_ != p1_) { - // This would require fewer vertices with a triangle strip, but the - // round caps cannot use that mode so we have to list 2 full triangles. - vertices.emplace_back(p0_ + across); - vertices.emplace_back(p1_ + across); - vertices.emplace_back(p0_ - across); - vertices.emplace_back(p1_ + across); - vertices.emplace_back(p0_ - across); - vertices.emplace_back(p1_ - across); - } - divider.FillQuadrantTriangles(vertices, p1_, across, along); - divider.FillQuadrantTriangles(vertices, p1_, along, -across); - FML_DCHECK(vertices.size() == point_count); - } - - return PrimitiveType::kTriangle; -} - GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { std::vector vertices; - PrimitiveType triangle_type; + auto& transform = entity.GetTransform(); + auto radius = ComputeHalfWidth(transform); if (cap_ == Cap::kRound) { - triangle_type = FillRoundCapVertices(vertices, entity.GetTransform()); + CircleTessellator tessellator(transform, radius); + vertices.reserve(tessellator.GetCircleVertexCount()); + tessellator.GenerateRoundCapLineTriangleStrip( + [&vertices](const Point& p) { // + vertices.push_back(p); + }, + p0_, p1_, radius); } else { - triangle_type = FillRectCapVertices(vertices, entity.GetTransform()); + Point corners[4]; + if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { + vertices.reserve(4); + vertices.push_back(corners[0]); + vertices.push_back(corners[1]); + vertices.push_back(corners[2]); + vertices.push_back(corners[3]); + } } if (vertices.empty()) { return {}; @@ -128,7 +97,7 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, static_assert(sizeof(Point) == 2 * sizeof(float)); static_assert(alignof(Point) == alignof(float)); return GeometryResult{ - .type = triangle_type, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = pass.GetTransientsBuffer().Emplace( @@ -150,35 +119,49 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const Entity& entity, RenderPass& pass) const { std::vector vertices; - PrimitiveType triangle_type; + auto& transform = entity.GetTransform(); + auto radius = ComputeHalfWidth(transform); + + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; if (cap_ == Cap::kRound) { - triangle_type = FillRoundCapVertices(vertices, entity.GetTransform()); + CircleTessellator tessellator(transform, radius); + vertices.reserve(tessellator.GetCircleVertexCount() * 2); + tessellator.GenerateRoundCapLineTriangleStrip( + [&vertices, &uv_transform](const Point& p) { + vertices.push_back(p); + vertices.push_back(uv_transform * p); + }, + p0_, p1_, radius); } else { - triangle_type = FillRectCapVertices(vertices, entity.GetTransform()); + Point corners[4]; + if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { + vertices.reserve(8); + vertices.push_back(corners[0]); + vertices.push_back(uv_transform * corners[0]); + vertices.push_back(corners[1]); + vertices.push_back(uv_transform * corners[1]); + vertices.push_back(corners[2]); + vertices.push_back(uv_transform * corners[2]); + vertices.push_back(corners[3]); + vertices.push_back(uv_transform * corners[3]); + } } if (vertices.empty()) { return {}; } - auto uv_transform = - texture_coverage.GetNormalizingTransform() * effect_transform; - - std::vector data(vertices.size() * 2); - for (auto i = 0u, j = 0u; i < data.size(); i += 2, j++) { - data[i] = vertices[j]; - data[i + 1] = uv_transform * vertices[j]; - } - static_assert(sizeof(Point) == 2 * sizeof(float)); static_assert(alignof(Point) == alignof(float)); return GeometryResult{ - .type = triangle_type, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = pass.GetTransientsBuffer().Emplace( - data.data(), data.size() * sizeof(Point), alignof(Point)), - .vertex_count = vertices.size(), + vertices.data(), vertices.size() * sizeof(Point), + alignof(Point)), + .vertex_count = vertices.size() / 2, .index_type = IndexType::kNone, }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 1fa06eba0fcf8..272702d91a42a 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -40,8 +40,8 @@ class LineGeometry final : public Geometry { const Matrix& transform, bool extend_endpoints) const; - Point ComputeAlongVector(const Matrix& transform, - bool allow_zero_length) const; + Vector2 ComputeAlongVector(const Matrix& transform, + bool allow_zero_length) const; Scalar ComputeHalfWidth(const Matrix& transform) const; @@ -50,12 +50,6 @@ class LineGeometry final : public Geometry { const Entity& entity, RenderPass& pass) const override; - PrimitiveType FillRoundCapVertices(std::vector& vertices, - const Matrix& transform) const; - - PrimitiveType FillRectCapVertices(std::vector& vertices, - const Matrix& transform) const; - // |Geometry| GeometryVertexType GetVertexType() const override; diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index c22b58122430f..52741e89ef74d 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -30,7 +30,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( auto& host_buffer = pass.GetTransientsBuffer(); return { - .type = PrimitiveType::kTriangle, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = vtx_builder->CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), @@ -59,7 +59,7 @@ GeometryResult PointFieldGeometry::GetPositionUVBuffer( auto& host_buffer = pass.GetTransientsBuffer(); return { - .type = PrimitiveType::kTriangle, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = uv_vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), @@ -85,30 +85,50 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, VertexBufferBuilder vtx_builder; if (round_) { - CircleTessellator divider(entity.GetTransform(), radius); + CircleTessellator tessellator(entity.GetTransform(), radius); // Get polygon relative to {0, 0} so we can translate it to each point // in turn. - auto vertices = divider.GetCircleTriangles({}, radius); - FML_DCHECK(vertices.size() == divider.GetCircleVertexCount()); + std::vector vertices; + tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + vertices.push_back(p); + }, + {}, radius); + FML_DCHECK(vertices.size() == tessellator.GetCircleVertexCount()); vtx_builder.Reserve(vertices.size() * points_.size()); + bool first = true; + Point prev; for (auto& center : points_) { + if (first) { + first = false; + } else { + vtx_builder.AppendVertex({prev}); + vtx_builder.AppendVertex({center + vertices[0]}); + } for (auto& vertex : vertices) { - vtx_builder.AppendVertex({center + vertex}); + prev = center + vertex; + vtx_builder.AppendVertex({prev}); } } } else { vtx_builder.Reserve(6 * points_.size()); + bool first = true; + Point prev; for (auto& point : points_) { - // Upper left -> Upper right -> Lower right (-> Upper left) + if (first) { + first = false; + } else { + vtx_builder.AppendVertex({prev}); + vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); + } + + // Z pattern from UL -> UR -> LL -> LR vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); vtx_builder.AppendVertex({{point.x + radius, point.y - radius}}); - vtx_builder.AppendVertex({{point.x + radius, point.y + radius}}); - // Upper left -> Lower right -> Lower left (-> Upper left) - vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); - vtx_builder.AppendVertex({{point.x + radius, point.y + radius}}); vtx_builder.AppendVertex({{point.x - radius, point.y + radius}}); + vtx_builder.AppendVertex({prev = {point.x + radius, point.y + radius}}); } } From d4981242758f50581d19c2df86224709cec31bc2 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 17 Nov 2023 03:20:24 -0800 Subject: [PATCH 3/9] Fix old naming typos in comments --- impeller/entity/geometry/circle_tessellator.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/impeller/entity/geometry/circle_tessellator.h b/impeller/entity/geometry/circle_tessellator.h index 81c5690db6e0c..5663f8efa0e5c 100644 --- a/impeller/entity/geometry/circle_tessellator.h +++ b/impeller/entity/geometry/circle_tessellator.h @@ -54,8 +54,8 @@ using TessellatedPointProc = std::function; /// triangles are rendered for maximum tessellation fidelity. class CircleTessellator { public: - /// @brief Constructs a CircleDivider that produces enough segments to - /// reasonably approximate a circle with a specified |radius| + /// @brief Constructs a CircleTessellator that produces enough segments + /// to reasonably approximate a circle with a specified |radius| /// when viewed under the specified |transform|. constexpr CircleTessellator(const Matrix& transform, Scalar radius) : CircleTessellator(transform.GetMaxBasisLength() * radius) {} @@ -102,8 +102,8 @@ class CircleTessellator { private: const size_t quadrant_divisions_; - /// @brief Constructs a CircleDivider that produces enough segments to - /// reasonably approximate a circle with a specified radius + /// @brief Constructs a CircleTessellator that produces enough segments + /// to reasonably approximate a circle with a specified radius /// in pixels. constexpr explicit CircleTessellator(Scalar pixel_radius) : quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {} From cab69d8f98f81968585632207c2226c6afe70a01 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 21 Nov 2023 16:20:13 -0800 Subject: [PATCH 4/9] simplify vertex accumulation --- impeller/entity/geometry/ellipse_geometry.cc | 38 +++++----- impeller/entity/geometry/line_geometry.cc | 71 ++++++++----------- .../entity/geometry/point_field_geometry.cc | 49 ++++++------- impeller/renderer/vertex_buffer_builder.h | 5 ++ 4 files changed, 71 insertions(+), 92 deletions(-) diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index ef3485e907b16..9d8d37a4762f2 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -19,21 +19,19 @@ GeometryResult EllipseGeometry::GetPositionBuffer( RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); + VertexBufferBuilder vtx_builder; + CircleTessellator tessellator(entity.GetTransform(), radius_); - auto vertices = std::vector(tessellator.GetCircleVertexCount()); + vtx_builder.Reserve(tessellator.GetCircleVertexCount()); tessellator.GenerateCircleTriangleStrip( - [&vertices](const Point& p) { vertices.push_back(p); }, center_, radius_); + [&vtx_builder](const Point& p) { // + vtx_builder.AppendVertex({.position = p}); + }, + center_, radius_); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = - { - .vertex_buffer = host_buffer.Emplace( - vertices.data(), vertices.size() * sizeof(Point), - alignof(float)), - .vertex_count = vertices.size(), - .index_type = IndexType::kNone, - }, + .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, @@ -51,26 +49,22 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; + VertexBufferBuilder vtx_builder; CircleTessellator tessellator(entity.GetTransform(), radius_); - auto vertices = std::vector(tessellator.GetCircleVertexCount()); + vtx_builder.Reserve(tessellator.GetCircleVertexCount()); tessellator.GenerateCircleTriangleStrip( - [&vertices, &uv_transform](const Point& p) { - vertices.push_back(p); - vertices.push_back(uv_transform * p); + [&vtx_builder, &uv_transform](const Point& p) { + vtx_builder.AppendVertex({ + .position = p, + .texture_coords = uv_transform * p, + }); }, center_, radius_); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = - { - .vertex_buffer = host_buffer.Emplace( - vertices.data(), vertices.size() * sizeof(Point), - alignof(float)), - .vertex_count = vertices.size() / 2, - .index_type = IndexType::kNone, - }, + .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 78dd621c6c9e3..52878f609b438 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -68,44 +68,37 @@ bool LineGeometry::ComputeCorners(Point corners[4], GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - std::vector vertices; auto& transform = entity.GetTransform(); auto radius = ComputeHalfWidth(transform); + VertexBufferBuilder vtx_builder; + if (cap_ == Cap::kRound) { CircleTessellator tessellator(transform, radius); - vertices.reserve(tessellator.GetCircleVertexCount()); + vtx_builder.Reserve(tessellator.GetCircleVertexCount()); tessellator.GenerateRoundCapLineTriangleStrip( - [&vertices](const Point& p) { // - vertices.push_back(p); + [&vtx_builder](const Point& p) { // + vtx_builder.AppendVertex({.position = p}); }, p0_, p1_, radius); } else { Point corners[4]; if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { - vertices.reserve(4); - vertices.push_back(corners[0]); - vertices.push_back(corners[1]); - vertices.push_back(corners[2]); - vertices.push_back(corners[3]); + vtx_builder.Reserve(4); + vtx_builder.AppendVertex({.position = corners[0]}); + vtx_builder.AppendVertex({.position = corners[1]}); + vtx_builder.AppendVertex({.position = corners[2]}); + vtx_builder.AppendVertex({.position = corners[3]}); } } - if (vertices.empty()) { + if (!vtx_builder.HasVertices()) { return {}; } - static_assert(sizeof(Point) == 2 * sizeof(float)); - static_assert(alignof(Point) == alignof(float)); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = - { - .vertex_buffer = pass.GetTransientsBuffer().Emplace( - vertices.data(), vertices.size() * sizeof(Point), - alignof(Point)), - .vertex_count = vertices.size(), - .index_type = IndexType::kNone, - }, + vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, @@ -118,52 +111,44 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - std::vector vertices; auto& transform = entity.GetTransform(); auto radius = ComputeHalfWidth(transform); auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; + VertexBufferBuilder vtx_builder; if (cap_ == Cap::kRound) { CircleTessellator tessellator(transform, radius); - vertices.reserve(tessellator.GetCircleVertexCount() * 2); + vtx_builder.Reserve(tessellator.GetCircleVertexCount()); tessellator.GenerateRoundCapLineTriangleStrip( - [&vertices, &uv_transform](const Point& p) { - vertices.push_back(p); - vertices.push_back(uv_transform * p); + [&vtx_builder, &uv_transform](const Point& p) { + vtx_builder.AppendVertex({ + .position = p, + .texture_coords = uv_transform * p, + }); }, p0_, p1_, radius); } else { Point corners[4]; if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { - vertices.reserve(8); - vertices.push_back(corners[0]); - vertices.push_back(uv_transform * corners[0]); - vertices.push_back(corners[1]); - vertices.push_back(uv_transform * corners[1]); - vertices.push_back(corners[2]); - vertices.push_back(uv_transform * corners[2]); - vertices.push_back(corners[3]); - vertices.push_back(uv_transform * corners[3]); + vtx_builder.Reserve(4); + for (auto& corner : corners) { + vtx_builder.AppendVertex({ + .position = corner, + .texture_coords = uv_transform * corner, + }); + } } } - if (vertices.empty()) { + if (!vtx_builder.HasVertices()) { return {}; } - static_assert(sizeof(Point) == 2 * sizeof(float)); - static_assert(alignof(Point) == alignof(float)); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = - { - .vertex_buffer = pass.GetTransientsBuffer().Emplace( - vertices.data(), vertices.size() * sizeof(Point), - alignof(Point)), - .vertex_count = vertices.size() / 2, - .index_type = IndexType::kNone, - }, + vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index 52741e89ef74d..efe38e79e395c 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -87,48 +87,43 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, if (round_) { CircleTessellator tessellator(entity.GetTransform(), radius); - // Get polygon relative to {0, 0} so we can translate it to each point - // in turn. - std::vector vertices; + // Get triangulation relative to {0, 0} so we can translate it to each + // point in turn. + std::vector circle_vertices; + circle_vertices.reserve(tessellator.GetCircleVertexCount()); tessellator.GenerateCircleTriangleStrip( - [&vertices](const Point& p) { // - vertices.push_back(p); + [&circle_vertices](const Point& p) { // + circle_vertices.push_back(p); }, {}, radius); - FML_DCHECK(vertices.size() == tessellator.GetCircleVertexCount()); - vtx_builder.Reserve(vertices.size() * points_.size()); + FML_DCHECK(circle_vertices.size() == tessellator.GetCircleVertexCount()); - bool first = true; - Point prev; + vtx_builder.Reserve((circle_vertices.size() + 2) * points_.size() - 2); for (auto& center : points_) { - if (first) { - first = false; - } else { - vtx_builder.AppendVertex({prev}); - vtx_builder.AppendVertex({center + vertices[0]}); + if (vtx_builder.HasVertices()) { + vtx_builder.AppendVertex(vtx_builder.Last()); + vtx_builder.AppendVertex({center + circle_vertices[0]}); } - for (auto& vertex : vertices) { - prev = center + vertex; - vtx_builder.AppendVertex({prev}); + + for (auto& vertex : circle_vertices) { + vtx_builder.AppendVertex({center + vertex}); } } } else { - vtx_builder.Reserve(6 * points_.size()); - bool first = true; - Point prev; + vtx_builder.Reserve(6 * points_.size() - 2); for (auto& point : points_) { - if (first) { - first = false; - } else { - vtx_builder.AppendVertex({prev}); - vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); + auto first = Point(point.x - radius, point.y - radius); + + if (vtx_builder.HasVertices()) { + vtx_builder.AppendVertex(vtx_builder.Last()); + vtx_builder.AppendVertex({first}); } // Z pattern from UL -> UR -> LL -> LR - vtx_builder.AppendVertex({{point.x - radius, point.y - radius}}); + vtx_builder.AppendVertex({first}); vtx_builder.AppendVertex({{point.x + radius, point.y - radius}}); vtx_builder.AppendVertex({{point.x - radius, point.y + radius}}); - vtx_builder.AppendVertex({prev = {point.x + radius, point.y + radius}}); + vtx_builder.AppendVertex({{point.x + radius, point.y + radius}}); } } diff --git a/impeller/renderer/vertex_buffer_builder.h b/impeller/renderer/vertex_buffer_builder.h index b70e8a700389a..2f92e24c96eec 100644 --- a/impeller/renderer/vertex_buffer_builder.h +++ b/impeller/renderer/vertex_buffer_builder.h @@ -56,6 +56,11 @@ class VertexBufferBuilder { return indices_.size() > 0 ? indices_.size() : vertices_.size(); } + const VertexType& Last() const { + FML_DCHECK(!vertices_.empty()); + return vertices_.back(); + } + VertexBufferBuilder& AppendVertex(VertexType_ vertex) { vertices_.emplace_back(std::move(vertex)); return *this; From cfca2e18599099c67b9c05128a534c1d5dcaa0c0 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 21 Nov 2023 22:45:58 -0800 Subject: [PATCH 5/9] re-enable GPU compute tessellator for PointFields --- impeller/entity/geometry/point_field_geometry.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index efe38e79e395c..462a2d175fb92 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -19,8 +19,7 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (renderer.GetDeviceCapabilities().SupportsCompute() && false) { - FML_LOG(ERROR) << "Using compute"; + if (renderer.GetDeviceCapabilities().SupportsCompute()) { return GetPositionBufferGPU(renderer, entity, pass); } auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass); @@ -44,8 +43,7 @@ GeometryResult PointFieldGeometry::GetPositionUVBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (renderer.GetDeviceCapabilities().SupportsCompute() && false) { - FML_LOG(ERROR) << "Using compute"; + if (renderer.GetDeviceCapabilities().SupportsCompute()) { return GetPositionBufferGPU(renderer, entity, pass, texture_coverage, effect_transform); } From 4b638ba6f8add217c2a37d7046f8c8e0027cb652 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 22 Nov 2023 13:56:39 -0800 Subject: [PATCH 6/9] move to direct HostBuffer emplacement of tessellation results --- .../entity/geometry/circle_tessellator.cc | 11 +- impeller/entity/geometry/circle_tessellator.h | 7 +- impeller/entity/geometry/ellipse_geometry.cc | 67 ++++++---- impeller/entity/geometry/line_geometry.cc | 117 ++++++++++++------ 4 files changed, 135 insertions(+), 67 deletions(-) diff --git a/impeller/entity/geometry/circle_tessellator.cc b/impeller/entity/geometry/circle_tessellator.cc index 94f9735b70b0f..188f994b992e8 100644 --- a/impeller/entity/geometry/circle_tessellator.cc +++ b/impeller/entity/geometry/circle_tessellator.cc @@ -8,7 +8,7 @@ namespace impeller { -std::vector CircleTessellator::trigs_[MAX_DIVISIONS_ + 1]; +std::vector CircleTessellator::trigs_[kMaxDivisions + 1]; size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { // Note: these values are approximated based on the values returned from @@ -26,16 +26,17 @@ size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { return 9; } pixel_radius /= 4; - if (pixel_radius > (MAX_DIVISIONS_ - 1)) { - return MAX_DIVISIONS_; + if (pixel_radius > (kMaxDivisions - 1)) { + return kMaxDivisions; } - return static_cast(ceil(pixel_radius)); + return static_cast(ceil(pixel_radius)); } const std::vector& CircleTessellator::GetTrigForDivisions( size_t divisions) { - FML_DCHECK(divisions > 0 && divisions <= MAX_DIVISIONS_); + FML_DCHECK(divisions > 0 && divisions <= kMaxDivisions); std::vector& trigs = trigs_[divisions]; + trigs.reserve(divisions + 1); if (trigs.empty()) { double angle_scale = kPiOver2 / divisions; diff --git a/impeller/entity/geometry/circle_tessellator.h b/impeller/entity/geometry/circle_tessellator.h index 5663f8efa0e5c..504e8d8eb2ff1 100644 --- a/impeller/entity/geometry/circle_tessellator.h +++ b/impeller/entity/geometry/circle_tessellator.h @@ -16,7 +16,8 @@ namespace impeller { /// @brief A structure to store the sine and cosine of an angle. struct Trig { /// Construct a Trig object from a given angle in radians. - Trig(Radians r) : cos(std::cos(r.radians)), sin(std::sin(r.radians)) {} + explicit Trig(Radians r) + : cos(std::cos(r.radians)), sin(std::sin(r.radians)) {} /// Construct a Trig object from the given cosine and sine values. Trig(double cos, double sin) : cos(cos), sin(sin) {} @@ -128,9 +129,9 @@ class CircleTessellator { /// @return The vector of (divisions + 1) trig values. static const std::vector& GetTrigForDivisions(size_t divisions); - static constexpr int MAX_DIVISIONS_ = 35; + static constexpr int kMaxDivisions = 35; - static std::vector trigs_[MAX_DIVISIONS_ + 1]; + static std::vector trigs_[kMaxDivisions + 1]; }; } // namespace impeller diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 9d8d37a4762f2..178e0ad9808b1 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -18,20 +18,33 @@ GeometryResult EllipseGeometry::GetPositionBuffer( const Entity& entity, RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); + using VT = SolidFillVertexShader::PerVertexData; - VertexBufferBuilder vtx_builder; - + Scalar radius = radius_; + const Point& center = center_; CircleTessellator tessellator(entity.GetTransform(), radius_); - vtx_builder.Reserve(tessellator.GetCircleVertexCount()); - tessellator.GenerateCircleTriangleStrip( - [&vtx_builder](const Point& p) { // - vtx_builder.AppendVertex({.position = p}); - }, - center_, radius_); + size_t count = tessellator.GetCircleVertexCount(); + auto vertex_buffer = + host_buffer.Emplace(count * sizeof(VT), alignof(VT), + [&tessellator, ¢er, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + center, radius); + }); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .vertex_count = count, + .index_type = IndexType::kNone, + }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, @@ -46,25 +59,37 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( const Entity& entity, RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); - + using VT = TextureFillVertexShader::PerVertexData; auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; - VertexBufferBuilder vtx_builder; + Scalar radius = radius_; + const Point& center = center_; CircleTessellator tessellator(entity.GetTransform(), radius_); - vtx_builder.Reserve(tessellator.GetCircleVertexCount()); - tessellator.GenerateCircleTriangleStrip( - [&vtx_builder, &uv_transform](const Point& p) { - vtx_builder.AppendVertex({ - .position = p, - .texture_coords = uv_transform * p, - }); - }, - center_, radius_); + size_t count = tessellator.GetCircleVertexCount(); + auto vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&tessellator, &uv_transform, ¢er, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + tessellator.GenerateCircleTriangleStrip( + [&vertices, &uv_transform](const Point& p) { // + *vertices++ = { + .position = p, + .texture_coords = uv_transform * p, + }; + }, + center, radius); + }); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, - .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .vertex_count = count, + .index_type = IndexType::kNone, + }, + // .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 52878f609b438..b7eba185d9154 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -68,37 +68,58 @@ bool LineGeometry::ComputeCorners(Point corners[4], GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { + auto& host_buffer = pass.GetTransientsBuffer(); + using VT = SolidFillVertexShader::PerVertexData; + auto& transform = entity.GetTransform(); auto radius = ComputeHalfWidth(transform); - VertexBufferBuilder vtx_builder; - + size_t count; + BufferView vertex_buffer; if (cap_ == Cap::kRound) { + const Point& p0 = p0_; + const Point& p1 = p1_; + CircleTessellator tessellator(transform, radius); - vtx_builder.Reserve(tessellator.GetCircleVertexCount()); - tessellator.GenerateRoundCapLineTriangleStrip( - [&vtx_builder](const Point& p) { // - vtx_builder.AppendVertex({.position = p}); - }, - p0_, p1_, radius); + count = tessellator.GetCircleVertexCount(); + vertex_buffer = + host_buffer.Emplace(count * sizeof(VT), alignof(VT), + [&tessellator, &p0, &p1, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + tessellator.GenerateRoundCapLineTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + p0, p1, radius); + }); } else { Point corners[4]; if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { - vtx_builder.Reserve(4); - vtx_builder.AppendVertex({.position = corners[0]}); - vtx_builder.AppendVertex({.position = corners[1]}); - vtx_builder.AppendVertex({.position = corners[2]}); - vtx_builder.AppendVertex({.position = corners[3]}); + count = 4; + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), [&corners](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + for (auto& corner : corners) { + *vertices++ = { + .position = corner, + }; + } + }); + } else { + return {}; } } - if (!vtx_builder.HasVertices()) { - return {}; - } return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = - vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()), + { + .vertex_buffer = vertex_buffer, + .vertex_count = count, + .index_type = IndexType::kNone, + }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, @@ -111,44 +132,64 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { + auto& host_buffer = pass.GetTransientsBuffer(); + using VT = TextureFillVertexShader::PerVertexData; + auto& transform = entity.GetTransform(); auto radius = ComputeHalfWidth(transform); auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; - VertexBufferBuilder vtx_builder; + size_t count; + BufferView vertex_buffer; if (cap_ == Cap::kRound) { + const Point& p0 = p0_; + const Point& p1 = p1_; + CircleTessellator tessellator(transform, radius); - vtx_builder.Reserve(tessellator.GetCircleVertexCount()); - tessellator.GenerateRoundCapLineTriangleStrip( - [&vtx_builder, &uv_transform](const Point& p) { - vtx_builder.AppendVertex({ - .position = p, - .texture_coords = uv_transform * p, - }); - }, - p0_, p1_, radius); + count = tessellator.GetCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&tessellator, &uv_transform, &p0, &p1, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + tessellator.GenerateRoundCapLineTriangleStrip( + [&vertices, &uv_transform](const Point& p) { // + *vertices++ = { + .position = p, + .texture_coords = uv_transform * p, + }; + }, + p0, p1, radius); + }); } else { Point corners[4]; if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { - vtx_builder.Reserve(4); - for (auto& corner : corners) { - vtx_builder.AppendVertex({ - .position = corner, - .texture_coords = uv_transform * corner, - }); - } + count = 4; + vertex_buffer = + host_buffer.Emplace(count * sizeof(VT), alignof(VT), + [&uv_transform, &corners](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + for (auto& corner : corners) { + *vertices++ = { + .position = corner, + .texture_coords = uv_transform * corner, + }; + } + }); + } else { + return {}; } } - if (!vtx_builder.HasVertices()) { - return {}; - } return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = - vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()), + { + .vertex_buffer = vertex_buffer, + .vertex_count = count, + .index_type = IndexType::kNone, + }, .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, From 4ccdb468bbefa32b30cd57b18adf81a0f7f7c6a2 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 29 Nov 2023 21:51:54 -0800 Subject: [PATCH 7/9] Move CircleTessellator to tessellator dir and move cache to Tessellator instance --- ci/licenses_golden/excluded_files | 3 +- ci/licenses_golden/licenses_flutter | 12 +- impeller/entity/BUILD.gn | 3 - .../entity/geometry/circle_tessellator.cc | 109 ------------ .../geometry/circle_tessellator_unittests.cc | 136 -------------- impeller/entity/geometry/ellipse_geometry.cc | 8 +- impeller/entity/geometry/line_geometry.cc | 8 +- .../entity/geometry/point_field_geometry.cc | 5 +- impeller/geometry/BUILD.gn | 3 + impeller/geometry/trig.cc | 11 ++ impeller/geometry/trig.h | 32 ++++ impeller/geometry/trig_unittests.cc | 64 +++++++ impeller/tessellator/BUILD.gn | 16 +- impeller/tessellator/circle_tessellator.cc | 167 ++++++++++++++++++ .../circle_tessellator.h | 59 +++---- .../circle_tessellator_unittests.cc | 93 ++++++++++ impeller/tessellator/tessellator.h | 6 + 17 files changed, 440 insertions(+), 295 deletions(-) delete mode 100644 impeller/entity/geometry/circle_tessellator.cc delete mode 100644 impeller/entity/geometry/circle_tessellator_unittests.cc create mode 100644 impeller/geometry/trig.cc create mode 100644 impeller/geometry/trig.h create mode 100644 impeller/geometry/trig_unittests.cc create mode 100644 impeller/tessellator/circle_tessellator.cc rename impeller/{entity/geometry => tessellator}/circle_tessellator.h (77%) create mode 100644 impeller/tessellator/circle_tessellator_unittests.cc diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 7e84b168a753c..3cbe03f25dbda 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -147,7 +147,6 @@ ../../../flutter/impeller/entity/contents/vertices_contents_unittests.cc ../../../flutter/impeller/entity/entity_pass_target_unittests.cc ../../../flutter/impeller/entity/entity_unittests.cc -../../../flutter/impeller/entity/geometry/circle_tessellator_unittests.cc ../../../flutter/impeller/entity/geometry/geometry_unittests.cc ../../../flutter/impeller/entity/render_target_cache_unittests.cc ../../../flutter/impeller/fixtures @@ -157,6 +156,7 @@ ../../../flutter/impeller/geometry/path_unittests.cc ../../../flutter/impeller/geometry/rect_unittests.cc ../../../flutter/impeller/geometry/size_unittests.cc +../../../flutter/impeller/geometry/trig_unittests.cc ../../../flutter/impeller/golden_tests/README.md ../../../flutter/impeller/golden_tests_harvester/.dart_tool ../../../flutter/impeller/golden_tests_harvester/.gitignore @@ -194,6 +194,7 @@ ../../../flutter/impeller/scene/importer/importer_unittests.cc ../../../flutter/impeller/scene/scene_unittests.cc ../../../flutter/impeller/shader_archive/shader_archive_unittests.cc +../../../flutter/impeller/tessellator/circle_tessellator_unittests.cc ../../../flutter/impeller/tessellator/dart/.dart_tool ../../../flutter/impeller/tessellator/dart/pubspec.lock ../../../flutter/impeller/tessellator/dart/pubspec.yaml diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ea9c781a691ee..5e94ca5d1e55c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5099,8 +5099,6 @@ ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutte ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_playground.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_playground.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/geometry/circle_tessellator.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/cover_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc + ../../../flutter/LICENSE @@ -5210,6 +5208,8 @@ ORIGIN: ../../../flutter/impeller/geometry/sigma.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/sigma.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/size.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/size.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/geometry/trig.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/geometry/trig.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/type_traits.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/type_traits.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/geometry/vector.cc + ../../../flutter/LICENSE @@ -5527,6 +5527,8 @@ ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive_writer.cc + ../. ORIGIN: ../../../flutter/impeller/shader_archive/shader_archive_writer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/c/tessellator.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/c/tessellator.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/tessellator/circle_tessellator.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/tessellator/circle_tessellator.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/tessellator.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/tessellator.h + ../../../flutter/LICENSE @@ -7895,8 +7897,6 @@ FILE: ../../../flutter/impeller/entity/entity_pass_target.cc FILE: ../../../flutter/impeller/entity/entity_pass_target.h FILE: ../../../flutter/impeller/entity/entity_playground.cc FILE: ../../../flutter/impeller/entity/entity_playground.h -FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.cc -FILE: ../../../flutter/impeller/entity/geometry/circle_tessellator.h FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/cover_geometry.h FILE: ../../../flutter/impeller/entity/geometry/ellipse_geometry.cc @@ -8006,6 +8006,8 @@ FILE: ../../../flutter/impeller/geometry/sigma.cc FILE: ../../../flutter/impeller/geometry/sigma.h FILE: ../../../flutter/impeller/geometry/size.cc FILE: ../../../flutter/impeller/geometry/size.h +FILE: ../../../flutter/impeller/geometry/trig.cc +FILE: ../../../flutter/impeller/geometry/trig.h FILE: ../../../flutter/impeller/geometry/type_traits.cc FILE: ../../../flutter/impeller/geometry/type_traits.h FILE: ../../../flutter/impeller/geometry/vector.cc @@ -8324,6 +8326,8 @@ FILE: ../../../flutter/impeller/shader_archive/shader_archive_writer.cc FILE: ../../../flutter/impeller/shader_archive/shader_archive_writer.h FILE: ../../../flutter/impeller/tessellator/c/tessellator.cc FILE: ../../../flutter/impeller/tessellator/c/tessellator.h +FILE: ../../../flutter/impeller/tessellator/circle_tessellator.cc +FILE: ../../../flutter/impeller/tessellator/circle_tessellator.h FILE: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart FILE: ../../../flutter/impeller/tessellator/tessellator.cc FILE: ../../../flutter/impeller/tessellator/tessellator.h diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 9934b1d9938dd..24bc4670e94da 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -191,8 +191,6 @@ impeller_component("entity") { "entity_pass_delegate.h", "entity_pass_target.cc", "entity_pass_target.h", - "geometry/circle_tessellator.cc", - "geometry/circle_tessellator.h", "geometry/cover_geometry.cc", "geometry/cover_geometry.h", "geometry/ellipse_geometry.cc", @@ -270,7 +268,6 @@ impeller_component("entity_unittests") { "entity_playground.cc", "entity_playground.h", "entity_unittests.cc", - "geometry/circle_tessellator_unittests.cc", "geometry/geometry_unittests.cc", "render_target_cache_unittests.cc", ] diff --git a/impeller/entity/geometry/circle_tessellator.cc b/impeller/entity/geometry/circle_tessellator.cc deleted file mode 100644 index 188f994b992e8..0000000000000 --- a/impeller/entity/geometry/circle_tessellator.cc +++ /dev/null @@ -1,109 +0,0 @@ -// 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/entity/geometry/circle_tessellator.h" - -#include "flutter/fml/logging.h" - -namespace impeller { - -std::vector CircleTessellator::trigs_[kMaxDivisions + 1]; - -size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { - // Note: these values are approximated based on the values returned from - // the decomposition of 4 cubics performed by Path::CreatePolyline. - if (pixel_radius < 1.0) { - return 1; - } - if (pixel_radius < 2.0) { - return 2; - } - if (pixel_radius < 12.0) { - return 6; - } - if (pixel_radius <= 36.0) { - return 9; - } - pixel_radius /= 4; - if (pixel_radius > (kMaxDivisions - 1)) { - return kMaxDivisions; - } - return static_cast(ceil(pixel_radius)); -} - -const std::vector& CircleTessellator::GetTrigForDivisions( - size_t divisions) { - FML_DCHECK(divisions > 0 && divisions <= kMaxDivisions); - std::vector& trigs = trigs_[divisions]; - trigs.reserve(divisions + 1); - - if (trigs.empty()) { - double angle_scale = kPiOver2 / divisions; - - trigs.emplace_back(1.0, 0.0); - for (size_t i = 1; i < divisions; i++) { - trigs.emplace_back(Radians(i * angle_scale)); - } - trigs.emplace_back(0.0, 1.0); - - FML_DCHECK(trigs.size() == divisions + 1); - } - - return trigs; -} - -void CircleTessellator::GenerateCircleTriangleStrip( - const TessellatedPointProc& proc, - const Point& center, - Scalar radius) const { - auto trigs = GetTrigForDivisions(quadrant_divisions_); - - for (auto& trig : trigs) { - auto offset = trig * radius; - proc({center.x - offset.x, center.y + offset.y}); - proc({center.x - offset.x, center.y - offset.y}); - } - // The second half of the circle should be iterated in reverse, but - // we can instead iterate forward and swap the x/y values of the - // offset as the angles should be symmetric and thus should generate - // symmetrically reversed offset vectors. - for (auto& trig : trigs) { - auto offset = trig * radius; - proc({center.x + offset.y, center.y + offset.x}); - proc({center.x + offset.y, center.y - offset.x}); - } -} - -void CircleTessellator::GenerateRoundCapLineTriangleStrip( - const TessellatedPointProc& proc, - const Point& p0, - const Point& p1, - Scalar radius) const { - auto trigs = GetTrigForDivisions(quadrant_divisions_); - auto along = p1 - p0; - auto length = along.GetLength(); - if (length < kEhCloseEnough) { - return GenerateCircleTriangleStrip(proc, p0, radius); - } - along *= radius / length; - auto across = Point(-along.y, along.x); - - for (auto& trig : trigs) { - auto relative_across = across * trig.cos; - auto relative_along = along * trig.sin; - proc({p0 + relative_across - relative_along}); - proc({p1 + relative_across + relative_along}); - } - // The second half of the round caps should be iterated in reverse, but - // we can instead iterate forward and swap the sin/cos values as they - // should be symmetric. - for (auto& trig : trigs) { - auto relative_across = across * trig.sin; - auto relative_along = along * trig.cos; - proc({p0 - relative_across - relative_along}); - proc({p1 - relative_across + relative_along}); - } -} - -} // namespace impeller diff --git a/impeller/entity/geometry/circle_tessellator_unittests.cc b/impeller/entity/geometry/circle_tessellator_unittests.cc deleted file mode 100644 index 47a15baf3d8a1..0000000000000 --- a/impeller/entity/geometry/circle_tessellator_unittests.cc +++ /dev/null @@ -1,136 +0,0 @@ -// 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 "fml/logging.h" -#include "gtest/gtest.h" - -#include "flutter/impeller/entity/geometry/circle_tessellator.h" -#include "flutter/impeller/geometry/geometry_asserts.h" - -namespace impeller { -namespace testing { - -TEST(CircleTessellator, TrigAngles) { - { - Trig trig(Degrees(0.0)); - EXPECT_EQ(trig.cos, 1.0); - EXPECT_EQ(trig.sin, 0.0); - } - - { - Trig trig(Radians(0.0)); - EXPECT_EQ(trig.cos, 1.0); - EXPECT_EQ(trig.sin, 0.0); - } - - { - Trig trig(Degrees(30.0)); - EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); - EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); - } - - { - Trig trig(Radians(kPi / 6.0)); - EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); - EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); - } - - { - Trig trig(Degrees(60.0)); - EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); - EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); - } - - { - Trig trig(Radians(kPi / 3.0)); - EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); - EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); - } - - { - Trig trig(Degrees(90.0)); - EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); - EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); - } - - { - Trig trig(Radians(kPi / 2.0)); - EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); - EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); - } -} - -TEST(CircleTessellator, DivisionVertexCounts) { - auto test = [](const Matrix& matrix, Scalar radius, - size_t quadrant_divisions) { - CircleTessellator tessellator(matrix, radius); - - EXPECT_EQ(tessellator.GetQuadrantDivisionCount(), quadrant_divisions) - << "transform = " << matrix << ", radius = " << radius; - - EXPECT_EQ(tessellator.GetCircleVertexCount(), (quadrant_divisions + 1) * 4) - << "transform = " << matrix << ", radius = " << radius; - }; - - test({}, 0.0, 1u); - test({}, 0.9, 1u); - test({}, 1.0, 2u); - test({}, 1.9, 2u); - test(Matrix::MakeScale(Vector2(2.0, 2.0)), 0.95, 2u); - test({}, 2.0, 6u); - test(Matrix::MakeScale(Vector2(2.0, 2.0)), 1.0, 6u); - test({}, 11.9, 6u); - test({}, 12.0, 9u); - test({}, 35.9, 9u); - for (int i = 36; i < 140; i += 4) { - test({}, i, i / 4); - test({}, i + .1, i / 4 + 1); - } - for (int i = 140; i <= 1000; i++) { - test({}, i, 35u); - } -} - -TEST(CircleTessellator, CircleTessellationVertices) { - auto test = [](Scalar pixel_radius, Point center, Scalar radius) { - CircleTessellator tessellator({}, pixel_radius); - - auto vertex_count = tessellator.GetCircleVertexCount(); - auto vertices = std::vector(); - tessellator.GenerateCircleTriangleStrip( - [&vertices](const Point& p) { // - vertices.push_back(p); - }, - center, radius); - ASSERT_EQ(vertices.size(), vertex_count); - ASSERT_EQ(vertex_count % 4, 0u); - - auto quadrant_count = vertex_count / 4; - for (size_t i = 0; i < quadrant_count; i++) { - double angle = kPiOver2 * i / (quadrant_count - 1); - double rsin = sin(angle) * radius; - double rcos = cos(angle) * radius; - EXPECT_POINT_NEAR(vertices[i * 2], - Point(center.x - rcos, center.y + rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; - EXPECT_POINT_NEAR(vertices[i * 2 + 1], - Point(center.x - rcos, center.y - rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; - EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1], - Point(center.x + rcos, center.y - rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; - EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2], - Point(center.x + rcos, center.y + rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; - } - }; - - test(2.0, {}, 2.0); - test(2.0, {10, 10}, 2.0); - test(1000.0, {}, 2.0); - test(2.0, {}, 1000.0); -} - -} // namespace testing -} // namespace impeller diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 178e0ad9808b1..87919780d2dbc 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -4,7 +4,7 @@ #include "impeller/entity/geometry/ellipse_geometry.h" -#include "flutter/impeller/entity/geometry/circle_tessellator.h" +#include "flutter/impeller/tessellator/circle_tessellator.h" namespace impeller { @@ -22,7 +22,8 @@ GeometryResult EllipseGeometry::GetPositionBuffer( Scalar radius = radius_; const Point& center = center_; - CircleTessellator tessellator(entity.GetTransform(), radius_); + std::shared_ptr t = renderer.GetTessellator(); + CircleTessellator tessellator(t, entity.GetTransform(), radius_); size_t count = tessellator.GetCircleVertexCount(); auto vertex_buffer = host_buffer.Emplace(count * sizeof(VT), alignof(VT), @@ -65,7 +66,8 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( Scalar radius = radius_; const Point& center = center_; - CircleTessellator tessellator(entity.GetTransform(), radius_); + std::shared_ptr t = renderer.GetTessellator(); + CircleTessellator tessellator(t, entity.GetTransform(), radius_); size_t count = tessellator.GetCircleVertexCount(); auto vertex_buffer = host_buffer.Emplace( count * sizeof(VT), alignof(VT), diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index b7eba185d9154..7beb5af6342c0 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -4,7 +4,7 @@ #include "impeller/entity/geometry/line_geometry.h" -#include "flutter/impeller/entity/geometry/circle_tessellator.h" +#include "flutter/impeller/tessellator/circle_tessellator.h" namespace impeller { @@ -80,7 +80,8 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Point& p0 = p0_; const Point& p1 = p1_; - CircleTessellator tessellator(transform, radius); + std::shared_ptr t = renderer.GetTessellator(); + CircleTessellator tessellator(t, entity.GetTransform(), radius); count = tessellator.GetCircleVertexCount(); vertex_buffer = host_buffer.Emplace(count * sizeof(VT), alignof(VT), @@ -147,7 +148,8 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const Point& p0 = p0_; const Point& p1 = p1_; - CircleTessellator tessellator(transform, radius); + std::shared_ptr t = renderer.GetTessellator(); + CircleTessellator tessellator(t, entity.GetTransform(), radius); count = tessellator.GetCircleVertexCount(); vertex_buffer = host_buffer.Emplace( count * sizeof(VT), alignof(VT), diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index 462a2d175fb92..1a25d9957e396 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -4,7 +4,7 @@ #include "impeller/entity/geometry/point_field_geometry.h" -#include "flutter/impeller/entity/geometry/circle_tessellator.h" +#include "flutter/impeller/tessellator/circle_tessellator.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/compute_command.h" @@ -83,7 +83,8 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, VertexBufferBuilder vtx_builder; if (round_) { - CircleTessellator tessellator(entity.GetTransform(), radius); + std::shared_ptr t = renderer.GetTessellator(); + CircleTessellator tessellator(t, entity.GetTransform(), radius_); // Get triangulation relative to {0, 0} so we can translate it to each // point in turn. diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn index 72b8a01160153..ce6fe023c999d 100644 --- a/impeller/geometry/BUILD.gn +++ b/impeller/geometry/BUILD.gn @@ -36,6 +36,8 @@ impeller_component("geometry") { "sigma.h", "size.cc", "size.h", + "trig.cc", + "trig.h", "type_traits.cc", "type_traits.h", "vector.cc", @@ -65,6 +67,7 @@ impeller_component("geometry_unittests") { "path_unittests.cc", "rect_unittests.cc", "size_unittests.cc", + "trig_unittests.cc", ] deps = [ diff --git a/impeller/geometry/trig.cc b/impeller/geometry/trig.cc new file mode 100644 index 0000000000000..c613a7b5c9620 --- /dev/null +++ b/impeller/geometry/trig.cc @@ -0,0 +1,11 @@ +// 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 "trig.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/geometry/trig.h b/impeller/geometry/trig.h new file mode 100644 index 0000000000000..eddf6552f5ed3 --- /dev/null +++ b/impeller/geometry/trig.h @@ -0,0 +1,32 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/impeller/geometry/point.h" + +namespace impeller { + +/// @brief A structure to store the sine and cosine of an angle. +struct Trig { + /// Construct a Trig object from a given angle in radians. + explicit Trig(Radians r) + : cos(std::cos(r.radians)), sin(std::sin(r.radians)) {} + + /// Construct a Trig object from the given cosine and sine values. + Trig(double cos, double sin) : cos(cos), sin(sin) {} + + double cos; + double sin; + + Vector2 operator*(double radius) const { + return Vector2(static_cast(cos * radius), + static_cast(sin * radius)); + } +}; + +} // namespace impeller diff --git a/impeller/geometry/trig_unittests.cc b/impeller/geometry/trig_unittests.cc new file mode 100644 index 0000000000000..dcdc6b6da2934 --- /dev/null +++ b/impeller/geometry/trig_unittests.cc @@ -0,0 +1,64 @@ +// 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 "fml/logging.h" +#include "gtest/gtest.h" + +#include "flutter/impeller/geometry/trig.h" + +namespace impeller { +namespace testing { + +TEST(TrigTest, TrigAngles) { + { + Trig trig(Degrees(0.0)); + EXPECT_EQ(trig.cos, 1.0); + EXPECT_EQ(trig.sin, 0.0); + } + + { + Trig trig(Radians(0.0)); + EXPECT_EQ(trig.cos, 1.0); + EXPECT_EQ(trig.sin, 0.0); + } + + { + Trig trig(Degrees(30.0)); + EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); + EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 6.0)); + EXPECT_NEAR(trig.cos, sqrt(0.75), kEhCloseEnough); + EXPECT_NEAR(trig.sin, 0.5, kEhCloseEnough); + } + + { + Trig trig(Degrees(60.0)); + EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); + EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 3.0)); + EXPECT_NEAR(trig.cos, 0.5, kEhCloseEnough); + EXPECT_NEAR(trig.sin, sqrt(0.75), kEhCloseEnough); + } + + { + Trig trig(Degrees(90.0)); + EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); + EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); + } + + { + Trig trig(Radians(kPi / 2.0)); + EXPECT_NEAR(trig.cos, 0.0, kEhCloseEnough); + EXPECT_NEAR(trig.sin, 1.0, kEhCloseEnough); + } +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/tessellator/BUILD.gn b/impeller/tessellator/BUILD.gn index d3915f9996679..d49c2459492c5 100644 --- a/impeller/tessellator/BUILD.gn +++ b/impeller/tessellator/BUILD.gn @@ -6,13 +6,18 @@ import("//flutter/impeller/tools/impeller.gni") impeller_component("tessellator") { sources = [ + "circle_tessellator.cc", + "circle_tessellator.h", "tessellator.cc", "tessellator.h", ] public_deps = [ "../geometry" ] - deps = [ "//third_party/libtess2" ] + deps = [ + "//flutter/fml", + "//third_party/libtess2", + ] } impeller_component("tessellator_shared") { @@ -26,12 +31,15 @@ impeller_component("tessellator_shared") { sources = [ "c/tessellator.cc", "c/tessellator.h", + "circle_tessellator.cc", + "circle_tessellator.h", "tessellator.cc", "tessellator.h", ] deps = [ "../geometry", + "//flutter/fml", "//third_party/libtess2", ] @@ -42,9 +50,13 @@ impeller_component("tessellator_shared") { impeller_component("tessellator_unittests") { testonly = true - sources = [ "tessellator_unittests.cc" ] + sources = [ + "circle_tessellator_unittests.cc", + "tessellator_unittests.cc", + ] deps = [ ":tessellator", + "../geometry:geometry_asserts", "//flutter/testing", ] } diff --git a/impeller/tessellator/circle_tessellator.cc b/impeller/tessellator/circle_tessellator.cc new file mode 100644 index 0000000000000..16949fb530caa --- /dev/null +++ b/impeller/tessellator/circle_tessellator.cc @@ -0,0 +1,167 @@ +// 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/tessellator/circle_tessellator.h" + +#include "flutter/fml/logging.h" + +namespace impeller { + +int CircleTessellator::kPrecomputedDivisions[kPrecomputedDivisionCount] = { + // clang-format off + 1, 2, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, + 10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, + 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35, + 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, + 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, + 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 44, + 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, + 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, + // clang-format on +}; + +size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { + if (pixel_radius <= 0.0) { + return 1; + } + int radius_index = ceil(pixel_radius); + if (radius_index < kPrecomputedDivisionCount) { + return kPrecomputedDivisions[radius_index]; + } + double k = kCircleTolerance / pixel_radius; + return ceil(kPi / sqrt(2 * k) / 4); +} + +const std::vector& CircleTessellator::GetTrigsForDivisions( + std::shared_ptr& tessellator, + size_t divisions) { + std::vector& trigs = (divisions < Tessellator::kCachedTrigCount) + ? tessellator->precomputed_trigs_[divisions] + : temp_trigs_; + + if (trigs.empty()) { + // Either not cached yet, or we are usig the temp vector... + trigs.reserve(divisions + 1); + + double angle_scale = kPiOver2 / divisions; + + trigs.emplace_back(1.0, 0.0); + for (size_t i = 1; i < divisions; i++) { + trigs.emplace_back(Radians(i * angle_scale)); + } + trigs.emplace_back(0.0, 1.0); + + FML_DCHECK(trigs.size() == divisions + 1); + } + + return trigs; +} + +void CircleTessellator::GenerateCircleTriangleStrip( + const TessellatedPointProc& proc, + const Point& center, + Scalar radius) const { + for (auto& trig : trigs_) { + auto offset = trig * radius; + proc({center.x - offset.x, center.y + offset.y}); + proc({center.x - offset.x, center.y - offset.y}); + } + // The second half of the circle should be iterated in reverse, but + // we can instead iterate forward and swap the x/y values of the + // offset as the angles should be symmetric and thus should generate + // symmetrically reversed offset vectors. + for (auto& trig : trigs_) { + auto offset = trig * radius; + proc({center.x + offset.y, center.y + offset.x}); + proc({center.x + offset.y, center.y - offset.x}); + } +} + +void CircleTessellator::GenerateRoundCapLineTriangleStrip( + const TessellatedPointProc& proc, + const Point& p0, + const Point& p1, + Scalar radius) const { + auto along = p1 - p0; + auto length = along.GetLength(); + if (length < kEhCloseEnough) { + return GenerateCircleTriangleStrip(proc, p0, radius); + } + along *= radius / length; + auto across = Point(-along.y, along.x); + + for (auto& trig : trigs_) { + auto relative_across = across * trig.cos; + auto relative_along = along * trig.sin; + proc({p0 + relative_across - relative_along}); + proc({p1 + relative_across + relative_along}); + } + // The second half of the round caps should be iterated in reverse, but + // we can instead iterate forward and swap the sin/cos values as they + // should be symmetric. + for (auto& trig : trigs_) { + auto relative_across = across * trig.sin; + auto relative_along = along * trig.cos; + proc({p0 - relative_across - relative_along}); + proc({p1 - relative_across + relative_along}); + } +} + +} // namespace impeller diff --git a/impeller/entity/geometry/circle_tessellator.h b/impeller/tessellator/circle_tessellator.h similarity index 77% rename from impeller/entity/geometry/circle_tessellator.h rename to impeller/tessellator/circle_tessellator.h index 504e8d8eb2ff1..74624eb78a8c1 100644 --- a/impeller/entity/geometry/circle_tessellator.h +++ b/impeller/tessellator/circle_tessellator.h @@ -10,30 +10,11 @@ #include "flutter/impeller/geometry/matrix.h" #include "flutter/impeller/geometry/point.h" #include "flutter/impeller/geometry/scalar.h" +#include "flutter/impeller/geometry/trig.h" +#include "flutter/impeller/tessellator/tessellator.h" namespace impeller { -/// @brief A structure to store the sine and cosine of an angle. -struct Trig { - /// Construct a Trig object from a given angle in radians. - explicit Trig(Radians r) - : cos(std::cos(r.radians)), sin(std::sin(r.radians)) {} - - /// Construct a Trig object from the given cosine and sine values. - Trig(double cos, double sin) : cos(cos), sin(sin) {} - - double cos; - double sin; - - Vector2 operator*(Scalar radius) const { - return Vector2(cos * radius, sin * radius); - } - - Vector2 interpolate(Vector2 start_vector, Vector2 end_vector) { - return start_vector * cos + end_vector * sin; - } -}; - using TessellatedPointProc = std::function; /// @brief A utility class to compute the number of divisions for a circle @@ -55,17 +36,27 @@ using TessellatedPointProc = std::function; /// triangles are rendered for maximum tessellation fidelity. class CircleTessellator { public: + /// @brief The pixel tolerance used by the algorighm to determine how + /// many divisions to create for a circle. + /// + /// No point on the polygon of vertices should deviate from the + /// true circle by more than this tolerance. + static constexpr Scalar kCircleTolerance = 0.1; + /// @brief Constructs a CircleTessellator that produces enough segments /// to reasonably approximate a circle with a specified |radius| /// when viewed under the specified |transform|. - constexpr CircleTessellator(const Matrix& transform, Scalar radius) - : CircleTessellator(transform.GetMaxBasisLength() * radius) {} + CircleTessellator(std::shared_ptr& tessellator, + const Matrix& transform, + Scalar radius) + : CircleTessellator(tessellator, transform.GetMaxBasisLength() * radius) { + } ~CircleTessellator() = default; /// @brief Return the number of divisions computed by the algorithm for /// a single quarter circle. - size_t GetQuadrantDivisionCount() const { return quadrant_divisions_; } + size_t GetQuadrantDivisionCount() const { return trigs_.size() - 1; } /// @brief Return the number of vertices that will be generated to /// tessellate a full circle with a triangle strip. @@ -74,7 +65,7 @@ class CircleTessellator { /// to hold the vertices that will be produced by the /// |GenerateCircleTriangleStrip| and /// |GenerateRoundCapLineTriangleStrip| methods. - size_t GetCircleVertexCount() const { return (quadrant_divisions_ + 1) * 4; } + size_t GetCircleVertexCount() const { return trigs_.size() * 4; } /// @brief Generate the vertices for a triangle strip that covers the /// circle at a given |radius| from a given |center|, delivering @@ -101,13 +92,16 @@ class CircleTessellator { Scalar radius) const; private: - const size_t quadrant_divisions_; + const std::vector& trigs_; + std::vector temp_trigs_; /// @brief Constructs a CircleTessellator that produces enough segments /// to reasonably approximate a circle with a specified radius /// in pixels. - constexpr explicit CircleTessellator(Scalar pixel_radius) - : quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {} + explicit CircleTessellator(std::shared_ptr& tessellator, + Scalar pixel_radius) + : trigs_(GetTrigsForDivisions(tessellator, + ComputeQuadrantDivisions(pixel_radius))) {} CircleTessellator(const CircleTessellator&) = delete; @@ -127,11 +121,12 @@ class CircleTessellator { /// contain (divisions + 1) values. /// /// @return The vector of (divisions + 1) trig values. - static const std::vector& GetTrigForDivisions(size_t divisions); - - static constexpr int kMaxDivisions = 35; + const std::vector& GetTrigsForDivisions( + std::shared_ptr& tessellator, + size_t divisions); - static std::vector trigs_[kMaxDivisions + 1]; + static constexpr int kPrecomputedDivisionCount = 1024; + static int kPrecomputedDivisions[kPrecomputedDivisionCount]; }; } // namespace impeller diff --git a/impeller/tessellator/circle_tessellator_unittests.cc b/impeller/tessellator/circle_tessellator_unittests.cc new file mode 100644 index 0000000000000..81372ef5fb004 --- /dev/null +++ b/impeller/tessellator/circle_tessellator_unittests.cc @@ -0,0 +1,93 @@ +// 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 "fml/logging.h" +#include "gtest/gtest.h" + +#include "flutter/impeller/geometry/geometry_asserts.h" +#include "flutter/impeller/tessellator/circle_tessellator.h" + +namespace impeller { +namespace testing { + +TEST(CircleTessellator, DivisionVertexCounts) { + auto t = std::make_shared(); + + auto test = [&t](const Matrix& transform, Scalar radius) { + CircleTessellator tessellator(t, transform, radius); + size_t quadrant_divisions = tessellator.GetQuadrantDivisionCount(); + + EXPECT_EQ(tessellator.GetCircleVertexCount(), (quadrant_divisions + 1) * 4) + << "transform = " << transform << ", radius = " << radius; + + double angle = kPi * 0.5 / quadrant_divisions; + Point first = {radius, 0}; + Point next = {static_cast(cos(angle) * radius), + static_cast(sin(angle) * radius)}; + Point midpoint = (first + next) * 0.5; + EXPECT_GE(midpoint.GetLength(), + radius - CircleTessellator::kCircleTolerance * 1.01) + << ", transform = " << transform << ", radius = " << radius + << ", divisions = " << quadrant_divisions; + }; + + test({}, 0.0); + test({}, 0.9); + test({}, 1.0); + test({}, 1.9); + test(Matrix::MakeScale(Vector2(2.0, 2.0)), 0.95); + test({}, 2.0); + test(Matrix::MakeScale(Vector2(2.0, 2.0)), 1.0); + test({}, 11.9); + test({}, 12.0); + test({}, 35.9); + for (int i = 36; i < 10000; i += 4) { + test({}, i); + } +} + +TEST(CircleTessellator, CircleTessellationVertices) { + auto t = std::make_shared(); + + auto test = [&t](Scalar pixel_radius, Point center, Scalar radius) { + CircleTessellator tessellator(t, {}, pixel_radius); + + auto vertex_count = tessellator.GetCircleVertexCount(); + auto vertices = std::vector(); + tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + vertices.push_back(p); + }, + center, radius); + ASSERT_EQ(vertices.size(), vertex_count); + ASSERT_EQ(vertex_count % 4, 0u); + + auto quadrant_count = vertex_count / 4; + for (size_t i = 0; i < quadrant_count; i++) { + double angle = kPiOver2 * i / (quadrant_count - 1); + double rsin = sin(angle) * radius; + double rcos = cos(angle) * radius; + EXPECT_POINT_NEAR(vertices[i * 2], + Point(center.x - rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[i * 2 + 1], + Point(center.x - rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1], + Point(center.x + rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2], + Point(center.x + rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + } + }; + + test(2.0, {}, 2.0); + test(2.0, {10, 10}, 2.0); + test(1000.0, {}, 2.0); + test(2.0, {}, 1000.0); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 282b47b4829ed..3f5b5d7ebb522 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -11,6 +11,7 @@ #include "flutter/fml/macros.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" +#include "impeller/geometry/trig.h" struct TESStesselator; @@ -89,6 +90,11 @@ class Tessellator { std::unique_ptr> point_buffer_; CTessellator c_tessellator_; + // Cached data for CircleTessellator + static constexpr size_t kCachedTrigCount = 300; + std::vector precomputed_trigs_[kCachedTrigCount]; + friend class CircleTessellator; + Tessellator(const Tessellator&) = delete; Tessellator& operator=(const Tessellator&) = delete; From aeef5429d6f22890a14b4cfd0183cec222bb2543 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 29 Nov 2023 23:48:55 -0800 Subject: [PATCH 8/9] document and refine circle division computation --- impeller/tessellator/circle_tessellator.cc | 46 ++++++++++++++++++- .../circle_tessellator_unittests.cc | 5 +- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/impeller/tessellator/circle_tessellator.cc b/impeller/tessellator/circle_tessellator.cc index 16949fb530caa..35f0f924db38c 100644 --- a/impeller/tessellator/circle_tessellator.cc +++ b/impeller/tessellator/circle_tessellator.cc @@ -85,8 +85,52 @@ size_t CircleTessellator::ComputeQuadrantDivisions(Scalar pixel_radius) { if (radius_index < kPrecomputedDivisionCount) { return kPrecomputedDivisions[radius_index]; } + + // For a circle with N divisions per quadrant, the maximum deviation of + // the polgyon approximation from the true circle will be at the center + // of the base of each triangular pie slice. We can compute that distance + // by finding the midpoint of the line of the first slice and compare + // its distance from the center of the circle to the radius. We will aim + // to have the length of that bisector to be within |kCircleTolerance| + // from the radius in pixels. + // + // Each vertex will appear at an angle of: + // theta(i) = (kPi / 2) * (i / N) // for i in [0..N] + // with each point falling at: + // point(i) = r * (cos(theta), sin(theta)) + // If we consider the unit circle to simplify the calculations below then + // we need to scale the tolerance from its absolute quantity into a unit + // circle fraction: + // k = tolerance / radius + // Using this scaled tolerance below to avoid multiplying by the radius + // throughout all of the math, we have: + // first point = (1, 0) // theta(0) == 0 + // theta = kPi / 2 / N // theta(1) + // second point = (cos(theta), sin(theta)) = (c, s) + // midpoint = (first + second) * 0.5 = ((1 + c)/2, s/2) + // |midpoint| = sqrt((1 + c)*(1 + c)/4 + s*s/4) + // = sqrt((1 + c + c + c*c + s*s) / 4) + // = sqrt((1 + 2c + 1) / 4) + // = sqrt((2 + 2c) / 4) + // = sqrt((1 + c) / 2) + // = cos(theta / 2) // using half-angle cosine formula + // error = 1 - |midpoint| = 1 - cos(theta / 2) + // cos(theta/2) = 1 - error + // theta/2 = acos(1 - error) + // kPi / 2 / N / 2 = acos(1 - error) + // kPi / 4 / acos(1 - error) = N + // Since we need error <= k, we want divisions >= N, so we use: + // N = ceil(kPi / 4 / acos(1 - k)) + // + // Math is confirmed in https://math.stackexchange.com/a/4132095 + // (keeping in mind that we are computing quarter circle divisions here) + // which also points out a performance optimization that is accurate + // to within an over-estimation of 1 division would be: + // N = ceil(kPi / 4 / sqrt(2 * k)) + // Since we have precomputed the divisions for radii up to 1024, we can + // afford to be more accurate using the acos formula here for larger radii. double k = kCircleTolerance / pixel_radius; - return ceil(kPi / sqrt(2 * k) / 4); + return ceil(kPiOver4 / std::acos(1 - k)); } const std::vector& CircleTessellator::GetTrigsForDivisions( diff --git a/impeller/tessellator/circle_tessellator_unittests.cc b/impeller/tessellator/circle_tessellator_unittests.cc index 81372ef5fb004..76db15d4e0dd4 100644 --- a/impeller/tessellator/circle_tessellator_unittests.cc +++ b/impeller/tessellator/circle_tessellator_unittests.cc @@ -21,7 +21,10 @@ TEST(CircleTessellator, DivisionVertexCounts) { EXPECT_EQ(tessellator.GetCircleVertexCount(), (quadrant_divisions + 1) * 4) << "transform = " << transform << ", radius = " << radius; - double angle = kPi * 0.5 / quadrant_divisions; + // Confirm the approximation error is within the currently accepted + // |kCircleTolerance| value advertised by |CircleTessellator|. + // (With an additional 1% tolerance for floating point rounding.) + double angle = kPiOver2 / quadrant_divisions; Point first = {radius, 0}; Point next = {static_cast(cos(angle) * radius), static_cast(sin(angle) * radius)}; From 16fa52dadb490b70fd9269f556370799c17d962b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 30 Nov 2023 11:13:48 -0800 Subject: [PATCH 9/9] style feedback from review --- impeller/entity/geometry/ellipse_geometry.cc | 43 ++++++++++--------- impeller/entity/geometry/line_geometry.cc | 43 ++++++++++--------- .../entity/geometry/point_field_geometry.cc | 12 +++--- .../circle_tessellator_unittests.cc | 21 ++++----- 4 files changed, 63 insertions(+), 56 deletions(-) diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 87919780d2dbc..c0c42f1e8fdc4 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -22,21 +22,22 @@ GeometryResult EllipseGeometry::GetPositionBuffer( Scalar radius = radius_; const Point& center = center_; - std::shared_ptr t = renderer.GetTessellator(); - CircleTessellator tessellator(t, entity.GetTransform(), radius_); - size_t count = tessellator.GetCircleVertexCount(); - auto vertex_buffer = - host_buffer.Emplace(count * sizeof(VT), alignof(VT), - [&tessellator, ¢er, radius](uint8_t* buffer) { - auto vertices = reinterpret_cast(buffer); - tessellator.GenerateCircleTriangleStrip( - [&vertices](const Point& p) { // - *vertices++ = { - .position = p, - }; - }, - center, radius); - }); + std::shared_ptr tessellator = renderer.GetTessellator(); + CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), + radius_); + size_t count = circle_tessellator.GetCircleVertexCount(); + auto vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, ¢er, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + center, radius); + }); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -66,14 +67,15 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( Scalar radius = radius_; const Point& center = center_; - std::shared_ptr t = renderer.GetTessellator(); - CircleTessellator tessellator(t, entity.GetTransform(), radius_); - size_t count = tessellator.GetCircleVertexCount(); + std::shared_ptr tessellator = renderer.GetTessellator(); + CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), + radius_); + size_t count = circle_tessellator.GetCircleVertexCount(); auto vertex_buffer = host_buffer.Emplace( count * sizeof(VT), alignof(VT), - [&tessellator, &uv_transform, ¢er, radius](uint8_t* buffer) { + [&circle_tessellator, &uv_transform, ¢er, radius](uint8_t* buffer) { auto vertices = reinterpret_cast(buffer); - tessellator.GenerateCircleTriangleStrip( + circle_tessellator.GenerateCircleTriangleStrip( [&vertices, &uv_transform](const Point& p) { // *vertices++ = { .position = p, @@ -91,7 +93,6 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( .vertex_count = count, .index_type = IndexType::kNone, }, - // .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransform(), .prevent_overdraw = false, diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 7beb5af6342c0..2d2dd8317d783 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -80,21 +80,22 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, const Point& p0 = p0_; const Point& p1 = p1_; - std::shared_ptr t = renderer.GetTessellator(); - CircleTessellator tessellator(t, entity.GetTransform(), radius); - count = tessellator.GetCircleVertexCount(); - vertex_buffer = - host_buffer.Emplace(count * sizeof(VT), alignof(VT), - [&tessellator, &p0, &p1, radius](uint8_t* buffer) { - auto vertices = reinterpret_cast(buffer); - tessellator.GenerateRoundCapLineTriangleStrip( - [&vertices](const Point& p) { // - *vertices++ = { - .position = p, - }; - }, - p0, p1, radius); - }); + std::shared_ptr tessellator = renderer.GetTessellator(); + CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), + radius); + count = circle_tessellator.GetCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, &p0, &p1, radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateRoundCapLineTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + p0, p1, radius); + }); } else { Point corners[4]; if (ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { @@ -148,14 +149,16 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, const Point& p0 = p0_; const Point& p1 = p1_; - std::shared_ptr t = renderer.GetTessellator(); - CircleTessellator tessellator(t, entity.GetTransform(), radius); - count = tessellator.GetCircleVertexCount(); + std::shared_ptr tessellator = renderer.GetTessellator(); + CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), + radius); + count = circle_tessellator.GetCircleVertexCount(); vertex_buffer = host_buffer.Emplace( count * sizeof(VT), alignof(VT), - [&tessellator, &uv_transform, &p0, &p1, radius](uint8_t* buffer) { + [&circle_tessellator, &uv_transform, &p0, &p1, + radius](uint8_t* buffer) { auto vertices = reinterpret_cast(buffer); - tessellator.GenerateRoundCapLineTriangleStrip( + circle_tessellator.GenerateRoundCapLineTriangleStrip( [&vertices, &uv_transform](const Point& p) { // *vertices++ = { .position = p, diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index 1a25d9957e396..dfc97059dac20 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -83,19 +83,21 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, VertexBufferBuilder vtx_builder; if (round_) { - std::shared_ptr t = renderer.GetTessellator(); - CircleTessellator tessellator(t, entity.GetTransform(), radius_); + std::shared_ptr tessellator = renderer.GetTessellator(); + CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), + radius_); // Get triangulation relative to {0, 0} so we can translate it to each // point in turn. std::vector circle_vertices; - circle_vertices.reserve(tessellator.GetCircleVertexCount()); - tessellator.GenerateCircleTriangleStrip( + circle_vertices.reserve(circle_tessellator.GetCircleVertexCount()); + circle_tessellator.GenerateCircleTriangleStrip( [&circle_vertices](const Point& p) { // circle_vertices.push_back(p); }, {}, radius); - FML_DCHECK(circle_vertices.size() == tessellator.GetCircleVertexCount()); + FML_DCHECK(circle_vertices.size() == + circle_tessellator.GetCircleVertexCount()); vtx_builder.Reserve((circle_vertices.size() + 2) * points_.size() - 2); for (auto& center : points_) { diff --git a/impeller/tessellator/circle_tessellator_unittests.cc b/impeller/tessellator/circle_tessellator_unittests.cc index 76db15d4e0dd4..a84735f35b21d 100644 --- a/impeller/tessellator/circle_tessellator_unittests.cc +++ b/impeller/tessellator/circle_tessellator_unittests.cc @@ -12,13 +12,14 @@ namespace impeller { namespace testing { TEST(CircleTessellator, DivisionVertexCounts) { - auto t = std::make_shared(); + auto tessellator = std::make_shared(); - auto test = [&t](const Matrix& transform, Scalar radius) { - CircleTessellator tessellator(t, transform, radius); - size_t quadrant_divisions = tessellator.GetQuadrantDivisionCount(); + auto test = [&tessellator](const Matrix& transform, Scalar radius) { + CircleTessellator circle_tessellator(tessellator, transform, radius); + size_t quadrant_divisions = circle_tessellator.GetQuadrantDivisionCount(); - EXPECT_EQ(tessellator.GetCircleVertexCount(), (quadrant_divisions + 1) * 4) + EXPECT_EQ(circle_tessellator.GetCircleVertexCount(), + (quadrant_divisions + 1) * 4) << "transform = " << transform << ", radius = " << radius; // Confirm the approximation error is within the currently accepted @@ -51,14 +52,14 @@ TEST(CircleTessellator, DivisionVertexCounts) { } TEST(CircleTessellator, CircleTessellationVertices) { - auto t = std::make_shared(); + auto tessellator = std::make_shared(); - auto test = [&t](Scalar pixel_radius, Point center, Scalar radius) { - CircleTessellator tessellator(t, {}, pixel_radius); + auto test = [&tessellator](Scalar pixel_radius, Point center, Scalar radius) { + CircleTessellator circle_tessellator(tessellator, {}, pixel_radius); - auto vertex_count = tessellator.GetCircleVertexCount(); + auto vertex_count = circle_tessellator.GetCircleVertexCount(); auto vertices = std::vector(); - tessellator.GenerateCircleTriangleStrip( + circle_tessellator.GenerateCircleTriangleStrip( [&vertices](const Point& p) { // vertices.push_back(p); },