Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
[Impeller] offload all text computation into vertex shader (#42417)
Browse files Browse the repository at this point in the history
TextContents::Render occassionally shows up in the highest CPU functions. We can actually unload most of this computation into the vertex shader.
  • Loading branch information
jonahwilliams authored May 31, 2023
1 parent 5374988 commit 7a4a267
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 118 deletions.
87 changes: 32 additions & 55 deletions impeller/entity/contents/text_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,32 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
return bounds->TransformBounds(entity.GetTransformation());
}

static bool CommonRender(
const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Color& color,
const TextFrame& frame,
Vector2 offset,
std::shared_ptr<GlyphAtlas>
atlas, // NOLINT(performance-unnecessary-value-param)
Command& cmd) {
static bool CommonRender(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Color& color,
const TextFrame& frame,
Vector2 offset,
const std::shared_ptr<GlyphAtlas>& atlas,
Command& cmd) {
using VS = GlyphAtlasPipeline::VertexShader;
using FS = GlyphAtlasPipeline::FragmentShader;

// Common vertex uniforms for all glyphs.
VS::FrameInfo frame_info;

frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
frame_info.atlas_size =
Vector2{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
frame_info.offset = offset;
frame_info.is_translation_scale =
entity.GetTransformation().IsTranslationScaleOnly();
frame_info.entity_transform = entity.GetTransformation();

VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));

SamplerDescriptor sampler_desc;
if (entity.GetTransformation().IsTranslationScaleOnly()) {
if (frame_info.is_translation_scale) {
sampler_desc.min_filter = MinMagFilter::kNearest;
sampler_desc.mag_filter = MinMagFilter::kNearest;
} else {
Expand Down Expand Up @@ -152,12 +157,6 @@ static bool CommonRender(
index_offset += 4;
}

auto atlas_size =
Point{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};

Vector2 screen_offset = (entity.GetTransformation() * offset).Round();

for (const auto& run : frame.GetRuns()) {
const Font& font = run.GetFont();

Expand All @@ -168,52 +167,30 @@ static bool CommonRender(
VALIDATION_LOG << "Could not find glyph position in the atlas.";
return false;
}

// For each glyph, we compute two rectangles. One for the vertex positions
// and one for the texture coordinates (UVs).

auto uv_origin =
(atlas_glyph_bounds->origin - Point(0.5, 0.5)) / atlas_size;
auto uv_size = (atlas_glyph_bounds->size + Size(1, 1)) / atlas_size;

// Rounding here prevents most jitter between glyphs in the run when
// nearest sampling.
auto screen_glyph_position =
screen_offset +
(entity.GetTransformation().Basis() *
(glyph_position.position + glyph_position.glyph.bounds.origin))
.Round();
Vector4 atlas_glyph_bounds_vec = Vector4(
atlas_glyph_bounds->origin.x, atlas_glyph_bounds->origin.y,
atlas_glyph_bounds->size.width, atlas_glyph_bounds->size.height);
Vector4 glyph_bounds_vec =
Vector4(glyph_position.glyph.bounds.origin.x,
glyph_position.glyph.bounds.origin.y,
glyph_position.glyph.bounds.size.width,
glyph_position.glyph.bounds.size.height);

for (const auto& point : unit_points) {
VS::PerVertexData vtx;

if (entity.GetTransformation().IsTranslationScaleOnly()) {
// Rouding up here prevents the bounds from becoming 1 pixel too small
// when nearest sampling. This path breaks down for projections.
vtx.position =
screen_glyph_position + (entity.GetTransformation().Basis() *
point * glyph_position.glyph.bounds.size)
.Ceil();
} else {
vtx.position = entity.GetTransformation() *
Vector4(offset + glyph_position.position +
glyph_position.glyph.bounds.origin +
point * glyph_position.glyph.bounds.size);
}
vtx.uv = uv_origin + point * uv_size;
vertex_builder.AppendVertex(vtx);
vertex_builder.AppendVertex(VS::PerVertexData{
.atlas_glyph_bounds = atlas_glyph_bounds_vec,
.glyph_bounds = glyph_bounds_vec,
.unit_position = point,
.glyph_position = glyph_position.position,
});
}
}
}
auto vertex_buffer =
vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer());
cmd.BindVertices(vertex_buffer);

if (!pass.AddCommand(cmd)) {
return false;
}

return true;
return pass.AddCommand(cmd);
}

bool TextContents::Render(const ContentContext& renderer,
Expand Down
66 changes: 63 additions & 3 deletions impeller/entity/shaders/glyph_atlas.vert
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,75 @@

uniform FrameInfo {
mat4 mvp;
mat4 entity_transform;
vec2 atlas_size;
vec2 offset;
float is_translation_scale;
}
frame_info;

in highp vec4 position;
in vec2 uv;
// XYWH.
in vec4 atlas_glyph_bounds;
// XYWH
in vec4 glyph_bounds;

in vec2 unit_position;
in vec2 glyph_position;

out vec2 v_uv;

mat4 basis(mat4 m) {
return mat4(m[0][0], m[0][1], m[0][2], 0.0, //
m[1][0], m[1][1], m[1][2], 0.0, //
m[2][0], m[2][1], m[2][2], 0.0, //
0.0, 0.0, 0.0, 1.0 //
);
}

vec2 project(mat4 m, vec2 v) {
float w = v.x * m[0][3] + v.y * m[1][3] + m[3][3];
vec2 result = vec2(v.x * m[0][0] + v.y * m[1][0] + m[3][0],
v.x * m[0][1] + v.y * m[1][1] + m[3][1]);

// This is Skia's behavior, but it may be reasonable to allow UB for the w=0
// case.
if (w != 0) {
w = 1 / w;
}
return result * w;
}

void main() {
vec2 screen_offset =
round(project(frame_info.entity_transform, frame_info.offset));

// For each glyph, we compute two rectangles. One for the vertex positions
// and one for the texture coordinates (UVs).
vec2 uv_origin = (atlas_glyph_bounds.xy - vec2(0.5)) / frame_info.atlas_size;
vec2 uv_size = (atlas_glyph_bounds.zw + vec2(1)) / frame_info.atlas_size;

// Rounding here prevents most jitter between glyphs in the run when
// nearest sampling.
mat4 basis_transform = basis(frame_info.entity_transform);
vec2 screen_glyph_position =
screen_offset +
round(project(basis_transform, (glyph_position + glyph_bounds.xy)));

vec4 position;
if (frame_info.is_translation_scale == 1.0) {
// Rouding up here prevents the bounds from becoming 1 pixel too small
// when nearest sampling. This path breaks down for projections.
position = vec4(
screen_glyph_position +
ceil(project(basis_transform, unit_position * glyph_bounds.zw)),
0.0, 1.0);
} else {
position = frame_info.entity_transform *
vec4(frame_info.offset + glyph_position + glyph_bounds.xy +
unit_position * glyph_bounds.zw,
0.0, 1.0);
}

gl_Position = frame_info.mvp * position;
v_uv = uv;
v_uv = uv_origin + unit_position * uv_size;
}
Loading

0 comments on commit 7a4a267

Please sign in to comment.