[Impeller] Avoid extra copies when using CreateSolidFillVertices (flutter/engine#36555)

This commit is contained in:
Jonah Williams 2022-10-03 13:47:20 -07:00 committed by GitHub
parent db5fd760a9
commit 0e7ce79e9c
11 changed files with 212 additions and 25 deletions

View File

@ -119,8 +119,8 @@ bool ClipContents::Render(const ContentContext& renderer,
}
cmd.pipeline = renderer.GetClipPipeline(options);
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
renderer.GetTessellator(), path_, pass.GetTransientsBuffer()));
cmd.BindVertices(CreateSolidFillVertices(renderer.GetTessellator(), path_,
pass.GetTransientsBuffer()));
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();

View File

@ -77,7 +77,7 @@ bool LinearGradientContents::Render(const ContentContext& renderer,
cmd.pipeline = renderer.GetLinearGradientFillPipeline(
OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
cmd.BindVertices(CreateSolidFillVertices(
renderer.GetTessellator(),
GetCover()
? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath()

View File

@ -76,7 +76,7 @@ bool RadialGradientContents::Render(const ContentContext& renderer,
cmd.pipeline = renderer.GetRadialGradientFillPipeline(
OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
cmd.BindVertices(CreateSolidFillVertices(
renderer.GetTessellator(),
GetCover()
? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath()

View File

@ -62,7 +62,7 @@ bool SolidColorContents::Render(const ContentContext& renderer,
renderer.GetSolidFillPipeline(OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
cmd.BindVertices(CreateSolidFillVertices(
renderer.GetTessellator(),
cover_
? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath()

View File

@ -4,8 +4,33 @@
#include "impeller/entity/contents/solid_fill_utils.h"
#include "impeller/geometry/path.h"
#include "impeller/renderer/device_buffer.h"
#include "impeller/renderer/host_buffer.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
//
VertexBuffer CreateSolidFillVertices(std::shared_ptr<Tessellator> tessellator,
const Path& path,
HostBuffer& buffer) {
VertexBuffer vertex_buffer;
auto tesselation_result = tessellator->TessellateBuilder(
path.GetFillType(), path.CreatePolyline(),
[&vertex_buffer, &buffer](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) {
vertex_buffer.vertex_buffer = buffer.Emplace(
vertices, vertices_count * sizeof(float), alignof(float));
vertex_buffer.index_buffer = buffer.Emplace(
indices, indices_count * sizeof(uint16_t), alignof(uint16_t));
vertex_buffer.index_count = indices_count;
vertex_buffer.index_type = IndexType::k16bit;
return true;
});
if (tesselation_result != Tessellator::Result::kSuccess) {
return {};
}
return vertex_buffer;
}
} // namespace impeller

View File

@ -3,26 +3,26 @@
// found in the LICENSE file.
#pragma once
#include "impeller/renderer/vertex_buffer_builder.h"
#include "impeller/tessellator/tessellator.h"
#include "impeller/renderer/vertex_buffer.h"
namespace impeller {
template <typename PerVertexData>
class Tessellator;
class Path;
class HostBuffer;
/**
* @brief Populate a VertexBuffer with solid fill vertices created by
* tessellating an input path.
*
* @param tessellator The tessellator
* @param path The path to be tessellated
* @param buffer The transient buffer
* @return VertexBuffer A populated vertex buffer if successful, otherwise
* empty.
*/
VertexBuffer CreateSolidFillVertices(std::shared_ptr<Tessellator> tessellator,
const Path& path,
HostBuffer& buffer) {
VertexBufferBuilder<PerVertexData> vtx_builder;
auto tesselation_result = tessellator->Tessellate(
path.GetFillType(), path.CreatePolyline(),
[&vtx_builder](auto point) { vtx_builder.AppendVertex({point}); });
if (tesselation_result != Tessellator::Result::kSuccess) {
return {};
}
return vtx_builder.CreateVertexBuffer(buffer);
}
HostBuffer& buffer);
} // namespace impeller

View File

@ -83,7 +83,7 @@ bool SweepGradientContents::Render(const ContentContext& renderer,
cmd.pipeline = renderer.GetSweepGradientFillPipeline(
OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
cmd.BindVertices(CreateSolidFillVertices(
renderer.GetTessellator(),
GetCover()
? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath()

View File

@ -67,12 +67,12 @@ bool TiledTextureContents::Render(const ContentContext& renderer,
cmd.pipeline =
renderer.GetTiledTexturePipeline(OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidFillVertices<VS::PerVertexData>(
cmd.BindVertices(CreateSolidFillVertices(
renderer.GetTessellator(),
GetCover()
? PathBuilder{}.AddRect(Size(pass.GetRenderTargetSize())).TakePath()
: GetPath(),
pass.GetTransientsBuffer()));
host_buffer));
VS::BindVertInfo(cmd, host_buffer.EmplaceUniform(vert_info));
FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
FS::BindTextureSampler(cmd, texture_,

View File

@ -128,6 +128,76 @@ Tessellator::Result Tessellator::Tessellate(
return Result::kSuccess;
}
Tessellator::Result Tessellator::TessellateBuilder(
FillType fill_type,
const Path::Polyline& polyline,
const BuilderCallback& callback) const {
if (!callback) {
return Result::kInputError;
}
if (polyline.points.empty()) {
return Result::kInputError;
}
auto tessellator = c_tessellator_.get();
if (!tessellator) {
return Result::kTessellationError;
}
constexpr int kVertexSize = 2;
constexpr int kPolygonSize = 3;
//----------------------------------------------------------------------------
/// Feed contour information to the tessellator.
///
static_assert(sizeof(Point) == 2 * sizeof(float));
for (size_t contour_i = 0; contour_i < polyline.contours.size();
contour_i++) {
size_t start_point_index, end_point_index;
std::tie(start_point_index, end_point_index) =
polyline.GetContourPointBounds(contour_i);
::tessAddContour(tessellator, // the C tessellator
kVertexSize, //
polyline.points.data() + start_point_index, //
sizeof(Point), //
end_point_index - start_point_index //
);
}
//----------------------------------------------------------------------------
/// Let's tessellate.
///
auto result = ::tessTesselate(tessellator, // tessellator
ToTessWindingRule(fill_type), // winding
TESS_POLYGONS, // element type
kPolygonSize, // polygon size
kVertexSize, // vertex size
nullptr // normal (null is automatic)
);
if (result != 1) {
return Result::kTessellationError;
}
int vertexItemCount = tessGetVertexCount(tessellator) * kVertexSize;
auto vertices = tessGetVertices(tessellator);
int elementItemCount = tessGetElementCount(tessellator) * kPolygonSize;
auto elements = tessGetElements(tessellator);
// libtess uses an int index internally due to usage of -1 as a sentinel
// value.
std::vector<uint16_t> indices(elementItemCount);
for (int i = 0; i < elementItemCount; i++) {
indices[i] = static_cast<uint16_t>(elements[i]);
}
if (!callback(vertices, vertexItemCount, indices.data(), elementItemCount)) {
return Result::kInputError;
}
return Result::kSuccess;
}
void DestroyTessellator(TESStesselator* tessellator) {
if (tessellator != nullptr) {
::tessDeleteTess(tessellator);

View File

@ -44,6 +44,11 @@ class Tessellator {
~Tessellator();
using VertexCallback = std::function<void(Point)>;
using BuilderCallback = std::function<bool(const float* vertices,
size_t vertices_size,
const uint16_t* indices,
size_t indices_size)>;
//----------------------------------------------------------------------------
/// @brief Generates filled triangles from the polyline. A callback is
/// invoked for each vertex of the triangle.
@ -58,6 +63,20 @@ class Tessellator {
const Path::Polyline& polyline,
const VertexCallback& callback) const;
//----------------------------------------------------------------------------
/// @brief Generates filled triangles from the polyline. A callback is
/// invoked once for the entire tessellation.
///
/// @param[in] fill_type The fill rule to use when filling.
/// @param[in] polyline The polyline
/// @param[in] callback The callback, return false to indicate failure.
///
/// @return The result status of the tessellation.
///
Tessellator::Result TessellateBuilder(FillType fill_type,
const Path::Polyline& polyline,
const BuilderCallback& callback) const;
private:
CTessellator c_tessellator_;

View File

@ -62,5 +62,78 @@ TEST(TessellatorTest, TessellatorReturnsCorrectResultStatus) {
}
}
TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) {
// Zero points.
{
Tessellator t;
auto polyline = PathBuilder{}.TakePath().CreatePolyline();
Tessellator::Result result = t.TessellateBuilder(
FillType::kPositive, polyline,
[](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) { return true; });
ASSERT_EQ(polyline.points.size(), 0u);
ASSERT_EQ(result, Tessellator::Result::kInputError);
}
// One point.
{
Tessellator t;
auto polyline = PathBuilder{}.LineTo({0, 0}).TakePath().CreatePolyline();
Tessellator::Result result = t.TessellateBuilder(
FillType::kPositive, polyline,
[](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) { return true; });
ASSERT_EQ(polyline.points.size(), 1u);
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Two points.
{
Tessellator t;
auto polyline =
PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath().CreatePolyline();
Tessellator::Result result = t.TessellateBuilder(
FillType::kPositive, polyline,
[](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) { return true; });
ASSERT_EQ(polyline.points.size(), 2u);
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Many points.
{
Tessellator t;
PathBuilder builder;
for (int i = 0; i < 1000; i++) {
auto coord = i * 1.0f;
builder.AddLine({coord, coord}, {coord + 1, coord + 1});
}
auto polyline = builder.TakePath().CreatePolyline();
Tessellator::Result result = t.TessellateBuilder(
FillType::kPositive, polyline,
[](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) { return true; });
ASSERT_EQ(polyline.points.size(), 2000u);
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Closure fails.
{
Tessellator t;
auto polyline =
PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath().CreatePolyline();
Tessellator::Result result = t.TessellateBuilder(
FillType::kPositive, polyline,
[](const float* vertices, size_t vertices_size, const uint16_t* indices,
size_t indices_size) { return false; });
ASSERT_EQ(polyline.points.size(), 2u);
ASSERT_EQ(result, Tessellator::Result::kInputError);
}
}
} // namespace testing
} // namespace impeller