[Impeller] SDF text rendering (flutter/engine#36171)

This commit is contained in:
Dan Field 2022-09-20 18:19:07 -07:00 committed by GitHub
parent 232882b9a7
commit c4d33221d8
26 changed files with 470 additions and 150 deletions

View File

@ -656,6 +656,8 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.frag
FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.vert
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.frag
FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_sdf.vert
FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.vert
FILE: ../../../flutter/impeller/entity/shaders/linear_gradient_fill.frag
FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.frag

View File

@ -11,4 +11,24 @@ vec2 IPVec2TransformPosition(mat4 matrix, vec2 point) {
return transformed.xy / transformed.w;
}
// Returns the transformed gl_Position for a given glyph position in a glyph
// atlas.
vec4 IPPositionForGlyphPosition(mat4 mvp, vec2 unit_vertex, vec2 glyph_position, vec2 glyph_size) {
vec4 translate = mvp[0] * glyph_position.x
+ mvp[1] * glyph_position.y
+ mvp[3];
mat4 translated_mvp = mat4(
mvp[0],
mvp[1],
mvp[2],
vec4(
translate.xyz,
mvp[3].w
)
);
return translated_mvp *
vec4(unit_vertex.x * glyph_size.x,
unit_vertex.y * glyph_size.y, 0.0, 1.0);
}
#endif

View File

@ -36,6 +36,8 @@ impeller_shaders("entity_shaders") {
"shaders/gaussian_blur.vert",
"shaders/glyph_atlas.frag",
"shaders/glyph_atlas.vert",
"shaders/glyph_atlas_sdf.frag",
"shaders/glyph_atlas_sdf.vert",
"shaders/gradient_fill.vert",
"shaders/linear_to_srgb_filter.frag",
"shaders/linear_to_srgb_filter.vert",

View File

@ -207,6 +207,8 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
CreateDefaultPipeline<SolidStrokePipeline>(*context_);
glyph_atlas_pipelines_[{}] =
CreateDefaultPipeline<GlyphAtlasPipeline>(*context_);
glyph_atlas_sdf_pipelines_[{}] =
CreateDefaultPipeline<GlyphAtlasSdfPipeline>(*context_);
vertices_pipelines_[{}] = CreateDefaultPipeline<VerticesPipeline>(*context_);
atlas_pipelines_[{}] = CreateDefaultPipeline<AtlasPipeline>(*context_);

View File

@ -40,6 +40,8 @@
#include "impeller/entity/gaussian_blur.vert.h"
#include "impeller/entity/glyph_atlas.frag.h"
#include "impeller/entity/glyph_atlas.vert.h"
#include "impeller/entity/glyph_atlas_sdf.frag.h"
#include "impeller/entity/glyph_atlas_sdf.vert.h"
#include "impeller/entity/gradient_fill.vert.h"
#include "impeller/entity/linear_gradient_fill.frag.h"
#include "impeller/entity/linear_to_srgb_filter.frag.h"
@ -144,6 +146,8 @@ using SolidStrokePipeline =
RenderPipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
using GlyphAtlasPipeline =
RenderPipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
using GlyphAtlasSdfPipeline =
RenderPipelineT<GlyphAtlasSdfVertexShader, GlyphAtlasSdfFragmentShader>;
using VerticesPipeline =
RenderPipelineT<VerticesVertexShader, VerticesFragmentShader>;
using AtlasPipeline =
@ -272,6 +276,11 @@ class ContentContext {
return GetPipeline(glyph_atlas_pipelines_, opts);
}
std::shared_ptr<Pipeline<PipelineDescriptor>> GetGlyphAtlasSdfPipeline(
ContentContextOptions opts) const {
return GetPipeline(glyph_atlas_sdf_pipelines_, opts);
}
std::shared_ptr<Pipeline<PipelineDescriptor>> GetVerticesPipeline(
ContentContextOptions opts) const {
return GetPipeline(vertices_pipelines_, opts);
@ -399,6 +408,7 @@ class ContentContext {
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
mutable Variants<GlyphAtlasSdfPipeline> glyph_atlas_sdf_pipelines_;
mutable Variants<VerticesPipeline> vertices_pipelines_;
mutable Variants<AtlasPipeline> atlas_pipelines_;
// Advanced blends.

View File

@ -4,8 +4,8 @@
#include "impeller/entity/contents/text_contents.h"
#include <iostream>
#include <optional>
#include <type_traits>
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/entity.h"
@ -28,22 +28,16 @@ void TextContents::SetTextFrame(TextFrame frame) {
frame_ = std::move(frame);
}
void TextContents::SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas) {
atlas_ = std::move(atlas);
}
void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
atlas_ = std::move(atlas);
lazy_atlas_ = std::move(atlas);
}
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
GlyphAtlas::Type type,
std::shared_ptr<Context> context) const {
if (auto lazy_atlas = std::get_if<std::shared_ptr<LazyGlyphAtlas>>(&atlas_)) {
return lazy_atlas->get()->CreateOrGetGlyphAtlas(context);
}
if (auto atlas = std::get_if<std::shared_ptr<GlyphAtlas>>(&atlas_)) {
return *atlas;
FML_DCHECK(lazy_atlas_);
if (lazy_atlas_) {
return lazy_atlas_->CreateOrGetGlyphAtlas(type, context);
}
return nullptr;
@ -61,33 +55,19 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
return bounds->TransformBounds(entity.GetTransformation());
}
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (color_.IsTransparent()) {
return true;
}
auto atlas = ResolveAtlas(renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
return false;
}
using VS = GlyphAtlasPipeline::VertexShader;
using FS = GlyphAtlasPipeline::FragmentShader;
// Information shared by all glyph draw calls.
Command cmd;
cmd.label = "TextFrame";
cmd.primitive_type = PrimitiveType::kTriangle;
cmd.pipeline =
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
template <class TPipeline>
static bool CommonRender(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Color& color,
const TextFrame& frame,
std::shared_ptr<GlyphAtlas> atlas,
Command& cmd) {
using VS = typename TPipeline::VertexShader;
using FS = typename TPipeline::FragmentShader;
// Common vertex uniforms for all glyphs.
VS::FrameInfo frame_info;
typename VS::FrameInfo frame_info;
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
@ -96,8 +76,8 @@ bool TextContents::Render(const ContentContext& renderer,
sampler_desc.min_filter = MinMagFilter::kLinear;
sampler_desc.mag_filter = MinMagFilter::kLinear;
FS::FragInfo frag_info;
frag_info.text_color = ToVector(color_.Premultiply());
typename FS::FragInfo frag_info;
frag_info.text_color = ToVector(color.Premultiply());
frag_info.atlas_size =
Point{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
@ -122,16 +102,15 @@ bool TextContents::Render(const ContentContext& renderer,
{0, 0}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 1},
};
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
for (const auto& run : frame_.GetRuns()) {
VertexBufferBuilder<typename VS::PerVertexData> vertex_builder;
for (const auto& run : frame.GetRuns()) {
auto font = run.GetFont();
auto glyph_size = ISize::Ceil(font.GetMetrics().GetBoundingBox().size);
for (const auto& glyph_position : run.GetGlyphPositions()) {
FontGlyphPair font_glyph_pair{font, glyph_position.glyph};
auto color_glyph =
atlas->IsColorFontGlyphPair(font_glyph_pair) ? 1.0 : 0.0;
for (const auto& point : unit_vertex_points) {
VS::PerVertexData vtx;
typename VS::PerVertexData vtx;
vtx.unit_vertex = point;
auto atlas_glyph_pos = atlas->FindFontGlyphPosition(font_glyph_pair);
@ -149,7 +128,10 @@ bool TextContents::Render(const ContentContext& renderer,
1 / atlas_glyph_pos->size.height};
vtx.atlas_glyph_size =
Point{atlas_glyph_pos->size.width, atlas_glyph_pos->size.height};
vtx.color_glyph = color_glyph;
if constexpr (std::is_same_v<TPipeline, GlyphAtlasPipeline>) {
vtx.color_glyph =
glyph_position.glyph.type == Glyph::Type::kBitmap ? 1.0 : 0.0;
}
vertex_builder.AppendVertex(std::move(vtx));
}
}
@ -165,4 +147,55 @@ bool TextContents::Render(const ContentContext& renderer,
return true;
}
bool TextContents::RenderSdf(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto atlas = ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
return false;
}
// Information shared by all glyph draw calls.
Command cmd;
cmd.label = "TextFrameSDF";
cmd.primitive_type = PrimitiveType::kTriangle;
cmd.pipeline =
renderer.GetGlyphAtlasSdfPipeline(OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
return CommonRender<GlyphAtlasSdfPipeline>(renderer, entity, pass, color_,
frame_, atlas, cmd);
}
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
if (color_.IsTransparent()) {
return true;
}
auto atlas = ResolveAtlas(frame_.HasColor() ? GlyphAtlas::Type::kColorBitmap
: GlyphAtlas::Type::kAlphaBitmap,
renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
return false;
}
// Information shared by all glyph draw calls.
Command cmd;
cmd.label = "TextFrame";
cmd.primitive_type = PrimitiveType::kTriangle;
cmd.pipeline =
renderer.GetGlyphAtlasPipeline(OptionsFromPassAndEntity(pass, entity));
cmd.stencil_reference = entity.GetStencilDepth();
return CommonRender<GlyphAtlasPipeline>(renderer, entity, pass, color_,
frame_, atlas, cmd);
}
} // namespace impeller

View File

@ -12,11 +12,11 @@
#include "flutter/fml/macros.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/geometry/color.h"
#include "impeller/typographer/glyph_atlas.h"
#include "impeller/typographer/text_frame.h"
namespace impeller {
class GlyphAtlas;
class LazyGlyphAtlas;
class Context;
@ -28,8 +28,6 @@ class TextContents final : public Contents {
void SetTextFrame(TextFrame frame);
void SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
void SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas);
void SetColor(Color color);
@ -42,14 +40,18 @@ class TextContents final : public Contents {
const Entity& entity,
RenderPass& pass) const override;
// TODO(dnfield): remove this https://github.com/flutter/flutter/issues/111640
bool RenderSdf(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const;
private:
TextFrame frame_;
Color color_;
mutable std::variant<std::shared_ptr<GlyphAtlas>,
std::shared_ptr<LazyGlyphAtlas>>
atlas_;
mutable std::shared_ptr<LazyGlyphAtlas> lazy_atlas_;
std::shared_ptr<GlyphAtlas> ResolveAtlas(
GlyphAtlas::Type type,
std::shared_ptr<Context> context) const;
FML_DISALLOW_COPY_AND_ASSIGN(TextContents);

View File

@ -20,6 +20,7 @@
#include "impeller/entity/contents/rrect_shadow_contents.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/solid_stroke_contents.h"
#include "impeller/entity/contents/text_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/contents/vertices_contents.h"
#include "impeller/entity/entity.h"
@ -35,8 +36,11 @@
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/vertex_buffer_builder.h"
#include "impeller/tessellator/tessellator.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "impeller/typographer/backends/skia/text_render_context_skia.h"
#include "include/core/SkBlendMode.h"
#include "third_party/imgui/imgui.h"
#include "third_party/skia/include/core/SkTextBlob.h"
namespace impeller {
namespace testing {
@ -1976,5 +1980,31 @@ TEST_P(EntityTest, TTTBlendColor) {
}
}
TEST_P(EntityTest, SdfText) {
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
SkFont font;
font.setSize(30);
auto blob = SkTextBlob::MakeFromString(
"the quick brown fox jumped over the lazy dog (but with sdf).", font);
auto frame = TextFrameFromTextBlob(blob);
auto lazy_glyph_atlas = std::make_shared<LazyGlyphAtlas>();
lazy_glyph_atlas->AddTextFrame(frame);
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(std::move(frame));
text_contents->SetGlyphAtlas(std::move(lazy_glyph_atlas));
text_contents->SetColor(Color(1.0, 0.0, 0.0, 1.0));
Entity entity;
entity.SetTransformation(
Matrix::MakeTranslation(Vector3{200.0, 200.0, 0.0}) *
Matrix::MakeScale(GetContentScale()));
entity.SetContents(text_contents);
// Force SDF rendering.
return text_contents->RenderSdf(context, entity, pass);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
} // namespace testing
} // namespace impeller

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <impeller/transform.glsl>
uniform FrameInfo {
mat4 mvp;
} frame_info;
@ -19,22 +21,7 @@ out vec2 v_atlas_glyph_size;
out float v_color_glyph;
void main() {
vec4 translate = frame_info.mvp[0] * glyph_position.x
+ frame_info.mvp[1] * glyph_position.y
+ frame_info.mvp[3];
mat4 translated_mvp = mat4(
frame_info.mvp[0],
frame_info.mvp[1],
frame_info.mvp[2],
vec4(
translate.xyz,
frame_info.mvp[3].w
)
);
gl_Position = translated_mvp
* vec4(unit_vertex.x * glyph_size.x,
unit_vertex.y * glyph_size.y, 0.0, 1.0);
gl_Position = IPPositionForGlyphPosition(frame_info.mvp, unit_vertex, glyph_position, glyph_size);
v_unit_vertex = unit_vertex;
v_atlas_position = atlas_position;
v_atlas_glyph_size = atlas_glyph_size;

View File

@ -0,0 +1,34 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
uniform sampler2D glyph_atlas_sampler;
uniform FragInfo {
vec2 atlas_size;
vec4 text_color;
} frag_info;
in vec2 v_unit_vertex;
in vec2 v_atlas_position;
in vec2 v_atlas_glyph_size;
out vec4 frag_color;
void main() {
vec2 scale_perspective = v_atlas_glyph_size / frag_info.atlas_size;
vec2 offset = v_atlas_position / frag_info.atlas_size;
// Inspired by Metal by Example's SDF text rendering shader:
// https://github.com/metal-by-example/sample-code/blob/master/objc/12-TextRendering/TextRendering/Shaders.metal
// Outline of glyph is the isocontour with value 50%
float edge_distance = 0.5;
// Sample the signed-distance field to find distance from this fragment to the glyph outline
float sample_distance = texture(glyph_atlas_sampler, v_unit_vertex * scale_perspective + offset).a;
// Use local automatic gradients to find anti-aliased anisotropic edge width, cf. Gustavson 2012
float edge_width = length(vec2(dFdx(sample_distance), dFdy(sample_distance)));
// Smooth the glyph edge by interpolating across the boundary in a band with the width determined above
float insideness = smoothstep(edge_distance - edge_width, edge_distance + edge_width, sample_distance);
frag_color = frag_info.text_color * insideness;
}

View File

@ -0,0 +1,26 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <impeller/transform.glsl>
uniform FrameInfo {
mat4 mvp;
} frame_info;
in vec2 unit_vertex;
in vec2 glyph_position;
in vec2 glyph_size;
in vec2 atlas_position;
in vec2 atlas_glyph_size;
out vec2 v_unit_vertex;
out vec2 v_atlas_position;
out vec2 v_atlas_glyph_size;
void main() {
gl_Position = IPPositionForGlyphPosition(frame_info.mvp, unit_vertex, glyph_position, glyph_size);
v_unit_vertex = unit_vertex;
v_atlas_position = atlas_position;
v_atlas_glyph_size = atlas_glyph_size;
}

View File

@ -8,6 +8,7 @@
#include "impeller/typographer/backends/skia/typeface_skia.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkFontMetrics.h"
#include "third_party/skia/src/core/SkStrikeSpec.h" // nogncheck
#include "third_party/skia/src/core/SkTextBlobPriv.h" // nogncheck
namespace impeller {
@ -38,6 +39,12 @@ TextFrame TextFrameFromTextBlob(sk_sp<SkTextBlob> blob, Scalar scale) {
for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) {
TextRun text_run(ToFont(run.font(), scale));
// TODO(jonahwilliams): ask Skia for a public API to look this up.
// https://github.com/flutter/flutter/issues/112005
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(run.font());
SkBulkGlyphMetricsAndPaths paths{strikeSpec};
const auto glyph_count = run.glyphCount();
const auto* glyphs = run.glyphs();
switch (run.positioning()) {
@ -52,7 +59,12 @@ TextFrame TextFrameFromTextBlob(sk_sp<SkTextBlob> blob, Scalar scale) {
// kFull_Positioning has two scalars per glyph.
const SkPoint* glyph_points = run.points();
const auto* point = glyph_points + i;
text_run.AddGlyph(glyphs[i], Point{point->x(), point->y()});
Glyph::Type type = paths.glyph(glyphs[i])->isColor()
? Glyph::Type::kBitmap
: Glyph::Type::kPath;
text_run.AddGlyph(Glyph{glyphs[i], type},
Point{point->x(), point->y()});
}
break;
case SkTextBlobRunIterator::kRSXform_Positioning:

View File

@ -15,9 +15,8 @@
#include "third_party/skia/include/core/SkFontMetrics.h"
#include "third_party/skia/include/core/SkRSXform.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/src/core/SkIPoint16.h" //nogncheck
#include "third_party/skia/src/core/SkStrikeSpec.h" // nogncheck
#include "third_party/skia/src/gpu/GrRectanizer.h" //nogncheck
#include "third_party/skia/src/core/SkIPoint16.h" // nogncheck
#include "third_party/skia/src/gpu/GrRectanizer.h" // nogncheck
namespace impeller {
@ -27,11 +26,15 @@ TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr<Context> context)
TextRenderContextSkia::~TextRenderContextSkia() = default;
static FontGlyphPair::Set CollectUniqueFontGlyphPairsSet(
GlyphAtlas::Type type,
TextRenderContext::FrameIterator frame_iterator) {
FontGlyphPair::Set set;
while (auto frame = frame_iterator()) {
for (const auto& run : frame->GetRuns()) {
auto font = run.GetFont();
// TODO(dnfield): If we're doing SDF here, we should be using a consistent
// point size.
// https://github.com/flutter/flutter/issues/112016
for (const auto& glyph_position : run.GetGlyphPositions()) {
set.insert({font, glyph_position.glyph});
}
@ -41,10 +44,11 @@ static FontGlyphPair::Set CollectUniqueFontGlyphPairsSet(
}
static FontGlyphPair::Vector CollectUniqueFontGlyphPairs(
GlyphAtlas::Type type,
TextRenderContext::FrameIterator frame_iterator) {
TRACE_EVENT0("impeller", __FUNCTION__);
FontGlyphPair::Vector vector;
auto set = CollectUniqueFontGlyphPairsSet(frame_iterator);
auto set = CollectUniqueFontGlyphPairsSet(type, frame_iterator);
vector.reserve(set.size());
for (const auto& item : set) {
vector.emplace_back(std::move(item));
@ -104,13 +108,137 @@ static size_t OptimumAtlasSizeForFontGlyphPairs(
return 0u;
}
/// Compute signed-distance field for an 8-bpp grayscale image (values greater
/// than 127 are considered "on") For details of this algorithm, see "The 'dead
/// reckoning' signed distance transform" [Grevera 2004]
static void ConvertBitmapToSignedDistanceField(uint8_t* pixels,
uint16_t width,
uint16_t height) {
if (!pixels || width == 0 || height == 0) {
return;
}
using ShortPoint = TPoint<uint16_t>;
// distance to nearest boundary point map
std::vector<Scalar> distance_map(width * height);
// nearest boundary point map
std::vector<ShortPoint> boundary_point_map(width * height);
// Some helpers for manipulating the above arrays
#define image(_x, _y) (pixels[(_y)*width + (_x)] > 0x7f)
#define distance(_x, _y) distance_map[(_y)*width + (_x)]
#define nearestpt(_x, _y) boundary_point_map[(_y)*width + (_x)]
const Scalar maxDist = hypot(width, height);
const Scalar distUnit = 1;
const Scalar distDiag = sqrt(2);
// Initialization phase: set all distances to "infinity"; zero out nearest
// boundary point map
for (uint16_t y = 0; y < height; ++y) {
for (uint16_t x = 0; x < width; ++x) {
distance(x, y) = maxDist;
nearestpt(x, y) = ShortPoint{0, 0};
}
}
// Immediate interior/exterior phase: mark all points along the boundary as
// such
for (uint16_t y = 1; y < height - 1; ++y) {
for (uint16_t x = 1; x < width - 1; ++x) {
bool inside = image(x, y);
if (image(x - 1, y) != inside || image(x + 1, y) != inside ||
image(x, y - 1) != inside || image(x, y + 1) != inside) {
distance(x, y) = 0;
nearestpt(x, y) = ShortPoint{x, y};
}
}
}
// Forward dead-reckoning pass
for (uint16_t y = 1; y < height - 2; ++y) {
for (uint16_t x = 1; x < width - 2; ++x) {
if (distance_map[(y - 1) * width + (x - 1)] + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x, y - 1) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x + 1, y - 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y - 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x - 1, y) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
}
}
// Backward dead-reckoning pass
for (uint16_t y = height - 2; y >= 1; --y) {
for (uint16_t x = width - 2; x >= 1; --x) {
if (distance(x + 1, y) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x - 1, y + 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x - 1, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x, y + 1) + distUnit < distance(x, y)) {
nearestpt(x, y) = nearestpt(x, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
if (distance(x + 1, y + 1) + distDiag < distance(x, y)) {
nearestpt(x, y) = nearestpt(x + 1, y + 1);
distance(x, y) = hypot(x - nearestpt(x, y).x, y - nearestpt(x, y).y);
}
}
}
// Interior distance negation pass; distances outside the figure are
// considered negative
// Also does final quantization.
for (uint16_t y = 0; y < height; ++y) {
for (uint16_t x = 0; x < width; ++x) {
if (!image(x, y)) {
distance(x, y) = -distance(x, y);
}
float norm_factor = 13.5;
float dist = distance(x, y);
float clamped_dist = fmax(-norm_factor, fmin(dist, norm_factor));
float scaled_dist = clamped_dist / norm_factor;
uint8_t quantized_value = ((scaled_dist + 1) / 2) * UINT8_MAX;
pixels[y * width + x] = quantized_value;
}
}
#undef image
#undef distance
#undef nearestpt
}
static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
size_t atlas_size) {
TRACE_EVENT0("impeller", __FUNCTION__);
auto bitmap = std::make_shared<SkBitmap>();
auto image_info = atlas.ContainsColorGlyph()
? SkImageInfo::MakeN32Premul(atlas_size, atlas_size)
: SkImageInfo::MakeA8(atlas_size, atlas_size);
SkImageInfo image_info;
switch (atlas.GetType()) {
case GlyphAtlas::Type::kSignedDistanceField:
case GlyphAtlas::Type::kAlphaBitmap:
image_info = SkImageInfo::MakeA8(atlas_size, atlas_size);
break;
case GlyphAtlas::Type::kColorBitmap:
image_info = SkImageInfo::MakeN32Premul(atlas_size, atlas_size);
break;
}
if (!bitmap->tryAllocPixels(image_info)) {
return nullptr;
}
@ -133,7 +261,6 @@ static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
SkFont sk_font(
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
metrics.point_size);
auto glyph_color = SK_ColorWHITE;
SkPaint glyph_paint;
@ -196,29 +323,20 @@ static std::shared_ptr<Texture> UploadGlyphTextureAtlas(
}
std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
GlyphAtlas::Type type,
FrameIterator frame_iterator) const {
TRACE_EVENT0("impeller", __FUNCTION__);
if (!IsValid()) {
return nullptr;
}
auto glyph_atlas = std::make_shared<GlyphAtlas>();
glyph_atlas->SetFontColorCallback([](const FontGlyphPair& font_glyph) {
// TODO(jonahwilliams): ask Skia for a public API to look this up.
SkFont sk_font(
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
font_glyph.font.GetMetrics().point_size);
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeWithNoDevice(sk_font);
SkBulkGlyphMetricsAndPaths paths{strikeSpec};
return paths.glyph(font_glyph.glyph.index)->isColor();
});
auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
// ---------------------------------------------------------------------------
// Step 1: Collect unique font-glyph pairs in the frame.
// ---------------------------------------------------------------------------
auto font_glyph_pairs = CollectUniqueFontGlyphPairs(frame_iterator);
auto font_glyph_pairs = CollectUniqueFontGlyphPairs(type, frame_iterator);
if (font_glyph_pairs.empty()) {
return glyph_atlas;
}
@ -262,9 +380,19 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
// ---------------------------------------------------------------------------
// Step 6: Upload the atlas as a texture.
// ---------------------------------------------------------------------------
auto format = glyph_atlas->ContainsColorGlyph()
? PixelFormat::kR8G8B8A8UNormInt
: PixelFormat::kA8UNormInt;
PixelFormat format;
switch (type) {
case GlyphAtlas::Type::kSignedDistanceField:
ConvertBitmapToSignedDistanceField(
reinterpret_cast<uint8_t*>(bitmap->getPixels()), atlas_size,
atlas_size);
case GlyphAtlas::Type::kAlphaBitmap:
format = PixelFormat::kA8UNormInt;
break;
case GlyphAtlas::Type::kColorBitmap:
format = PixelFormat::kR8G8B8A8UNormInt;
break;
}
auto texture = UploadGlyphTextureAtlas(GetContext()->GetResourceAllocator(),
bitmap, atlas_size, format);
if (!texture) {

View File

@ -17,6 +17,7 @@ class TextRenderContextSkia : public TextRenderContext {
// |TextRenderContext|
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
FrameIterator iterator) const override;
private:

View File

@ -15,9 +15,19 @@ namespace impeller {
/// @brief The glyph index in the typeface.
///
struct Glyph {
enum class Type {
kPath,
kBitmap,
};
uint16_t index = 0;
Glyph(uint16_t p_index) : index(p_index) {}
//------------------------------------------------------------------------------
/// @brief Whether the glyph is a path or a bitmap.
///
Type type = Type::kPath;
Glyph(uint16_t p_index, Type p_type) : index(p_index), type(p_type) {}
};
} // namespace impeller

View File

@ -6,7 +6,7 @@
namespace impeller {
GlyphAtlas::GlyphAtlas() = default;
GlyphAtlas::GlyphAtlas(Type type) : type_(type) {}
GlyphAtlas::~GlyphAtlas() = default;
@ -14,8 +14,8 @@ bool GlyphAtlas::IsValid() const {
return !!texture_;
}
bool GlyphAtlas::ContainsColorGlyph() const {
return has_color_glyph;
GlyphAtlas::Type GlyphAtlas::GetType() const {
return type_;
}
const std::shared_ptr<Texture>& GlyphAtlas::GetTexture() const {
@ -27,28 +27,9 @@ void GlyphAtlas::SetTexture(std::shared_ptr<Texture> texture) {
}
void GlyphAtlas::AddTypefaceGlyphPosition(FontGlyphPair pair, Rect rect) {
if (callback_.has_value()) {
auto has_color = callback_.value()(pair);
has_color_glyph |= has_color;
colors_[pair] = has_color;
}
positions_[pair] = rect;
}
void GlyphAtlas::SetFontColorCallback(
std::function<bool(const FontGlyphPair& pair)> callback) {
callback_ = std::move(callback);
}
bool GlyphAtlas::IsColorFontGlyphPair(const FontGlyphPair& pair) const {
auto found = colors_.find(pair);
if (found == colors_.end()) {
return false;
}
return found->second;
}
std::optional<Rect> GlyphAtlas::FindFontGlyphPosition(
const FontGlyphPair& pair) const {
auto found = positions_.find(pair);

View File

@ -11,6 +11,7 @@
#include "flutter/fml/macros.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/pipeline.h"
#include "impeller/renderer/texture.h"
#include "impeller/typographer/font_glyph_pair.h"
@ -23,19 +24,44 @@ namespace impeller {
///
class GlyphAtlas {
public:
//----------------------------------------------------------------------------
/// @brief Describes how the glyphs are represented in the texture.
enum class Type {
//--------------------------------------------------------------------------
/// The glyphs are represented at a fixed size in an 8-bit grayscale texture
/// where the value of each pixel represents a signed-distance field that
/// stores the glyph outlines.
///
kSignedDistanceField,
//--------------------------------------------------------------------------
/// The glyphs are reprsented at their requested size using only an 8-bit
/// alpha channel.
///
kAlphaBitmap,
//--------------------------------------------------------------------------
/// The glyphs are reprsented at their requested size using N32 premul
/// colors.
///
kColorBitmap,
};
//----------------------------------------------------------------------------
/// @brief Create an empty glyph atlas.
///
GlyphAtlas();
/// @param[in] type How the glyphs are represented in the texture.
///
explicit GlyphAtlas(Type type);
~GlyphAtlas();
bool IsValid() const;
//----------------------------------------------------------------------------
/// @brief Whether at least one font-glyph pair has colors.
/// @brief Describes how the glyphs are represented in the texture.
///
bool ContainsColorGlyph() const;
Type GetType() const;
//----------------------------------------------------------------------------
/// @brief Set the texture for the glyph atlas.
@ -44,20 +70,6 @@ class GlyphAtlas {
///
void SetTexture(std::shared_ptr<Texture> texture);
//----------------------------------------------------------------------------
/// @brief Set a callback that determines if a glyph-font pair
/// has color.
///
/// @param[in] callback The callback
///
void SetFontColorCallback(
std::function<bool(const FontGlyphPair& pair)> callback);
//----------------------------------------------------------------------------
/// @brief Whether the provided glyph-font pair contains color.
///
bool IsColorFontGlyphPair(const FontGlyphPair& pair) const;
//----------------------------------------------------------------------------
/// @brief Get the texture for the glyph atlas.
///
@ -104,9 +116,8 @@ class GlyphAtlas {
std::optional<Rect> FindFontGlyphPosition(const FontGlyphPair& pair) const;
private:
const Type type_;
std::shared_ptr<Texture> texture_;
std::optional<std::function<bool(const FontGlyphPair& pair)>> callback_;
bool has_color_glyph = false;
std::unordered_map<FontGlyphPair,
Rect,
@ -114,12 +125,6 @@ class GlyphAtlas {
FontGlyphPair::Equal>
positions_;
std::unordered_map<FontGlyphPair,
bool,
FontGlyphPair::Hash,
FontGlyphPair::Equal>
colors_;
FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlas);
};

View File

@ -14,14 +14,18 @@ LazyGlyphAtlas::LazyGlyphAtlas() = default;
LazyGlyphAtlas::~LazyGlyphAtlas() = default;
void LazyGlyphAtlas::AddTextFrame(TextFrame frame) {
FML_DCHECK(!atlas_);
FML_DCHECK(atlas_map_.empty());
frames_.emplace_back(std::move(frame));
}
std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<Context> context) const {
if (atlas_) {
return atlas_;
{
auto atlas_it = atlas_map_.find(type);
if (atlas_it != atlas_map_.end()) {
return atlas_it->second;
}
}
auto text_context = TextRenderContext::Create(std::move(context));
@ -37,13 +41,13 @@ std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
i++;
return &result;
};
auto atlas = text_context->CreateGlyphAtlas(iterator);
auto atlas = text_context->CreateGlyphAtlas(type, iterator);
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Could not create valid atlas.";
return nullptr;
}
atlas_ = std::move(atlas);
return atlas_;
atlas_map_[type] = atlas;
return atlas;
}
} // namespace impeller

View File

@ -4,6 +4,8 @@
#pragma once
#include <unordered_map>
#include "flutter/fml/macros.h"
#include "impeller/renderer/context.h"
#include "impeller/typographer/glyph_atlas.h"
@ -20,11 +22,13 @@ class LazyGlyphAtlas {
void AddTextFrame(TextFrame frame);
std::shared_ptr<GlyphAtlas> CreateOrGetGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<Context> context) const;
private:
std::vector<TextFrame> frames_;
mutable std::shared_ptr<GlyphAtlas> atlas_;
mutable std::unordered_map<GlyphAtlas::Type, std::shared_ptr<GlyphAtlas>>
atlas_map_;
FML_DISALLOW_COPY_AND_ASSIGN(LazyGlyphAtlas);
};

View File

@ -29,6 +29,7 @@ bool TextFrame::AddTextRun(TextRun run) {
if (!run.IsValid()) {
return false;
}
has_color_ |= run.HasColor();
runs_.emplace_back(std::move(run));
return true;
}
@ -41,4 +42,8 @@ const std::vector<TextRun>& TextFrame::GetRuns() const {
return runs_;
}
bool TextFrame::HasColor() const {
return has_color_;
}
} // namespace impeller

View File

@ -52,8 +52,13 @@ class TextFrame {
///
const std::vector<TextRun>& GetRuns() const;
//----------------------------------------------------------------------------
/// @brief Whether any run in this frame has color.
bool HasColor() const;
private:
std::vector<TextRun> runs_;
bool has_color_ = false;
};
} // namespace impeller

View File

@ -33,6 +33,7 @@ const std::shared_ptr<Context>& TextRenderContext::GetContext() const {
}
std::shared_ptr<GlyphAtlas> TextRenderContext::CreateGlyphAtlas(
GlyphAtlas::Type type,
const TextFrame& frame) const {
size_t count = 0;
FrameIterator iterator = [&]() -> const TextFrame* {
@ -42,7 +43,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContext::CreateGlyphAtlas(
}
return nullptr;
};
return CreateGlyphAtlas(iterator);
return CreateGlyphAtlas(type, iterator);
}
} // namespace impeller

View File

@ -38,10 +38,16 @@ class TextRenderContext {
const std::shared_ptr<Context>& GetContext() const;
using FrameIterator = std::function<const TextFrame*(void)>;
// TODO(dnfield): Callers should not need to know which type of atlas to
// create. https://github.com/flutter/flutter/issues/111640
virtual std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
FrameIterator iterator) const = 0;
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(const TextFrame& frame) const;
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(GlyphAtlas::Type type,
const TextFrame& frame) const;
protected:
//----------------------------------------------------------------------------

View File

@ -17,6 +17,7 @@ TextRun::~TextRun() = default;
bool TextRun::AddGlyph(Glyph glyph, Point position) {
glyphs_.emplace_back(GlyphPosition{glyph, position});
has_color_ |= glyph.type == Glyph::Type::kBitmap;
return true;
}
@ -36,4 +37,8 @@ const Font& TextRun::GetFont() const {
return font_;
}
bool TextRun::HasColor() const {
return has_color_;
}
} // namespace impeller

View File

@ -68,10 +68,15 @@ class TextRun {
///
const Font& GetFont() const;
//----------------------------------------------------------------------------
/// @brief Whether any glyph in this run has color.
bool HasColor() const;
private:
Font font_;
std::vector<GlyphPosition> glyphs_;
bool is_valid_ = false;
bool has_color_ = false;
};
} // namespace impeller

View File

@ -38,9 +38,9 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) {
SkFont sk_font;
auto blob = SkTextBlob::MakeFromString("hello", sk_font);
ASSERT_TRUE(blob);
auto atlas = context->CreateGlyphAtlas(TextFrameFromTextBlob(blob));
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_FALSE(atlas->ContainsColorGlyph());
OpenPlaygroundHere([](RenderTarget&) { return true; });
}