[Impeller] small cpu perf for text contents. (#166199)

* Compute scaling matrices once per frame instead of per glyph.
* Use index buffer to avoid performing redundant computations for second
triangle.
This commit is contained in:
Jonah Williams
2025-03-31 18:37:20 -07:00
committed by GitHub
parent c68c971e4c
commit 49ebe507a9
2 changed files with 81 additions and 63 deletions

View File

@@ -108,8 +108,7 @@ void TextContents::ComputeVertexData(
// interpolated vertex information is also used in the fragment shader to
// sample from the glyph atlas.
constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0},
Point{0, 1}, Point{1, 0},
constexpr std::array<Point, 4> unit_points = {Point{0, 0}, Point{1, 0},
Point{0, 1}, Point{1, 1}};
ISize atlas_size = atlas->GetTexture()->GetSize();
@@ -119,9 +118,19 @@ void TextContents::ComputeVertexData(
VS::PerVertexData vtx;
size_t i = 0u;
size_t bounds_offset = 0u;
Rational rounded_scale = frame->GetScale();
Scalar inverted_rounded_scale = static_cast<Scalar>(rounded_scale.Invert());
Matrix unscaled_basis =
basis_transform *
Matrix::MakeScale({inverted_rounded_scale, inverted_rounded_scale, 1});
// In typical scales < 48x these values should be -1 or 1. We round to
// those to avoid inaccuracies.
unscaled_basis.m[0] = AttractToOne(unscaled_basis.m[0]);
unscaled_basis.m[5] = AttractToOne(unscaled_basis.m[5]);
for (const TextRun& run : frame->GetRuns()) {
const Font& font = run.GetFont();
Rational rounded_scale = frame->GetScale();
const Matrix transform = frame->GetOffsetTransform();
FontGlyphAtlas* font_atlas = nullptr;
@@ -181,26 +190,15 @@ void TextContents::ComputeVertexData(
atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
}
Scalar inverted_rounded_scale =
static_cast<Scalar>(rounded_scale.Invert());
Rect scaled_bounds = glyph_bounds.Scale(inverted_rounded_scale);
// For each glyph, we compute two rectangles. One for the vertex
// positions and one for the texture coordinates (UVs). The atlas
// glyph bounds are used to compute UVs in cases where the
// destination and source sizes may differ due to clamping the sizes
// of large glyphs.
Point uv_origin = (atlas_glyph_bounds.GetLeftTop()) / atlas_size;
Point uv_origin = atlas_glyph_bounds.GetLeftTop() / atlas_size;
Point uv_size = SizeToPoint(atlas_glyph_bounds.GetSize()) / atlas_size;
Matrix unscaled_basis =
basis_transform * Matrix::MakeScale({inverted_rounded_scale,
inverted_rounded_scale, 1});
// In typical scales < 48x these values should be -1 or 1. We round to
// those to avoid inaccuracies.
unscaled_basis.m[0] = AttractToOne(unscaled_basis.m[0]);
unscaled_basis.m[5] = AttractToOne(unscaled_basis.m[5]);
Point unrounded_glyph_position =
// This is for RTL text.
unscaled_basis * glyph_bounds.GetLeftTop() +
@@ -231,12 +229,12 @@ void TextContents::ComputeVertexData(
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto color = GetColor();
Color color = GetColor();
if (color.IsTransparent()) {
return true;
}
auto type = frame_->GetAtlasType();
GlyphAtlas::Type type = frame_->GetAtlasType();
const std::shared_ptr<GlyphAtlas>& atlas =
renderer.GetLazyGlyphAtlas()->CreateOrGetGlyphAtlas(
*renderer.GetContext(), renderer.GetTransientsBuffer(), type);
@@ -298,26 +296,45 @@ bool TextContents::Render(const ContentContext& renderer,
sampler_desc) // sampler
);
auto& host_buffer = renderer.GetTransientsBuffer();
size_t vertex_count = 0;
HostBuffer& host_buffer = renderer.GetTransientsBuffer();
size_t glyph_count = 0;
for (const auto& run : frame_->GetRuns()) {
vertex_count += run.GetGlyphPositions().size();
glyph_count += run.GetGlyphPositions().size();
}
vertex_count *= 6;
size_t vertex_count = glyph_count * 4;
size_t index_count = glyph_count * 6;
BufferView buffer_view = host_buffer.Emplace(
vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
[&](uint8_t* contents) {
[&](uint8_t* data) {
VS::PerVertexData* vtx_contents =
reinterpret_cast<VS::PerVertexData*>(contents);
ComputeVertexData(vtx_contents, frame_, scale_,
/*entity_transform=*/entity_transform, offset_,
GetGlyphProperties(), atlas);
reinterpret_cast<VS::PerVertexData*>(data);
ComputeVertexData(/*vtx_contents=*/vtx_contents,
/*frame=*/frame_,
/*scale=*/scale_,
/*entity_transform=*/entity_transform,
/*offset=*/offset_,
/*glyph_properties=*/GetGlyphProperties(),
/*atlas=*/atlas);
});
BufferView index_buffer_view = host_buffer.Emplace(
index_count * sizeof(uint16_t), alignof(uint16_t), [&](uint8_t* data) {
uint16_t* indices = reinterpret_cast<uint16_t*>(data);
size_t j = 0;
for (auto i = 0u; i < glyph_count; i++) {
size_t base = i * 4;
indices[j++] = base + 0;
indices[j++] = base + 1;
indices[j++] = base + 2;
indices[j++] = base + 1;
indices[j++] = base + 2;
indices[j++] = base + 3;
}
});
pass.SetVertexBuffer(std::move(buffer_view));
pass.SetIndexBuffer({}, IndexType::kNone);
pass.SetElementCount(vertex_count);
pass.SetIndexBuffer(index_buffer_view, IndexType::k16bit);
pass.SetElementCount(index_count);
return pass.Draw().ok();
}

View File

@@ -69,12 +69,13 @@ std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
}
Rect PerVertexDataPositionToRect(
GlyphAtlasPipeline::VertexShader::PerVertexData data[6]) {
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator
data) {
Scalar right = FLT_MIN;
Scalar left = FLT_MAX;
Scalar top = FLT_MAX;
Scalar bottom = FLT_MIN;
for (int i = 0; i < 6; ++i) {
for (int i = 0; i < 4; ++i) {
right = std::max(right, data[i].position.x);
left = std::min(left, data[i].position.x);
top = std::min(top, data[i].position.y);
@@ -85,13 +86,13 @@ Rect PerVertexDataPositionToRect(
}
Rect PerVertexDataUVToRect(
GlyphAtlasPipeline::VertexShader::PerVertexData data[6],
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator data,
ISize texture_size) {
Scalar right = FLT_MIN;
Scalar left = FLT_MAX;
Scalar top = FLT_MAX;
Scalar bottom = FLT_MIN;
for (int i = 0; i < 6; ++i) {
for (int i = 0; i < 4; ++i) {
right = std::max(right, data[i].uv.x * texture_size.width);
left = std::min(left, data[i].uv.x * texture_size.width);
top = std::min(top, data[i].uv.y * texture_size.height);
@@ -111,7 +112,7 @@ TEST_P(TextContentsTest, SimpleComputeVertexData) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame =
MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
@@ -128,13 +129,13 @@ TEST_P(TextContentsTest, SimpleComputeVertexData) {
atlas_context, text_frame, /*offset=*/{0, 0});
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(data, text_frame, /*scale=*/1.0,
TextContents::ComputeVertexData(data.data(), text_frame, /*scale=*/1.0,
/*entity_transform=*/Matrix(),
/*offset=*/Vector2(0, 0),
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
// The -1 offset comes from Skia in `ComputeGlyphSize`. So since the font size
// is 50, the math appears to be to get back a 50x50 rect and apply 1 pixel
// of padding.
@@ -147,8 +148,7 @@ TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame =
MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
@@ -166,15 +166,15 @@ TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, static_cast<Scalar>(font_scale),
data.data(), text_frame, static_cast<Scalar>(font_scale),
/*entity_transform=*/
Matrix::MakeScale({static_cast<Scalar>(font_scale),
static_cast<Scalar>(font_scale), 1}),
/*offset=*/Vector2(0, 0),
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -81, 102, 102));
EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 102, 102));
}
@@ -196,7 +196,8 @@ TEST_P(TextContentsTest, MaintainsShape) {
Rect uv_rect[2];
{
GlyphAtlasPipeline::VertexShader::PerVertexData data[12];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(12);
std::shared_ptr<GlyphAtlas> atlas =
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
GlyphAtlas::Type::kAlphaBitmap, font_scale,
@@ -204,16 +205,16 @@ TEST_P(TextContentsTest, MaintainsShape) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, static_cast<Scalar>(font_scale),
data.data(), text_frame, static_cast<Scalar>(font_scale),
/*entity_transform=*/
Matrix::MakeScale({static_cast<Scalar>(font_scale),
static_cast<Scalar>(font_scale), 1}),
/*offset=*/Vector2(0, 0),
/*glyph_properties=*/std::nullopt, atlas);
position_rect[0] = PerVertexDataPositionToRect(data);
uv_rect[0] = PerVertexDataUVToRect(data, texture_size);
position_rect[1] = PerVertexDataPositionToRect(data + 6);
uv_rect[1] = PerVertexDataUVToRect(data + 6, texture_size);
position_rect[0] = PerVertexDataPositionToRect(data.begin());
uv_rect[0] = PerVertexDataUVToRect(data.begin(), texture_size);
position_rect[1] = PerVertexDataPositionToRect(data.begin() + 4);
uv_rect[1] = PerVertexDataUVToRect(data.begin() + 4, texture_size);
}
EXPECT_NEAR(GetAspectRatio(position_rect[1]), GetAspectRatio(uv_rect[1]),
0.001)
@@ -226,7 +227,7 @@ TEST_P(TextContentsTest, SimpleSubpixel) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
"1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
@@ -245,12 +246,12 @@ TEST_P(TextContentsTest, SimpleSubpixel) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, /*scale=*/1.0,
data.data(), text_frame, /*scale=*/1.0,
/*entity_transform=*/Matrix::MakeTranslation(offset), offset,
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
// The values at Point(0, 0).
// EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
// EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
@@ -263,7 +264,7 @@ TEST_P(TextContentsTest, SimpleSubpixel3x) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
"1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
@@ -283,7 +284,7 @@ TEST_P(TextContentsTest, SimpleSubpixel3x) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, static_cast<Scalar>(font_scale),
data.data(), text_frame, static_cast<Scalar>(font_scale),
/*entity_transform=*/
Matrix::MakeTranslation(offset) *
Matrix::MakeScale({static_cast<Scalar>(font_scale),
@@ -291,8 +292,8 @@ TEST_P(TextContentsTest, SimpleSubpixel3x) {
offset,
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
// Values at Point(0, 0)
// EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -121, 152, 152));
// EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 152, 152));
@@ -307,7 +308,7 @@ TEST_P(TextContentsTest, SimpleSubpixel26) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
"1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
@@ -326,12 +327,12 @@ TEST_P(TextContentsTest, SimpleSubpixel26) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, /*scale=*/1.0,
data.data(), text_frame, /*scale=*/1.0,
/*entity_transform=*/Matrix::MakeTranslation(offset), offset,
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
// The values without subpixel.
// EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
// EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
@@ -344,7 +345,7 @@ TEST_P(TextContentsTest, SimpleSubpixel80) {
GTEST_SKIP() << "Results aren't stable across linux and macos.";
#endif
GlyphAtlasPipeline::VertexShader::PerVertexData data[6];
std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
"1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
@@ -363,12 +364,12 @@ TEST_P(TextContentsTest, SimpleSubpixel80) {
ISize texture_size = atlas->GetTexture()->GetSize();
TextContents::ComputeVertexData(
data, text_frame, /*scale=*/1.0,
data.data(), text_frame, /*scale=*/1.0,
/*entity_transform=*/Matrix::MakeTranslation(offset), offset,
/*glyph_properties=*/std::nullopt, atlas);
Rect position_rect = PerVertexDataPositionToRect(data);
Rect uv_rect = PerVertexDataUVToRect(data, texture_size);
Rect position_rect = PerVertexDataPositionToRect(data.begin());
Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
// The values without subpixel.
// EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
// EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));