blob: 0fc99ecc9a32f8ea9ac82a036625c05fbddaefea [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.h"
#include "impeller/core/device_buffer.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/points.comp.h"
#include "impeller/entity/position_color.vert.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/geometry/matrix.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
/// Given a convex polyline, create a triangle fan structure.
std::pair<std::vector<Point>, std::vector<uint16_t>> TessellateConvex(
Path::Polyline polyline) {
std::vector<Point> output;
std::vector<uint16_t> indices;
for (auto j = 0u; j < polyline.contours.size(); j++) {
auto [start, end] = polyline.GetContourPointBounds(j);
auto center = polyline.points[start];
// Some polygons will not self close and an additional triangle
// must be inserted, others will self close and we need to avoid
// inserting an extra triangle.
if (polyline.points[end - 1] == polyline.points[start]) {
end--;
}
output.emplace_back(center);
output.emplace_back(polyline.points[start + 1]);
for (auto i = start + 2; i < end; i++) {
const auto& point_b = polyline.points[i];
output.emplace_back(point_b);
indices.emplace_back(0);
indices.emplace_back(i - 1);
indices.emplace_back(i);
}
}
return std::make_pair(output, indices);
}
Geometry::Geometry() = default;
Geometry::~Geometry() = default;
GeometryResult Geometry::GetPositionUVBuffer(Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
return {};
}
// static
std::unique_ptr<Geometry> Geometry::MakeFillPath(const Path& path) {
return std::make_unique<FillPathGeometry>(path);
}
// static
std::unique_ptr<Geometry> Geometry::MakePointField(std::vector<Point> points,
Scalar radius,
bool round) {
return std::make_unique<PointFieldGeometry>(std::move(points), radius, round);
}
// static
std::unique_ptr<Geometry> Geometry::MakeStrokePath(const Path& path,
Scalar stroke_width,
Scalar miter_limit,
Cap stroke_cap,
Join stroke_join) {
// Skia behaves like this.
if (miter_limit < 0) {
miter_limit = 4.0;
}
return std::make_unique<StrokePathGeometry>(path, stroke_width, miter_limit,
stroke_cap, stroke_join);
}
std::unique_ptr<Geometry> Geometry::MakeCover() {
return std::make_unique<CoverGeometry>();
}
std::unique_ptr<Geometry> Geometry::MakeRect(Rect rect) {
return std::make_unique<RectGeometry>(rect);
}
static GeometryResult ComputeUVGeometryForRect(Rect source_rect,
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
auto& host_buffer = pass.GetTransientsBuffer();
std::vector<Point> data(8);
auto points = source_rect.GetPoints();
for (auto i = 0u, j = 0u; i < 8; i += 2, j++) {
data[i] = points[j];
data[i + 1] = effect_transform * ((points[j] - texture_coverage.origin) /
texture_coverage.size);
}
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
{
.vertex_buffer = host_buffer.Emplace(
data.data(), 16 * sizeof(float), alignof(float)),
.vertex_count = 4,
.index_type = IndexType::kNone,
},
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
/////// Path Geometry ///////
FillPathGeometry::FillPathGeometry(const Path& path) : path_(path) {}
FillPathGeometry::~FillPathGeometry() = default;
GeometryResult FillPathGeometry::GetPositionBuffer(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
auto& host_buffer = pass.GetTransientsBuffer();
VertexBuffer vertex_buffer;
if (path_.GetFillType() == FillType::kNonZero && //
path_.IsConvex()) {
auto [points, indices] = TessellateConvex(
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()));
vertex_buffer.vertex_buffer = host_buffer.Emplace(
points.data(), points.size() * sizeof(Point), alignof(Point));
vertex_buffer.index_buffer = host_buffer.Emplace(
indices.data(), indices.size() * sizeof(uint16_t), alignof(uint16_t));
vertex_buffer.vertex_count = indices.size();
vertex_buffer.index_type = IndexType::k16bit;
return GeometryResult{
.type = PrimitiveType::kTriangle,
.vertex_buffer = vertex_buffer,
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
auto tesselation_result = renderer.GetTessellator()->Tessellate(
path_.GetFillType(),
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
[&vertex_buffer, &host_buffer](
const float* vertices, size_t vertices_count, const uint16_t* indices,
size_t indices_count) {
vertex_buffer.vertex_buffer = host_buffer.Emplace(
vertices, vertices_count * sizeof(float), alignof(float));
vertex_buffer.index_buffer = host_buffer.Emplace(
indices, indices_count * sizeof(uint16_t), alignof(uint16_t));
vertex_buffer.vertex_count = indices_count;
vertex_buffer.index_type = IndexType::k16bit;
return true;
});
if (tesselation_result != Tessellator::Result::kSuccess) {
return {};
}
return GeometryResult{
.type = PrimitiveType::kTriangle,
.vertex_buffer = vertex_buffer,
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
// |Geometry|
GeometryResult FillPathGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
using VS = TextureFillVertexShader;
if (path_.GetFillType() == FillType::kNonZero && //
path_.IsConvex()) {
auto [points, indices] = TessellateConvex(
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()));
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
vertex_builder.Reserve(points.size());
vertex_builder.ReserveIndices(indices.size());
for (auto i = 0u; i < points.size(); i++) {
VS::PerVertexData data;
data.position = points[i];
auto coverage_coords =
((points[i] - texture_coverage.origin) / texture_coverage.size) /
texture_coverage.size;
data.texture_coords = effect_transform * coverage_coords;
vertex_builder.AppendVertex(data);
}
for (auto i = 0u; i < indices.size(); i++) {
vertex_builder.AppendIndex(indices[i]);
}
return GeometryResult{
.type = PrimitiveType::kTriangle,
.vertex_buffer =
vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer()),
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
auto tesselation_result = renderer.GetTessellator()->Tessellate(
path_.GetFillType(),
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
[&vertex_builder, &texture_coverage, &effect_transform](
const float* vertices, size_t vertices_count, const uint16_t* indices,
size_t indices_count) {
for (auto i = 0u; i < vertices_count; i += 2) {
VS::PerVertexData data;
Point vtx = {vertices[i], vertices[i + 1]};
data.position = vtx;
auto coverage_coords =
((vtx - texture_coverage.origin) / texture_coverage.size) /
texture_coverage.size;
data.texture_coords = effect_transform * coverage_coords;
vertex_builder.AppendVertex(data);
}
FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count / 2);
for (auto i = 0u; i < indices_count; i++) {
vertex_builder.AppendIndex(indices[i]);
}
return true;
});
if (tesselation_result != Tessellator::Result::kSuccess) {
return {};
}
return GeometryResult{
.type = PrimitiveType::kTriangle,
.vertex_buffer =
vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer()),
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
GeometryVertexType FillPathGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}
std::optional<Rect> FillPathGeometry::GetCoverage(
const Matrix& transform) const {
return path_.GetTransformedBoundingBox(transform);
}
///// Stroke Geometry //////
StrokePathGeometry::StrokePathGeometry(const Path& path,
Scalar stroke_width,
Scalar miter_limit,
Cap stroke_cap,
Join stroke_join)
: path_(path),
stroke_width_(stroke_width),
miter_limit_(miter_limit),
stroke_cap_(stroke_cap),
stroke_join_(stroke_join) {}
StrokePathGeometry::~StrokePathGeometry() = default;
Scalar StrokePathGeometry::GetStrokeWidth() const {
return stroke_width_;
}
Scalar StrokePathGeometry::GetMiterLimit() const {
return miter_limit_;
}
Cap StrokePathGeometry::GetStrokeCap() const {
return stroke_cap_;
}
Join StrokePathGeometry::GetStrokeJoin() const {
return stroke_join_;
}
// static
Scalar StrokePathGeometry::CreateBevelAndGetDirection(
VertexBufferBuilder<SolidFillVertexShader::PerVertexData>& vtx_builder,
const Point& position,
const Point& start_offset,
const Point& end_offset) {
SolidFillVertexShader::PerVertexData vtx;
vtx.position = position;
vtx_builder.AppendVertex(vtx);
Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1;
vtx.position = position + start_offset * dir;
vtx_builder.AppendVertex(vtx);
vtx.position = position + end_offset * dir;
vtx_builder.AppendVertex(vtx);
return dir;
}
// static
StrokePathGeometry::JoinProc StrokePathGeometry::GetJoinProc(Join stroke_join) {
using VS = SolidFillVertexShader;
StrokePathGeometry::JoinProc join_proc;
switch (stroke_join) {
case Join::kBevel:
join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
Scalar scale) {
CreateBevelAndGetDirection(vtx_builder, position, start_offset,
end_offset);
};
break;
case Join::kMiter:
join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
Scalar scale) {
Point start_normal = start_offset.Normalize();
Point end_normal = end_offset.Normalize();
// 1 for no joint (straight line), 0 for max joint (180 degrees).
Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
if (ScalarNearlyEqual(alignment, 1)) {
return;
}
Scalar dir = CreateBevelAndGetDirection(vtx_builder, position,
start_offset, end_offset);
Point miter_point = (start_offset + end_offset) / 2 / alignment;
if (miter_point.GetDistanceSquared({0, 0}) >
miter_limit * miter_limit) {
return; // Convert to bevel when we exceed the miter limit.
}
// Outer miter point.
VS::PerVertexData vtx;
vtx.position = position + miter_point * dir;
vtx_builder.AppendVertex(vtx);
};
break;
case Join::kRound:
join_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_offset,
const Point& end_offset, Scalar miter_limit,
Scalar scale) {
Point start_normal = start_offset.Normalize();
Point end_normal = end_offset.Normalize();
// 0 for no joint (straight line), 1 for max joint (180 degrees).
Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
if (ScalarNearlyEqual(alignment, 0)) {
return;
}
Scalar dir = CreateBevelAndGetDirection(vtx_builder, position,
start_offset, end_offset);
Point middle =
(start_offset + end_offset).Normalize() * start_offset.GetLength();
Point middle_normal = middle.Normalize();
Point middle_handle = middle + Point(-middle.y, middle.x) *
PathBuilder::kArcApproximationMagic *
alignment * dir;
Point start_handle =
start_offset + Point(start_offset.y, -start_offset.x) *
PathBuilder::kArcApproximationMagic * alignment *
dir;
auto arc_points = CubicPathComponent(start_offset, start_handle,
middle_handle, middle)
.CreatePolyline(scale);
VS::PerVertexData vtx;
for (const auto& point : arc_points) {
vtx.position = position + point * dir;
vtx_builder.AppendVertex(vtx);
vtx.position = position + (-point * dir).Reflect(middle_normal);
vtx_builder.AppendVertex(vtx);
}
};
break;
}
return join_proc;
}
// static
StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) {
using VS = SolidFillVertexShader;
StrokePathGeometry::CapProc cap_proc;
switch (stroke_cap) {
case Cap::kButt:
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset, Scalar scale,
bool reverse) {
Point orientation = offset * (reverse ? -1 : 1);
VS::PerVertexData vtx;
vtx.position = position + orientation;
vtx_builder.AppendVertex(vtx);
vtx.position = position - orientation;
vtx_builder.AppendVertex(vtx);
};
break;
case Cap::kRound:
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset, Scalar scale,
bool reverse) {
Point orientation = offset * (reverse ? -1 : 1);
VS::PerVertexData vtx;
Point forward(offset.y, -offset.x);
Point forward_normal = forward.Normalize();
CubicPathComponent arc;
if (reverse) {
arc = CubicPathComponent(
forward,
forward + orientation * PathBuilder::kArcApproximationMagic,
orientation + forward * PathBuilder::kArcApproximationMagic,
orientation);
} else {
arc = CubicPathComponent(
orientation,
orientation + forward * PathBuilder::kArcApproximationMagic,
forward + orientation * PathBuilder::kArcApproximationMagic,
forward);
}
vtx.position = position + orientation;
vtx_builder.AppendVertex(vtx);
vtx.position = position - orientation;
vtx_builder.AppendVertex(vtx);
for (const auto& point : arc.CreatePolyline(scale)) {
vtx.position = position + point;
vtx_builder.AppendVertex(vtx);
vtx.position = position + (-point).Reflect(forward_normal);
vtx_builder.AppendVertex(vtx);
}
};
break;
case Cap::kSquare:
cap_proc = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& offset, Scalar scale,
bool reverse) {
Point orientation = offset * (reverse ? -1 : 1);
VS::PerVertexData vtx;
Point forward(offset.y, -offset.x);
vtx.position = position + orientation;
vtx_builder.AppendVertex(vtx);
vtx.position = position - orientation;
vtx_builder.AppendVertex(vtx);
vtx.position = position + orientation + forward;
vtx_builder.AppendVertex(vtx);
vtx.position = position - orientation + forward;
vtx_builder.AppendVertex(vtx);
};
break;
}
return cap_proc;
}
// static
VertexBufferBuilder<SolidFillVertexShader::PerVertexData>
StrokePathGeometry::CreateSolidStrokeVertices(
const Path& path,
Scalar stroke_width,
Scalar scaled_miter_limit,
const StrokePathGeometry::JoinProc& join_proc,
const StrokePathGeometry::CapProc& cap_proc,
Scalar scale) {
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
auto polyline = path.CreatePolyline(scale);
VS::PerVertexData vtx;
// Offset state.
Point offset;
Point previous_offset; // Used for computing joins.
auto compute_offset = [&polyline, &offset, &previous_offset,
&stroke_width](size_t point_i) {
previous_offset = offset;
Point direction =
(polyline.points[point_i] - polyline.points[point_i - 1]).Normalize();
offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5;
};
for (size_t contour_i = 0; contour_i < polyline.contours.size();
contour_i++) {
auto contour = polyline.contours[contour_i];
size_t contour_start_point_i, contour_end_point_i;
std::tie(contour_start_point_i, contour_end_point_i) =
polyline.GetContourPointBounds(contour_i);
switch (contour_end_point_i - contour_start_point_i) {
case 1: {
Point p = polyline.points[contour_start_point_i];
cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, scale, false);
cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, scale, false);
continue;
}
case 0:
continue; // This contour has no renderable content.
default:
break;
}
// The first point's offset is always the same as the second point.
compute_offset(contour_start_point_i + 1);
const Point contour_first_offset = offset;
if (contour_i > 0) {
// This branch only executes when we've just finished drawing a contour
// and are switching to a new one.
// We're drawing a triangle strip, so we need to "pick up the pen" by
// appending two vertices at the end of the previous contour and two
// vertices at the start of the new contour (thus connecting the two
// contours with two zero volume triangles, which will be discarded by
// the rasterizer).
vtx.position = polyline.points[contour_start_point_i - 1];
// Append two vertices when "picking up" the pen so that the triangle
// drawn when moving to the beginning of the new contour will have zero
// volume.
vtx_builder.AppendVertex(vtx);
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[contour_start_point_i];
// Append two vertices at the beginning of the new contour, which
// appends two triangles of zero area.
vtx_builder.AppendVertex(vtx);
vtx_builder.AppendVertex(vtx);
}
// Generate start cap.
if (!polyline.contours[contour_i].is_closed) {
auto cap_offset =
Vector2(-contour.start_direction.y, contour.start_direction.x) *
stroke_width * 0.5; // Counterclockwise normal
cap_proc(vtx_builder, polyline.points[contour_start_point_i], cap_offset,
scale, true);
}
// Generate contour geometry.
for (size_t point_i = contour_start_point_i + 1;
point_i < contour_end_point_i; point_i++) {
// Generate line rect.
vtx.position = polyline.points[point_i - 1] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i - 1] - offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] - offset;
vtx_builder.AppendVertex(vtx);
if (point_i < contour_end_point_i - 1) {
compute_offset(point_i + 1);
// Generate join from the current line to the next line.
join_proc(vtx_builder, polyline.points[point_i], previous_offset,
offset, scaled_miter_limit, scale);
}
}
// Generate end cap or join.
if (!polyline.contours[contour_i].is_closed) {
auto cap_offset =
Vector2(-contour.end_direction.y, contour.end_direction.x) *
stroke_width * 0.5; // Clockwise normal
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1],
cap_offset, scale, false);
} else {
join_proc(vtx_builder, polyline.points[contour_start_point_i], offset,
contour_first_offset, scaled_miter_limit, scale);
}
}
return vtx_builder;
}
GeometryResult StrokePathGeometry::GetPositionBuffer(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
if (stroke_width_ < 0.0) {
return {};
}
auto determinant = entity.GetTransformation().GetDeterminant();
if (determinant == 0) {
return {};
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar stroke_width = std::max(stroke_width_, min_size);
auto& host_buffer = pass.GetTransientsBuffer();
auto vertex_builder = CreateSolidStrokeVertices(
path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
entity.GetTransformation().GetMaxBasisLength());
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = true,
};
}
GeometryResult StrokePathGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
if (stroke_width_ < 0.0) {
return {};
}
auto determinant = entity.GetTransformation().GetDeterminant();
if (determinant == 0) {
return {};
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar stroke_width = std::max(stroke_width_, min_size);
auto& host_buffer = pass.GetTransientsBuffer();
auto stroke_builder = CreateSolidStrokeVertices(
path_, stroke_width, miter_limit_ * stroke_width_ * 0.5,
GetJoinProc(stroke_join_), GetCapProc(stroke_cap_),
entity.GetTransformation().GetMaxBasisLength());
VertexBufferBuilder<TextureFillVertexShader::PerVertexData> vertex_builder;
stroke_builder.IterateVertices(
[&vertex_builder, &texture_coverage,
&effect_transform](SolidFillVertexShader::PerVertexData old_vtx) {
TextureFillVertexShader::PerVertexData data;
data.position = old_vtx.position;
auto coverage_coords = old_vtx.position / texture_coverage.size;
data.texture_coords = effect_transform * coverage_coords;
vertex_builder.AppendVertex(data);
});
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer = vertex_builder.CreateVertexBuffer(host_buffer),
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = true,
};
}
GeometryVertexType StrokePathGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}
std::optional<Rect> StrokePathGeometry::GetCoverage(
const Matrix& transform) const {
auto path_bounds = path_.GetBoundingBox();
if (!path_bounds.has_value()) {
return std::nullopt;
}
auto path_coverage = path_bounds->TransformBounds(transform);
Scalar max_radius = 0.5;
if (stroke_cap_ == Cap::kSquare) {
max_radius = max_radius * kSqrt2;
}
if (stroke_join_ == Join::kMiter) {
max_radius = std::max(max_radius, miter_limit_ * 0.5f);
}
Scalar determinant = transform.GetDeterminant();
if (determinant == 0) {
return std::nullopt;
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Vector2 max_radius_xy =
transform
.TransformDirection(Vector2(max_radius, max_radius) *
std::max(stroke_width_, min_size))
.Abs();
return Rect(path_coverage.origin - max_radius_xy,
Size(path_coverage.size.width + max_radius_xy.x * 2,
path_coverage.size.height + max_radius_xy.y * 2));
}
/////// Cover Geometry ///////
CoverGeometry::CoverGeometry() = default;
CoverGeometry::~CoverGeometry() = default;
GeometryResult CoverGeometry::GetPositionBuffer(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
auto rect = Rect(Size(pass.GetRenderTargetSize()));
constexpr uint16_t kRectIndicies[4] = {0, 1, 2, 3};
auto& host_buffer = pass.GetTransientsBuffer();
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
{
.vertex_buffer = host_buffer.Emplace(
rect.GetPoints().data(), 8 * sizeof(float), alignof(float)),
.index_buffer = host_buffer.Emplace(
kRectIndicies, 4 * sizeof(uint16_t), alignof(uint16_t)),
.vertex_count = 4,
.index_type = IndexType::k16bit,
},
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()),
.prevent_overdraw = false,
};
}
// |Geometry|
GeometryResult CoverGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
auto rect = Rect(Size(pass.GetRenderTargetSize()));
return ComputeUVGeometryForRect(rect, texture_coverage, effect_transform,
renderer, entity, pass);
}
GeometryVertexType CoverGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}
std::optional<Rect> CoverGeometry::GetCoverage(const Matrix& transform) const {
return Rect::MakeMaximum();
}
/////// Rect Geometry ///////
RectGeometry::RectGeometry(Rect rect) : rect_(rect) {}
RectGeometry::~RectGeometry() = default;
GeometryResult RectGeometry::GetPositionBuffer(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
auto& host_buffer = pass.GetTransientsBuffer();
return GeometryResult{
.type = PrimitiveType::kTriangleStrip,
.vertex_buffer =
{
.vertex_buffer = host_buffer.Emplace(
rect_.GetPoints().data(), 8 * sizeof(float), alignof(float)),
.vertex_count = 4,
.index_type = IndexType::kNone,
},
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
// |Geometry|
GeometryResult RectGeometry::GetPositionUVBuffer(Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
return ComputeUVGeometryForRect(rect_, texture_coverage, effect_transform,
renderer, entity, pass);
}
GeometryVertexType RectGeometry::GetVertexType() const {
return GeometryVertexType::kPosition;
}
std::optional<Rect> RectGeometry::GetCoverage(const Matrix& transform) const {
return rect_.TransformBounds(transform);
}
/////// PointFieldGeometry Geometry ///////
PointFieldGeometry::PointFieldGeometry(std::vector<Point> points,
Scalar radius,
bool round)
: points_(std::move(points)), radius_(radius), round_(round) {}
PointFieldGeometry::~PointFieldGeometry() = default;
GeometryResult PointFieldGeometry::GetPositionBuffer(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
if (radius_ < 0.0) {
return {};
}
auto determinant = entity.GetTransformation().GetDeterminant();
if (determinant == 0) {
return {};
}
Scalar min_size = 1.0f / sqrt(std::abs(determinant));
Scalar radius = std::max(radius_, min_size);
if (!renderer.GetDeviceCapabilities().SupportsCompute()) {
return GetPositionBufferCPU(renderer, entity, pass, radius);
}
auto vertices_per_geom = ComputeCircleDivisions(
entity.GetTransformation().GetMaxBasisLength() * radius, round_);
auto points_per_circle = 3 + (vertices_per_geom - 3) * 3;
auto total = points_per_circle * points_.size();
auto& host_buffer = pass.GetTransientsBuffer();
using PS = PointsComputeShader;
auto points_data = host_buffer.Emplace(
points_.data(), points_.size() * sizeof(Point), alignof(Point));
DeviceBufferDescriptor buffer_desc;
buffer_desc.size = total * sizeof(Point);
buffer_desc.storage_mode = StorageMode::kDevicePrivate;
auto buffer =
renderer.GetContext()->GetResourceAllocator()->CreateBuffer(buffer_desc);
ComputeCommand cmd;
cmd.label = "Points Geometry";
cmd.pipeline = renderer.GetPointComputePipeline();
PS::FrameInfo frame_info;
frame_info.count = points_.size();
frame_info.radius = radius;
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(cmd, host_buffer.EmplaceUniform(frame_info));
PS::BindGeometryData(
cmd, {.buffer = buffer, .range = Range{0, total * sizeof(Point)}});
PS::BindPointData(cmd, points_data);
{
auto cmd_buffer = renderer.GetContext()->CreateCommandBuffer();
auto pass = cmd_buffer->CreateComputePass();
pass->SetGridSize(ISize(total, 1));
pass->SetThreadGroupSize(ISize(total, 1));
if (!pass->AddCommand(std::move(cmd)) || !pass->EncodeCommands() ||
!cmd_buffer->SubmitCommands()) {
return {};
}
}
return {
.type = PrimitiveType::kTriangle,
.vertex_buffer = {.vertex_buffer = {.buffer = buffer,
.range =
Range{0, total * sizeof(Point)}},
.vertex_count = total,
.index_type = IndexType::kNone},
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
GeometryResult PointFieldGeometry::GetPositionBufferCPU(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
Scalar radius) {
auto vertices_per_geom = ComputeCircleDivisions(
entity.GetTransformation().GetMaxBasisLength() * radius, round_);
auto points_per_circle = 3 + (vertices_per_geom - 3) * 3;
auto total = points_per_circle * points_.size();
auto& host_buffer = pass.GetTransientsBuffer();
auto radian_start = round_ ? 0.0f : 0.785398f;
auto radian_step = k2Pi / vertices_per_geom;
VertexBufferBuilder<SolidFillVertexShader::PerVertexData> vtx_builder;
vtx_builder.Reserve(total);
/// Precompute all relative points and angles for a fixed geometry size.
auto elapsed_angle = radian_start;
std::vector<Point> 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];
auto origin = center + angle_table[0];
vtx_builder.AppendVertex({origin});
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});
}
}
return {
.type = PrimitiveType::kTriangle,
.vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer),
.transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation(),
.prevent_overdraw = false,
};
}
GeometryResult PointFieldGeometry::GetPositionUVBuffer(
Rect texture_coverage,
Matrix effect_transform,
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) {
FML_UNREACHABLE();
}
/// @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);
}
return Rect::MakeLTRB(left - radius_, top - radius_, right + radius_,
bottom + radius_);
}
return std::nullopt;
}
} // namespace impeller