blob: 534ee53bf92a07b2e218234f67ce0d3e2ced0a03 [file] [log] [blame]
// 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/point_field_geometry.h"
#include "impeller/renderer/command_buffer.h"
namespace impeller {
PointFieldGeometry::PointFieldGeometry(std::vector<Point> points,
Scalar radius,
bool round)
: points_(std::move(points)), radius_(radius), round_(round) {}
GeometryResult PointFieldGeometry::GetPositionBuffer(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (renderer.GetDeviceCapabilities().SupportsCompute()) {
return GetPositionBufferGPU(renderer, entity, pass);
}
auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass);
if (!vtx_builder.has_value()) {
return {};
}
auto& host_buffer = renderer.GetTransientsBuffer();
return {
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer = vtx_builder->CreateVertexBuffer(host_buffer),
.transform = pass.GetOrthographicTransform() * entity.GetTransform(),
};
}
GeometryResult PointFieldGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (renderer.GetDeviceCapabilities().SupportsCompute()) {
return GetPositionBufferGPU(renderer, entity, pass, texture_coverage,
effect_transform);
}
auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass);
if (!vtx_builder.has_value()) {
return {};
}
auto uv_vtx_builder =
ComputeUVGeometryCPU(vtx_builder.value(), {0, 0},
texture_coverage.GetSize(), effect_transform);
auto& host_buffer = renderer.GetTransientsBuffer();
return {
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer = uv_vtx_builder.CreateVertexBuffer(host_buffer),
.transform = pass.GetOrthographicTransform() * entity.GetTransform(),
};
}
std::optional<VertexBufferBuilder<SolidFillVertexShader::PerVertexData>>
PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (radius_ < 0.0) {
return std::nullopt;
}
auto transform = entity.GetTransform();
auto determinant = transform.GetDeterminant();
if (determinant == 0) {
return std::nullopt;
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar radius = std::max(radius_, min_size);
VertexBufferBuilder<SolidFillVertexShader::PerVertexData> vtx_builder;
if (round_) {
// Get triangulation relative to {0, 0} so we can translate it to each
// point in turn.
auto generator =
renderer.GetTessellator()->FilledCircle(transform, {}, radius);
FML_DCHECK(generator.GetTriangleType() == PrimitiveType::kTriangleStrip);
std::vector<Point> circle_vertices;
circle_vertices.reserve(generator.GetVertexCount());
generator.GenerateVertices([&circle_vertices](const Point& p) { //
circle_vertices.push_back(p);
});
FML_DCHECK(circle_vertices.size() == generator.GetVertexCount());
vtx_builder.Reserve((circle_vertices.size() + 2) * points_.size() - 2);
for (auto& center : points_) {
if (vtx_builder.HasVertices()) {
vtx_builder.AppendVertex(vtx_builder.Last());
vtx_builder.AppendVertex({center + circle_vertices[0]});
}
for (auto& vertex : circle_vertices) {
vtx_builder.AppendVertex({center + vertex});
}
}
} else {
vtx_builder.Reserve(6 * points_.size() - 2);
for (auto& point : points_) {
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({first});
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;
}
GeometryResult PointFieldGeometry::GetPositionBufferGPU(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
std::optional<Rect> texture_coverage,
std::optional<Matrix> effect_transform) const {
FML_DCHECK(renderer.GetDeviceCapabilities().SupportsCompute());
if (radius_ < 0.0) {
return {};
}
Scalar determinant = entity.GetTransform().GetDeterminant();
if (determinant == 0) {
return {};
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar radius = std::max(radius_, min_size);
size_t vertices_per_geom = ComputeCircleDivisions(
entity.GetTransform().GetMaxBasisLength() * radius, round_);
size_t points_per_circle = 3 + (vertices_per_geom - 3) * 3;
size_t total = points_per_circle * points_.size();
std::shared_ptr<CommandBuffer> cmd_buffer =
renderer.GetContext()->CreateCommandBuffer();
std::shared_ptr<ComputePass> compute_pass = cmd_buffer->CreateComputePass();
HostBuffer& host_buffer = renderer.GetTransientsBuffer();
BufferView points_data =
host_buffer.Emplace(points_.data(), points_.size() * sizeof(Point),
DefaultUniformAlignment());
BufferView geometry_buffer =
host_buffer.Emplace(nullptr, total * sizeof(Point), alignof(Point));
BufferView output;
{
using PS = PointsComputeShader;
compute_pass->SetPipeline(renderer.GetPointComputePipeline());
compute_pass->SetCommandLabel("Points Geometry");
PS::FrameInfo frame_info;
frame_info.count = points_.size();
frame_info.radius = round_ ? radius : radius * kSqrt2;
frame_info.radian_start = round_ ? 0.0f : kPiOver4;
frame_info.radian_step = k2Pi / vertices_per_geom;
frame_info.points_per_circle = points_per_circle;
frame_info.divisions_per_circle = vertices_per_geom;
PS::BindFrameInfo(*compute_pass, host_buffer.EmplaceUniform(frame_info));
PS::BindGeometryData(*compute_pass, geometry_buffer);
PS::BindPointData(*compute_pass, points_data);
if (!compute_pass->Compute(ISize(total, 1)).ok()) {
return {};
}
output = geometry_buffer;
}
if (texture_coverage.has_value() && effect_transform.has_value()) {
BufferView geometry_uv_buffer =
host_buffer.Emplace(nullptr, total * sizeof(Vector4), alignof(Vector4));
using UV = UvComputeShader;
compute_pass->AddBufferMemoryBarrier();
compute_pass->SetCommandLabel("UV Geometry");
compute_pass->SetPipeline(renderer.GetUvComputePipeline());
UV::FrameInfo frame_info;
frame_info.count = total;
frame_info.effect_transform = effect_transform.value();
frame_info.texture_origin = {0, 0};
frame_info.texture_size = Vector2(texture_coverage.value().GetSize());
UV::BindFrameInfo(*compute_pass, host_buffer.EmplaceUniform(frame_info));
UV::BindGeometryData(*compute_pass, geometry_buffer);
UV::BindGeometryUVData(*compute_pass, geometry_uv_buffer);
if (!compute_pass->Compute(ISize(total, 1)).ok()) {
return {};
}
output = geometry_uv_buffer;
}
if (!compute_pass->EncodeCommands()) {
return {};
}
renderer.RecordCommandBuffer(std::move(cmd_buffer));
return {
.type = PrimitiveType::kTriangle,
.vertex_buffer = {.vertex_buffer = std::move(output),
.vertex_count = total,
.index_type = IndexType::kNone},
.transform = pass.GetOrthographicTransform() * entity.GetTransform(),
};
}
/// @brief Compute the number of vertices to divide each circle into.
///
/// @return the number of vertices.
size_t PointFieldGeometry::ComputeCircleDivisions(Scalar scaled_radius,
bool round) {
if (!round) {
return 4;
}
// Note: these values are approximated based on the values returned from
// the decomposition of 4 cubics performed by Path::CreatePolyline.
if (scaled_radius < 1.0) {
return 4;
}
if (scaled_radius < 2.0) {
return 8;
}
if (scaled_radius < 12.0) {
return 24;
}
if (scaled_radius < 22.0) {
return 34;
}
return std::min(scaled_radius, 140.0f);
}
// |Geometry|
GeometryVertexType PointFieldGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}
// |Geometry|
std::optional<Rect> PointFieldGeometry::GetCoverage(
const Matrix& transform) const {
if (points_.size() > 0) {
// Doesn't use MakePointBounds as this isn't resilient to points that
// all lie along the same axis.
auto first = points_.begin();
auto last = points_.end();
auto left = first->x;
auto top = first->y;
auto right = first->x;
auto bottom = first->y;
for (auto it = first + 1; it < last; ++it) {
left = std::min(left, it->x);
top = std::min(top, it->y);
right = std::max(right, it->x);
bottom = std::max(bottom, it->y);
}
auto coverage = Rect::MakeLTRB(left - radius_, top - radius_,
right + radius_, bottom + radius_);
return coverage.TransformBounds(transform);
}
return std::nullopt;
}
} // namespace impeller