Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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