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

Commit 38fef05

Browse files
authored
[Impeller] SDF text rendering (#36171)
1 parent 0a6360d commit 38fef05

26 files changed

+470
-150
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.frag
656656
FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.vert
657657
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag
658658
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert
659+
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.frag
660+
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.vert
659661
FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.vert
660662
FILE: ../../../flutter/impeller/entity/shaders/linear_gradient_fill.frag
661663
FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.frag

impeller/compiler/shader_lib/impeller/transform.glsl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,24 @@ vec2 IPVec2TransformPosition(mat4 matrix, vec2 point) {
1111
return transformed.xy / transformed.w;
1212
}
1313

14+
// Returns the transformed gl_Position for a given glyph position in a glyph
15+
// atlas.
16+
vec4 IPPositionForGlyphPosition(mat4 mvp, vec2 unit_vertex, vec2 glyph_position, vec2 glyph_size) {
17+
vec4 translate = mvp[0] * glyph_position.x
18+
+ mvp[1] * glyph_position.y
19+
+ mvp[3];
20+
mat4 translated_mvp = mat4(
21+
mvp[0],
22+
mvp[1],
23+
mvp[2],
24+
vec4(
25+
translate.xyz,
26+
mvp[3].w
27+
)
28+
);
29+
return translated_mvp *
30+
vec4(unit_vertex.x * glyph_size.x,
31+
unit_vertex.y * glyph_size.y, 0.0, 1.0);
32+
}
33+
1434
#endif

impeller/entity/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ impeller_shaders("entity_shaders") {
3636
"shaders/gaussian_blur.vert",
3737
"shaders/glyph_atlas.frag",
3838
"shaders/glyph_atlas.vert",
39+
"shaders/glyph_atlas_sdf.frag",
40+
"shaders/glyph_atlas_sdf.vert",
3941
"shaders/gradient_fill.vert",
4042
"shaders/linear_to_srgb_filter.frag",
4143
"shaders/linear_to_srgb_filter.vert",

impeller/entity/contents/content_context.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
207207
CreateDefaultPipeline<SolidStrokePipeline>(*context_);
208208
glyph_atlas_pipelines_[{}] =
209209
CreateDefaultPipeline<GlyphAtlasPipeline>(*context_);
210+
glyph_atlas_sdf_pipelines_[{}] =
211+
CreateDefaultPipeline<GlyphAtlasSdfPipeline>(*context_);
210212
vertices_pipelines_[{}] = CreateDefaultPipeline<VerticesPipeline>(*context_);
211213
atlas_pipelines_[{}] = CreateDefaultPipeline<AtlasPipeline>(*context_);
212214

impeller/entity/contents/content_context.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include "impeller/entity/gaussian_blur.vert.h"
4141
#include "impeller/entity/glyph_atlas.frag.h"
4242
#include "impeller/entity/glyph_atlas.vert.h"
43+
#include "impeller/entity/glyph_atlas_sdf.frag.h"
44+
#include "impeller/entity/glyph_atlas_sdf.vert.h"
4345
#include "impeller/entity/gradient_fill.vert.h"
4446
#include "impeller/entity/linear_gradient_fill.frag.h"
4547
#include "impeller/entity/linear_to_srgb_filter.frag.h"
@@ -144,6 +146,8 @@ using SolidStrokePipeline =
144146
RenderPipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
145147
using GlyphAtlasPipeline =
146148
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
149+
using GlyphAtlasSdfPipeline =
150+
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
147151
using VerticesPipeline =
148152
RenderPipelineT<VerticesVertexShader, VerticesFragmentShader>;
149153
using AtlasPipeline =
@@ -272,6 +276,11 @@ class ContentContext {
272276
return GetPipeline(glyph_atlas_pipelines_, opts);
273277
}
274278

279+
std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasSdfPipeline(
280+
ContentContextOptions opts) const {
281+
return GetPipeline(glyph_atlas_sdf_pipelines_, opts);
282+
}
283+
275284
std::shared_ptr<Pipeline<PipelineDescriptor>> GetVerticesPipeline(
276285
ContentContextOptions opts) const {
277286
return GetPipeline(vertices_pipelines_, opts);
@@ -399,6 +408,7 @@ class ContentContext {
399408
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
400409
mutable Variants<ClipPipeline> clip_pipelines_;
401410
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
411+
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
402412
mutable Variants<VerticesPipeline> vertices_pipelines_;
403413
mutable Variants<AtlasPipeline> atlas_pipelines_;
404414
// Advanced blends.

impeller/entity/contents/text_contents.cc

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
#include "impeller/entity/contents/text_contents.h"
66

7-
#include <iostream>
87
#include <optional>
8+
#include <type_traits>
99

1010
#include "impeller/entity/contents/content_context.h"
1111
#include "impeller/entity/entity.h"
@@ -28,22 +28,16 @@ void TextContents::SetTextFrame(TextFrame frame) {
2828
frame_ = std::move(frame);
2929
}
3030

31-
void TextContents::SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas) {
32-
atlas_ = std::move(atlas);
33-
}
34-
3531
void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
36-
atlas_ = std::move(atlas);
32+
lazy_atlas_ = std::move(atlas);
3733
}
3834

3935
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
36+
GlyphAtlas::Type type,
4037
std::shared_ptr<Context> context) const {
41-
if (auto lazy_atlas = std::get_if<std::shared_ptr<LazyGlyphAtlas>>(&atlas_)) {
42-
return lazy_atlas->get()->CreateOrGetGlyphAtlas(context);
43-
}
44-
45-
if (auto atlas = std::get_if<std::shared_ptr<GlyphAtlas>>(&atlas_)) {
46-
return *atlas;
38+
FML_DCHECK(lazy_atlas_);
39+
if (lazy_atlas_) {
40+
return lazy_atlas_->CreateOrGetGlyphAtlas(type, context);
4741
}
4842

4943
return nullptr;
@@ -61,33 +55,19 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
6155
return bounds->TransformBounds(entity.GetTransformation());
6256
}
6357

64-
bool TextContents::Render(const ContentContext& renderer,
65-
const Entity& entity,
66-
RenderPass& pass) const {
67-
if (color_.IsTransparent()) {
68-
return true;
69-
}
70-
71-
auto atlas = ResolveAtlas(renderer.GetContext());
72-
73-
if (!atlas || !atlas->IsValid()) {
74-
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
75-
return false;
76-
}
77-
78-
using VS = GlyphAtlasPipeline::VertexShader;
79-
using FS = GlyphAtlasPipeline::FragmentShader;
80-
81-
// Information shared by all glyph draw calls.
82-
Command cmd;
83-
cmd.label = "TextFrame";
84-
cmd.primitive_type = PrimitiveType::kTriangle;
85-
cmd.pipeline =
86-
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
87-
cmd.stencil_reference = entity.GetStencilDepth();
58+
template <class TPipeline>
59+
static bool CommonRender(const ContentContext& renderer,
60+
const Entity& entity,
61+
RenderPass& pass,
62+
const Color& color,
63+
const TextFrame& frame,
64+
std::shared_ptr<GlyphAtlas> atlas,
65+
Command& cmd) {
66+
using VS = typename TPipeline::VertexShader;
67+
using FS = typename TPipeline::FragmentShader;
8868

8969
// Common vertex uniforms for all glyphs.
90-
VS::FrameInfo frame_info;
70+
typename VS::FrameInfo frame_info;
9171
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
9272
entity.GetTransformation();
9373
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
@@ -96,8 +76,8 @@ bool TextContents::Render(const ContentContext& renderer,
9676
sampler_desc.min_filter = MinMagFilter::kLinear;
9777
sampler_desc.mag_filter = MinMagFilter::kLinear;
9878

99-
FS::FragInfo frag_info;
100-
frag_info.text_color = ToVector(color_.Premultiply());
79+
typename FS::FragInfo frag_info;
80+
frag_info.text_color = ToVector(color.Premultiply());
10181
frag_info.atlas_size =
10282
Point{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
10383
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
@@ -122,16 +102,15 @@ bool TextContents::Render(const ContentContext& renderer,
122102
{0, 0}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 1},
123103
};
124104

125-
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
126-
for (const auto& run : frame_.GetRuns()) {
105+
VertexBufferBuilder<typename VS::PerVertexData> vertex_builder;
106+
for (const auto& run : frame.GetRuns()) {
127107
auto font = run.GetFont();
128108
auto glyph_size = ISize::Ceil(font.GetMetrics().GetBoundingBox().size);
129109
for (const auto& glyph_position : run.GetGlyphPositions()) {
130110
FontGlyphPair font_glyph_pair{font, glyph_position.glyph};
131-
auto color_glyph =
132-
atlas->IsColorFontGlyphPair(font_glyph_pair) ? 1.0 : 0.0;
111+
133112
for (const auto& point : unit_vertex_points) {
134-
VS::PerVertexData vtx;
113+
typename VS::PerVertexData vtx;
135114
vtx.unit_vertex = point;
136115

137116
auto atlas_glyph_pos = atlas->FindFontGlyphPosition(font_glyph_pair);
@@ -149,7 +128,10 @@ bool TextContents::Render(const ContentContext& renderer,
149128
1 / atlas_glyph_pos->size.height};
150129
vtx.atlas_glyph_size =
151130
Point{atlas_glyph_pos->size.width, atlas_glyph_pos->size.height};
152-
vtx.color_glyph = color_glyph;
131+
if constexpr (std::is_same_v<TPipeline, GlyphAtlasPipeline>) {
132+
vtx.color_glyph =
133+
glyph_position.glyph.type == Glyph::Type::kBitmap ? 1.0 : 0.0;
134+
}
153135
vertex_builder.AppendVertex(std::move(vtx));
154136
}
155137
}
@@ -165,4 +147,55 @@ bool TextContents::Render(const ContentContext& renderer,
165147
return true;
166148
}
167149

150+
bool TextContents::RenderSdf(const ContentContext& renderer,
151+
const Entity& entity,
152+
RenderPass& pass) const {
153+
auto atlas = ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
154+
renderer.GetContext());
155+
156+
if (!atlas || !atlas->IsValid()) {
157+
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
158+
return false;
159+
}
160+
161+
// Information shared by all glyph draw calls.
162+
Command cmd;
163+
cmd.label = "TextFrameSDF";
164+
cmd.primitive_type = PrimitiveType::kTriangle;
165+
cmd.pipeline =
166+
renderer.GetGlyphAtlasSdfPipeline(OptionsFromPassAndEntity(pass, entity));
167+
cmd.stencil_reference = entity.GetStencilDepth();
168+
169+
return CommonRender<GlyphAtlasSdfPipeline>(renderer, entity, pass, color_,
170+
frame_, atlas, cmd);
171+
}
172+
173+
bool TextContents::Render(const ContentContext& renderer,
174+
const Entity& entity,
175+
RenderPass& pass) const {
176+
if (color_.IsTransparent()) {
177+
return true;
178+
}
179+
180+
auto atlas = ResolveAtlas(frame_.HasColor() ? GlyphAtlas::Type::kColorBitmap
181+
: GlyphAtlas::Type::kAlphaBitmap,
182+
renderer.GetContext());
183+
184+
if (!atlas || !atlas->IsValid()) {
185+
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
186+
return false;
187+
}
188+
189+
// Information shared by all glyph draw calls.
190+
Command cmd;
191+
cmd.label = "TextFrame";
192+
cmd.primitive_type = PrimitiveType::kTriangle;
193+
cmd.pipeline =
194+
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
195+
cmd.stencil_reference = entity.GetStencilDepth();
196+
197+
return CommonRender<GlyphAtlasPipeline>(renderer, entity, pass, color_,
198+
frame_, atlas, cmd);
199+
}
200+
168201
} // namespace impeller

impeller/entity/contents/text_contents.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
#include "flutter/fml/macros.h"
1313
#include "impeller/entity/contents/contents.h"
1414
#include "impeller/geometry/color.h"
15+
#include "impeller/typographer/glyph_atlas.h"
1516
#include "impeller/typographer/text_frame.h"
1617

1718
namespace impeller {
1819

19-
class GlyphAtlas;
2020
class LazyGlyphAtlas;
2121
class Context;
2222

@@ -28,8 +28,6 @@ class TextContents final : public Contents {
2828

2929
void SetTextFrame(TextFrame frame);
3030

31-
void SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
32-
3331
void SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas);
3432

3533
void SetColor(Color color);
@@ -42,14 +40,18 @@ class TextContents final : public Contents {
4240
const Entity& entity,
4341
RenderPass& pass) const override;
4442

43+
// TODO(dnfield): remove this https://github.com/flutter/flutter/issues/111640
44+
bool RenderSdf(const ContentContext& renderer,
45+
const Entity& entity,
46+
RenderPass& pass) const;
47+
4548
private:
4649
TextFrame frame_;
4750
Color color_;
48-
mutable std::variant<std::shared_ptr<GlyphAtlas>,
49-
std::shared_ptr<LazyGlyphAtlas>>
50-
atlas_;
51+
mutable std::shared_ptr<LazyGlyphAtlas> lazy_atlas_;
5152

5253
std::shared_ptr<GlyphAtlas> ResolveAtlas(
54+
GlyphAtlas::Type type,
5355
std::shared_ptr<Context> context) const;
5456

5557
FML_DISALLOW_COPY_AND_ASSIGN(TextContents);

impeller/entity/entity_unittests.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "impeller/entity/contents/rrect_shadow_contents.h"
2121
#include "impeller/entity/contents/solid_color_contents.h"
2222
#include "impeller/entity/contents/solid_stroke_contents.h"
23+
#include "impeller/entity/contents/text_contents.h"
2324
#include "impeller/entity/contents/texture_contents.h"
2425
#include "impeller/entity/contents/vertices_contents.h"
2526
#include "impeller/entity/entity.h"
@@ -35,8 +36,11 @@
3536
#include "impeller/renderer/render_pass.h"
3637
#include "impeller/renderer/vertex_buffer_builder.h"
3738
#include "impeller/tessellator/tessellator.h"
39+
#include "impeller/typographer/backends/skia/text_frame_skia.h"
40+
#include "impeller/typographer/backends/skia/text_render_context_skia.h"
3841
#include "include/core/SkBlendMode.h"
3942
#include "third_party/imgui/imgui.h"
43+
#include "third_party/skia/include/core/SkTextBlob.h"
4044

4145
namespace impeller {
4246
namespace testing {
@@ -1976,5 +1980,31 @@ TEST_P(EntityTest, TTTBlendColor) {
19761980
}
19771981
}
19781982

1983+
TEST_P(EntityTest, SdfText) {
1984+
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
1985+
SkFont font;
1986+
font.setSize(30);
1987+
auto blob = SkTextBlob::MakeFromString(
1988+
"the quick brown fox jumped over the lazy dog (but with sdf).", font);
1989+
auto frame = TextFrameFromTextBlob(blob);
1990+
auto lazy_glyph_atlas = std::make_shared<LazyGlyphAtlas>();
1991+
lazy_glyph_atlas->AddTextFrame(frame);
1992+
1993+
auto text_contents = std::make_shared<TextContents>();
1994+
text_contents->SetTextFrame(std::move(frame));
1995+
text_contents->SetGlyphAtlas(std::move(lazy_glyph_atlas));
1996+
text_contents->SetColor(Color(1.0, 0.0, 0.0, 1.0));
1997+
Entity entity;
1998+
entity.SetTransformation(
1999+
Matrix::MakeTranslation(Vector3{200.0, 200.0, 0.0}) *
2000+
Matrix::MakeScale(GetContentScale()));
2001+
entity.SetContents(text_contents);
2002+
2003+
// Force SDF rendering.
2004+
return text_contents->RenderSdf(context, entity, pass);
2005+
};
2006+
ASSERT_TRUE(OpenPlaygroundHere(callback));
2007+
}
2008+
19792009
} // namespace testing
19802010
} // namespace impeller

0 commit comments

Comments
 (0)