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
242 changes: 184 additions & 58 deletions impeller/typographer/backends/skia/text_render_context_skia.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@

namespace impeller {

// TODO(bdero): We might be able to remove this per-glyph padding if we fix
// the underlying causes of the overlap.
// https://github.com/flutter/flutter/issues/114563
constexpr auto kPadding = 2;

TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr<Context> context)
: TextRenderContext(std::move(context)) {}

Expand Down Expand Up @@ -58,33 +63,27 @@ static FontGlyphPair::Vector CollectUniqueFontGlyphPairs(
return vector;
}

static size_t PairsFitInAtlasOfSize(const FontGlyphPair::Vector& pairs,
const ISize& atlas_size,
std::vector<Rect>& glyph_positions) {
static size_t PairsFitInAtlasOfSize(
const FontGlyphPair::Vector& pairs,
const ISize& atlas_size,
std::vector<Rect>& glyph_positions,
const std::shared_ptr<GrRectanizer>& rect_packer) {
if (atlas_size.IsEmpty()) {
return false;
}

auto rect_packer = std::unique_ptr<GrRectanizer>(
GrRectanizer::Factory(atlas_size.width, atlas_size.height));

glyph_positions.clear();
glyph_positions.reserve(pairs.size());

// TODO(bdero): We might be able to remove this per-glyph padding if we fix
// the underlying causes of the overlap.
// https://github.com/flutter/flutter/issues/114563
constexpr auto padding = 2;

for (size_t i = 0; i < pairs.size(); i++) {
const auto& pair = pairs[i];

const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
SkIPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + padding, //
glyph_size.height + padding, //
&location_in_atlas //
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
&location_in_atlas //
)) {
return pairs.size() - i;
}
Expand All @@ -98,9 +97,48 @@ static size_t PairsFitInAtlasOfSize(const FontGlyphPair::Vector& pairs,
return 0;
}

static bool CanAppendToExistingAtlas(
const std::shared_ptr<GlyphAtlas>& atlas,
const FontGlyphPair::Vector& extra_pairs,
std::vector<Rect>& glyph_positions,
ISize atlas_size,
const std::shared_ptr<GrRectanizer>& rect_packer) {
TRACE_EVENT0("impeller", __FUNCTION__);
if (!rect_packer || atlas_size.IsEmpty()) {
return false;
}

// We assume that all existing glyphs will fit. After all, they fit before.
// The glyph_positions only contains the values for the additional glyphs
// from extra_pairs.
FML_DCHECK(glyph_positions.size() == 0);
glyph_positions.reserve(extra_pairs.size());
for (size_t i = 0; i < extra_pairs.size(); i++) {
const auto& pair = extra_pairs[i];

const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
SkIPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
&location_in_atlas //
)) {
return false;
}
glyph_positions.emplace_back(Rect::MakeXYWH(location_in_atlas.x(), //
location_in_atlas.y(), //
glyph_size.width, //
glyph_size.height //
));
}

return true;
}

static ISize OptimumAtlasSizeForFontGlyphPairs(
const FontGlyphPair::Vector& pairs,
std::vector<Rect>& glyph_positions) {
std::vector<Rect>& glyph_positions,
const std::shared_ptr<GlyphAtlasContext>& atlas_context) {
static constexpr auto kMinAtlasSize = 8u;
static constexpr auto kMaxAtlasSize = 4096u;

Expand All @@ -109,9 +147,13 @@ static ISize OptimumAtlasSizeForFontGlyphPairs(
ISize current_size(kMinAtlasSize, kMinAtlasSize);
size_t total_pairs = pairs.size() + 1;
do {
auto remaining_pairs =
PairsFitInAtlasOfSize(pairs, current_size, glyph_positions);
auto rect_packer = std::shared_ptr<GrRectanizer>(
GrRectanizer::Factory(current_size.width, current_size.height));

auto remaining_pairs = PairsFitInAtlasOfSize(pairs, current_size,
glyph_positions, rect_packer);
if (remaining_pairs == 0) {
atlas_context->UpdateRectPacker(rect_packer);
return current_size;
} else if (remaining_pairs < std::ceil(total_pairs / 2)) {
current_size = ISize::MakeWH(
Expand Down Expand Up @@ -243,6 +285,59 @@ static void ConvertBitmapToSignedDistanceField(uint8_t* pixels,
#undef nearestpt
}

static void DrawGlyph(SkCanvas* canvas,
const FontGlyphPair& font_glyph,
const Rect& location) {
const auto& metrics = font_glyph.font.GetMetrics();
const auto position = SkPoint::Make(location.origin.x / metrics.scale,
location.origin.y / metrics.scale);
SkGlyphID glyph_id = font_glyph.glyph.index;

SkFont sk_font(
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
metrics.point_size);
auto glyph_color = SK_ColorWHITE;

SkPaint glyph_paint;
glyph_paint.setColor(glyph_color);
canvas->resetMatrix();
canvas->scale(metrics.scale, metrics.scale);
canvas->drawGlyphs(
1u, // count
&glyph_id, // glyphs
&position, // positions
SkPoint::Make(-font_glyph.glyph.bounds.GetLeft(),
-font_glyph.glyph.bounds.GetTop()), // origin
sk_font, // font
glyph_paint // paint
);
}

static bool UpdateAtlasBitmap(const GlyphAtlas& atlas,
const std::shared_ptr<SkBitmap>& bitmap,
const FontGlyphPair::Vector& new_pairs) {
TRACE_EVENT0("impeller", __FUNCTION__);
FML_DCHECK(bitmap != nullptr);

auto surface = SkSurface::MakeRasterDirect(bitmap->pixmap());
if (!surface) {
return false;
}
auto canvas = surface->getCanvas();
if (!canvas) {
return false;
}

for (const auto& pair : new_pairs) {
auto pos = atlas.FindFontGlyphPosition(pair);
if (!pos.has_value()) {
continue;
}
DrawGlyph(canvas, pair, pos.value());
}
return true;
}

static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
const ISize& atlas_size) {
TRACE_EVENT0("impeller", __FUNCTION__);
Expand All @@ -263,6 +358,7 @@ static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
if (!bitmap->tryAllocPixels(image_info)) {
return nullptr;
}

auto surface = SkSurface::MakeRasterDirect(bitmap->pixmap());
if (!surface) {
return nullptr;
Expand All @@ -272,37 +368,31 @@ static std::shared_ptr<SkBitmap> CreateAtlasBitmap(const GlyphAtlas& atlas,
return nullptr;
}

atlas.IterateGlyphs([canvas](const FontGlyphPair& font_glyph,
const Rect& location) -> bool {
const auto& metrics = font_glyph.font.GetMetrics();
const auto position = SkPoint::Make(location.origin.x / metrics.scale,
location.origin.y / metrics.scale);
SkGlyphID glyph_id = font_glyph.glyph.index;

SkFont sk_font(
TypefaceSkia::Cast(*font_glyph.font.GetTypeface()).GetSkiaTypeface(),
metrics.point_size);
auto glyph_color = SK_ColorWHITE;

SkPaint glyph_paint;
glyph_paint.setColor(glyph_color);
canvas->resetMatrix();
canvas->scale(metrics.scale, metrics.scale);
canvas->drawGlyphs(
1u, // count
&glyph_id, // glyphs
&position, // positions
SkPoint::Make(-font_glyph.glyph.bounds.GetLeft(),
-font_glyph.glyph.bounds.GetTop()), // origin
sk_font, // font
glyph_paint // paint
);
return true;
});
atlas.IterateGlyphs(
[canvas](const FontGlyphPair& font_glyph, const Rect& location) -> bool {
DrawGlyph(canvas, font_glyph, location);
return true;
});

return bitmap;
}

static bool UpdateGlyphTextureAtlas(std::shared_ptr<SkBitmap> bitmap,
const std::shared_ptr<Texture>& texture) {
TRACE_EVENT0("impeller", __FUNCTION__);

FML_DCHECK(bitmap != nullptr);
auto texture_descriptor = texture->GetTextureDescriptor();

auto mapping = std::make_shared<fml::NonOwnedMapping>(
reinterpret_cast<const uint8_t*>(bitmap->getAddr(0, 0)), // data
texture_descriptor.GetByteSizeOfBaseMipLevel(), // size
[bitmap](auto, auto) mutable { bitmap.reset(); } // proc
);

return texture->SetContents(mapping);
}

static std::shared_ptr<Texture> UploadGlyphTextureAtlas(
const std::shared_ptr<Allocator>& allocator,
std::shared_ptr<SkBitmap> bitmap,
Expand Down Expand Up @@ -367,26 +457,61 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
// Step 2: Determine if the atlas type and font glyph pairs are compatible
// with the current atlas and reuse if possible.
// ---------------------------------------------------------------------------
if (last_atlas->GetType() == type &&
last_atlas->HasSamePairs(font_glyph_pairs)) {
auto new_glyphs = last_atlas->HasSamePairs(font_glyph_pairs);
if (last_atlas->GetType() == type && new_glyphs.size() == 0) {
return last_atlas;
}

auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
atlas_context->UpdateGlyphAtlas(glyph_atlas);

// ---------------------------------------------------------------------------
// Step 3: Get the optimum size of the texture atlas.
// Step 3: Determine if the additional missing glyphs can be appended to the
// existing bitmap without recreating the atlas.
// ---------------------------------------------------------------------------
std::vector<Rect> glyph_positions;
const auto atlas_size =
OptimumAtlasSizeForFontGlyphPairs(font_glyph_pairs, glyph_positions);
if (CanAppendToExistingAtlas(last_atlas, new_glyphs, glyph_positions,
atlas_context->GetAtlasSize(),
atlas_context->GetRectPacker())) {
// The old bitmap will be reused and only the additional glyphs will be
// added.

// ---------------------------------------------------------------------------
// Step 4: Record the positions in the glyph atlas of the newly added
// glyphs.
// ---------------------------------------------------------------------------
for (size_t i = 0, count = glyph_positions.size(); i < count; i++) {
last_atlas->AddTypefaceGlyphPosition(new_glyphs[i], glyph_positions[i]);
}

// ---------------------------------------------------------------------------
// Step 5: Draw new font-glyph pairs into the existing bitmap.
// ---------------------------------------------------------------------------
auto bitmap = atlas_context->GetBitmap();
if (!UpdateAtlasBitmap(*last_atlas, bitmap, new_glyphs)) {
return nullptr;
}

// ---------------------------------------------------------------------------
// Step 6: Update the existing texture with the updated bitmap.
// ---------------------------------------------------------------------------
if (!UpdateGlyphTextureAtlas(bitmap, last_atlas->GetTexture())) {
return nullptr;
}
return last_atlas;
}
// A new glyph atlas must be created.

// ---------------------------------------------------------------------------
// Step 4: Get the optimum size of the texture atlas.
// ---------------------------------------------------------------------------
auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
auto atlas_size = OptimumAtlasSizeForFontGlyphPairs(
font_glyph_pairs, glyph_positions, atlas_context);

atlas_context->UpdateGlyphAtlas(glyph_atlas, atlas_size);
if (atlas_size.IsEmpty()) {
return nullptr;
}

// ---------------------------------------------------------------------------
// Step 4: Find location of font-glyph pairs in the atlas. We have this from
// Step 5: Find location of font-glyph pairs in the atlas. We have this from
// the last step. So no need to do create another rect packer. But just do a
// sanity check of counts. This could also be just an assertion as only a
// construction issue would cause such a failure.
Expand All @@ -396,23 +521,24 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}

// ---------------------------------------------------------------------------
// Step 5: Record the positions in the glyph atlas.
// Step 6: Record the positions in the glyph atlas.
// ---------------------------------------------------------------------------
for (size_t i = 0, count = glyph_positions.size(); i < count; i++) {
glyph_atlas->AddTypefaceGlyphPosition(font_glyph_pairs[i],
glyph_positions[i]);
}

// ---------------------------------------------------------------------------
// Step 6: Draw font-glyph pairs in the correct spot in the atlas.
// Step 7: Draw font-glyph pairs in the correct spot in the atlas.
// ---------------------------------------------------------------------------
auto bitmap = CreateAtlasBitmap(*glyph_atlas, atlas_size);
if (!bitmap) {
return nullptr;
}
atlas_context->UpdateBitmap(bitmap);

// ---------------------------------------------------------------------------
// Step 7: Upload the atlas as a texture.
// Step 8: Upload the atlas as a texture.
// ---------------------------------------------------------------------------
PixelFormat format;
switch (type) {
Expand All @@ -434,7 +560,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}

// ---------------------------------------------------------------------------
// Step 8: Record the texture in the glyph atlas.
// Step 9: Record the texture in the glyph atlas.
// ---------------------------------------------------------------------------
glyph_atlas->SetTexture(std::move(texture));

Expand Down
Loading