| Flutter Impeller |

point_field_geometry.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "impeller/entity/geometry/point_field_geometry.h"
6
7 #include "impeller/renderer/command_buffer.h"
8 #include "impeller/renderer/compute_command.h"
9
10 namespace impeller {
11
12 PointFieldGeometry::PointFieldGeometry(std::vector<Point> points,
13 Scalar radius,
14 bool round)
15 : points_(std::move(points)), radius_(radius), round_(round) {}
16
17 GeometryResult PointFieldGeometry::GetPositionBuffer(
18 const ContentContext& renderer,
19 const Entity& entity,
20 RenderPass& pass) const {
21 if (CanUseCompute(renderer)) {
22 return GetPositionBufferGPU(renderer, entity, pass);
23 }
24 auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass);
25 if (!vtx_builder.has_value()) {
26 return {};
27 }
28
29 auto& host_buffer = pass.GetTransientsBuffer();
30 return {
31 .type = PrimitiveType::kTriangleStrip,
32 .vertex_buffer = vtx_builder->CreateVertexBuffer(host_buffer),
33 .transform = pass.GetOrthographicTransform() * entity.GetTransform(),
34 .prevent_overdraw = false,
35 };
36 }
37
38 GeometryResult PointFieldGeometry::GetPositionUVBuffer(
39 Rect texture_coverage,
40 Matrix effect_transform,
41 const ContentContext& renderer,
42 const Entity& entity,
43 RenderPass& pass) const {
44 if (CanUseCompute(renderer)) {
45 return GetPositionBufferGPU(renderer, entity, pass, texture_coverage,
46 effect_transform);
47 }
48
49 auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass);
50 if (!vtx_builder.has_value()) {
51 return {};
52 }
53 auto uv_vtx_builder =
54 ComputeUVGeometryCPU(vtx_builder.value(), {0, 0},
55 texture_coverage.GetSize(), effect_transform);
56
57 auto& host_buffer = pass.GetTransientsBuffer();
58 return {
59 .type = PrimitiveType::kTriangleStrip,
60 .vertex_buffer = uv_vtx_builder.CreateVertexBuffer(host_buffer),
61 .transform = pass.GetOrthographicTransform() * entity.GetTransform(),
62 .prevent_overdraw = false,
63 };
64 }
65
66 std::optional<VertexBufferBuilder<SolidFillVertexShader::PerVertexData>>
67 PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer,
68 const Entity& entity,
69 RenderPass& pass) const {
70 if (radius_ < 0.0) {
71 return std::nullopt;
72 }
73 auto transform = entity.GetTransform();
74 auto determinant = transform.GetDeterminant();
75 if (determinant == 0) {
76 return std::nullopt;
77 }
78
79 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
80 Scalar radius = std::max(radius_, min_size);
81
82 VertexBufferBuilder<SolidFillVertexShader::PerVertexData> vtx_builder;
83
84 if (round_) {
85 // Get triangulation relative to {0, 0} so we can translate it to each
86 // point in turn.
87 auto generator =
88 renderer.GetTessellator()->FilledCircle(transform, {}, radius);
89 FML_DCHECK(generator.GetTriangleType() == PrimitiveType::kTriangleStrip);
90 std::vector<Point> circle_vertices;
91 circle_vertices.reserve(generator.GetVertexCount());
92 generator.GenerateVertices([&circle_vertices](const Point& p) { //
93 circle_vertices.push_back(p);
94 });
95 FML_DCHECK(circle_vertices.size() == generator.GetVertexCount());
96
97 vtx_builder.Reserve((circle_vertices.size() + 2) * points_.size() - 2);
98 for (auto& center : points_) {
99 if (vtx_builder.HasVertices()) {
100 vtx_builder.AppendVertex(vtx_builder.Last());
101 vtx_builder.AppendVertex({center + circle_vertices[0]});
102 }
103
104 for (auto& vertex : circle_vertices) {
105 vtx_builder.AppendVertex({center + vertex});
106 }
107 }
108 } else {
109 vtx_builder.Reserve(6 * points_.size() - 2);
110 for (auto& point : points_) {
111 auto first = Point(point.x - radius, point.y - radius);
112
113 if (vtx_builder.HasVertices()) {
114 vtx_builder.AppendVertex(vtx_builder.Last());
115 vtx_builder.AppendVertex({first});
116 }
117
118 // Z pattern from UL -> UR -> LL -> LR
119 vtx_builder.AppendVertex({first});
120 vtx_builder.AppendVertex({{point.x + radius, point.y - radius}});
121 vtx_builder.AppendVertex({{point.x - radius, point.y + radius}});
122 vtx_builder.AppendVertex({{point.x + radius, point.y + radius}});
123 }
124 }
125
126 return vtx_builder;
127 }
128
129 GeometryResult PointFieldGeometry::GetPositionBufferGPU(
130 const ContentContext& renderer,
131 const Entity& entity,
132 RenderPass& pass,
133 std::optional<Rect> texture_coverage,
134 std::optional<Matrix> effect_transform) const {
135 FML_DCHECK(renderer.GetDeviceCapabilities().SupportsCompute());
136 if (radius_ < 0.0) {
137 return {};
138 }
139 auto determinant = entity.GetTransform().GetDeterminant();
140 if (determinant == 0) {
141 return {};
142 }
143
144 Scalar min_size = 1.0f / sqrt(std::abs(determinant));
145 Scalar radius = std::max(radius_, min_size);
146
147 auto vertices_per_geom = ComputeCircleDivisions(
148 entity.GetTransform().GetMaxBasisLength() * radius, round_);
149
150 auto points_per_circle = 3 + (vertices_per_geom - 3) * 3;
151 auto total = points_per_circle * points_.size();
152
153 auto cmd_buffer = renderer.GetContext()->CreateCommandBuffer();
154 auto compute_pass = cmd_buffer->CreateComputePass();
155 auto& host_buffer = compute_pass->GetTransientsBuffer();
156
157 auto points_data =
158 host_buffer.Emplace(points_.data(), points_.size() * sizeof(Point),
159 DefaultUniformAlignment());
160
161 DeviceBufferDescriptor buffer_desc;
162 buffer_desc.size = total * sizeof(Point);
163 buffer_desc.storage_mode = StorageMode::kDevicePrivate;
164
165 auto geometry_buffer = renderer.GetContext()
166 ->GetResourceAllocator()
167 ->CreateBuffer(buffer_desc)
168 ->AsBufferView();
169
170 BufferView output;
171 {
172 using PS = PointsComputeShader;
173 ComputeCommand cmd;
174 DEBUG_COMMAND_INFO(cmd, "Points Geometry");
175 cmd.pipeline = renderer.GetPointComputePipeline();
176
177 PS::FrameInfo frame_info;
178 frame_info.count = points_.size();
179 frame_info.radius = round_ ? radius : radius * kSqrt2;
180 frame_info.radian_start = round_ ? 0.0f : kPiOver4;
181 frame_info.radian_step = k2Pi / vertices_per_geom;
182 frame_info.points_per_circle = points_per_circle;
183 frame_info.divisions_per_circle = vertices_per_geom;
184
185 PS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
186 PS::BindGeometryData(cmd, geometry_buffer);
187 PS::BindPointData(cmd, points_data);
188
189 if (!compute_pass->AddCommand(std::move(cmd))) {
190 return {};
191 }
192 output = geometry_buffer;
193 }
194
195 if (texture_coverage.has_value() && effect_transform.has_value()) {
196 DeviceBufferDescriptor buffer_desc;
197 buffer_desc.size = total * sizeof(Vector4);
198 buffer_desc.storage_mode = StorageMode::kDevicePrivate;
199
200 auto geometry_uv_buffer = renderer.GetContext()
201 ->GetResourceAllocator()
202 ->CreateBuffer(buffer_desc)
203 ->AsBufferView();
204
205 using UV = UvComputeShader;
206
207 ComputeCommand cmd;
208 DEBUG_COMMAND_INFO(cmd, "UV Geometry");
209 cmd.pipeline = renderer.GetUvComputePipeline();
210
211 UV::FrameInfo frame_info;
212 frame_info.count = total;
213 frame_info.effect_transform = effect_transform.value();
214 frame_info.texture_origin = {0, 0};
215 frame_info.texture_size = Vector2(texture_coverage.value().GetSize());
216
217 UV::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
218 UV::BindGeometryData(cmd, geometry_buffer);
219 UV::BindGeometryUVData(cmd, geometry_uv_buffer);
220
221 if (!compute_pass->AddCommand(std::move(cmd))) {
222 return {};
223 }
224 output = geometry_uv_buffer;
225 }
226
227 compute_pass->SetGridSize(ISize(total, 1));
228 compute_pass->SetThreadGroupSize(ISize(total, 1));
229
230 if (!compute_pass->EncodeCommands() || !cmd_buffer->SubmitCommands()) {
231 return {};
232 }
233
234 return {
235 .type = PrimitiveType::kTriangle,
236 .vertex_buffer = {.vertex_buffer = output,
237 .vertex_count = total,
238 .index_type = IndexType::kNone},
239 .transform = pass.GetOrthographicTransform() * entity.GetTransform(),
240 .prevent_overdraw = false,
241 };
242 }
243
244 /// @brief Compute the number of vertices to divide each circle into.
245 ///
246 /// @return the number of vertices.
247 size_t PointFieldGeometry::ComputeCircleDivisions(Scalar scaled_radius,
248 bool round) {
249 if (!round) {
250 return 4;
251 }
252
253 // Note: these values are approximated based on the values returned from
254 // the decomposition of 4 cubics performed by Path::CreatePolyline.
255 if (scaled_radius < 1.0) {
256 return 4;
257 }
258 if (scaled_radius < 2.0) {
259 return 8;
260 }
261 if (scaled_radius < 12.0) {
262 return 24;
263 }
264 if (scaled_radius < 22.0) {
265 return 34;
266 }
267 return std::min(scaled_radius, 140.0f);
268 }
269
270 // |Geometry|
271 GeometryVertexType PointFieldGeometry::GetVertexType() const {
272 return GeometryVertexType::kPosition;
273 }
274
275 // Compute is disabled for Vulkan because the barriers are incorrect, see
276 // also: https://github.com/flutter/flutter/issues/140798 .
277 bool PointFieldGeometry::CanUseCompute(const ContentContext& renderer) {
278 return renderer.GetDeviceCapabilities().SupportsCompute() &&
279 renderer.GetContext()->GetBackendType() ==
280 Context::BackendType::kMetal;
281 }
282
283 // |Geometry|
284 std::optional<Rect> PointFieldGeometry::GetCoverage(
285 const Matrix& transform) const {
286 if (points_.size() > 0) {
287 // Doesn't use MakePointBounds as this isn't resilient to points that
288 // all lie along the same axis.
289 auto first = points_.begin();
290 auto last = points_.end();
291 auto left = first->x;
292 auto top = first->y;
293 auto right = first->x;
294 auto bottom = first->y;
295 for (auto it = first + 1; it < last; ++it) {
296 left = std::min(left, it->x);
297 top = std::min(top, it->y);
298 right = std::max(right, it->x);
299 bottom = std::max(bottom, it->y);
300 }
301 auto coverage = Rect::MakeLTRB(left - radius_, top - radius_,
302 right + radius_, bottom + radius_);
303 return coverage.TransformBounds(transform);
304 }
305 return std::nullopt;
306 }
307
308 } // namespace impeller
DEBUG_COMMAND_INFO
#define DEBUG_COMMAND_INFO(obj, arg)
Definition: command.h:28
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::DefaultUniformAlignment
constexpr size_t DefaultUniformAlignment()
Definition: platform.h:15
impeller::kSqrt2
constexpr float kSqrt2
Definition: constants.h:47
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:49
point_field_geometry.h
impeller::PointFieldGeometry::PointFieldGeometry
PointFieldGeometry(std::vector< Point > points, Scalar radius, bool round)
Definition: point_field_geometry.cc:12
impeller::Vector2
Point Vector2
Definition: point.h:312
impeller::RenderPass::GetOrthographicTransform
const Matrix & GetOrthographicTransform() const
Definition: render_pass.cc:51
impeller::ComputeUVGeometryCPU
VertexBufferBuilder< TextureFillVertexShader::PerVertexData > ComputeUVGeometryCPU(VertexBufferBuilder< SolidFillVertexShader::PerVertexData > &input, Point texture_origin, Size texture_coverage, Matrix effect_transform)
Compute UV geometry for a VBB that contains only position geometry.
Definition: geometry.cc:91
impeller::GeometryVertexType
GeometryVertexType
Definition: geometry.h:34
impeller::PrimitiveType::kTriangle
@ kTriangle
impeller::Entity
Definition: entity.h:21
impeller::PrimitiveType::kTriangleStrip
@ kTriangleStrip
impeller::Point
TPoint< Scalar > Point
Definition: point.h:308
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:29
impeller::ContentContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: content_context.cc:479
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::PointFieldGeometry::CanUseCompute
static bool CanUseCompute(const ContentContext &renderer)
If the platform can use compute safely.
Definition: point_field_geometry.cc:277
impeller::GeometryResult
Definition: geometry.h:20
impeller::IndexType::kNone
@ kNone
Does not use the index buffer.
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:488
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
impeller::RenderPass
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:29
command_buffer.h
impeller::ContentContext::GetDeviceCapabilities
const Capabilities & GetDeviceCapabilities() const
Definition: content_context.cc:483
impeller::Context::BackendType::kMetal
@ kMetal
std
Definition: comparable.h:95
impeller::PointFieldGeometry::ComputeCircleDivisions
static size_t ComputeCircleDivisions(Scalar scaled_radius, bool round)
Compute the number of vertices to divide each circle into.
Definition: point_field_geometry.cc:247
compute_command.h
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:27
impeller
Definition: aiks_context.cc:10
impeller::kPosition
@ kPosition
Definition: geometry.h:35
impeller::ContentContext
Definition: content_context.h:332
impeller::kPiOver4
constexpr float kPiOver4
Definition: constants.h:35
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::RenderPass::GetTransientsBuffer
HostBuffer & GetTransientsBuffer()
Definition: render_pass.cc:55
impeller::Capabilities::SupportsCompute
virtual bool SupportsCompute() const =0
Whether the context backend supports ComputePass.