[Impeller] Append to existing atlas if room exists, reuse texture (flutter/engine#38253)

* [impeller][wip] append to existing atlas if room exists

* cleanups and reuse texture

* ++

* ++

* ++

* ++

* dnfield review

* ++

* ++

* ++
This commit is contained in:
Jonah Williams 2023-02-03 13:54:47 -08:00 committed by GitHub
parent 8687f16411
commit b2a0500a9e
4 changed files with 277 additions and 67 deletions

View File

@ -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)) {}
@ -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;
}
@ -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;
@ -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(
@ -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__);
@ -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;
@ -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,
@ -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.
@ -396,7 +521,7 @@ 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],
@ -404,15 +529,16 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// 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) {
@ -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));

View File

@ -9,7 +9,8 @@
namespace impeller {
GlyphAtlasContext::GlyphAtlasContext()
: atlas_(std::make_shared<GlyphAtlas>(GlyphAtlas::Type::kAlphaBitmap)) {}
: atlas_(std::make_shared<GlyphAtlas>(GlyphAtlas::Type::kAlphaBitmap)),
atlas_size_(ISize(0, 0)) {}
GlyphAtlasContext::~GlyphAtlasContext() {}
@ -17,8 +18,31 @@ std::shared_ptr<GlyphAtlas> GlyphAtlasContext::GetGlyphAtlas() const {
return atlas_;
}
void GlyphAtlasContext::UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas) {
const ISize& GlyphAtlasContext::GetAtlasSize() const {
return atlas_size_;
}
std::shared_ptr<SkBitmap> GlyphAtlasContext::GetBitmap() const {
return bitmap_;
}
std::shared_ptr<skgpu::Rectanizer> GlyphAtlasContext::GetRectPacker() const {
return rect_packer_;
}
void GlyphAtlasContext::UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas,
ISize size) {
atlas_ = std::move(atlas);
atlas_size_ = size;
}
void GlyphAtlasContext::UpdateBitmap(std::shared_ptr<SkBitmap> bitmap) {
bitmap_ = std::move(bitmap);
}
void GlyphAtlasContext::UpdateRectPacker(
std::shared_ptr<skgpu::Rectanizer> rect_packer) {
rect_packer_ = std::move(rect_packer);
}
GlyphAtlas::GlyphAtlas(Type type) : type_(type) {}
@ -76,13 +100,15 @@ size_t GlyphAtlas::IterateGlyphs(
return count;
}
bool GlyphAtlas::HasSamePairs(const FontGlyphPair::Vector& new_glyphs) {
for (const auto& pair : new_glyphs) {
FontGlyphPair::Vector GlyphAtlas::HasSamePairs(
const FontGlyphPair::Vector& new_glyphs) {
std::vector<FontGlyphPair> new_pairs;
for (auto pair : new_glyphs) {
if (positions_.find(pair) == positions_.end()) {
return false;
new_pairs.push_back(pair);
}
}
return true;
return new_pairs;
}
} // namespace impeller

View File

@ -15,6 +15,11 @@
#include "impeller/renderer/texture.h"
#include "impeller/typographer/font_glyph_pair.h"
class SkBitmap;
namespace skgpu {
class Rectanizer;
}
namespace impeller {
//------------------------------------------------------------------------------
@ -122,9 +127,10 @@ class GlyphAtlas {
///
/// @param[in] new_glyphs The full set of new glyphs
///
/// @return Whether this atlas contains all passed pairs.
/// @return A vector containing the glyphs from new_glyphs that are not
/// present in the existing atlas. May be empty of there are none.
///
bool HasSamePairs(const FontGlyphPair::Vector& new_glyphs);
FontGlyphPair::Vector HasSamePairs(const FontGlyphPair::Vector& new_glyphs);
private:
const Type type_;
@ -152,12 +158,31 @@ class GlyphAtlasContext {
/// @brief Retrieve the current glyph atlas.
std::shared_ptr<GlyphAtlas> GetGlyphAtlas() const;
//----------------------------------------------------------------------------
/// @brief Retrieve the size of the current glyph atlas.
const ISize& GetAtlasSize() const;
//----------------------------------------------------------------------------
/// @brief Retrieve the previous (if any) SkBitmap instance.
std::shared_ptr<SkBitmap> GetBitmap() const;
//----------------------------------------------------------------------------
/// @brief Retrieve the previous (if any) rect packer.
std::shared_ptr<skgpu::Rectanizer> GetRectPacker() const;
//----------------------------------------------------------------------------
/// @brief Update the context with a newly constructed glyph atlas.
void UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
void UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas, ISize size);
void UpdateBitmap(std::shared_ptr<SkBitmap> bitmap);
void UpdateRectPacker(std::shared_ptr<skgpu::Rectanizer> rect_packer);
private:
std::shared_ptr<GlyphAtlas> atlas_;
ISize atlas_size_;
std::shared_ptr<SkBitmap> bitmap_;
std::shared_ptr<skgpu::Rectanizer> rect_packer_;
FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlasContext);
};

View File

@ -168,5 +168,38 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
atlas->GetTexture()->GetSize().height);
}
TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
auto context = TextRenderContext::Create(GetContext());
auto atlas_context = std::make_shared<GlyphAtlasContext>();
ASSERT_TRUE(context && context->IsValid());
SkFont sk_font;
auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
auto old_packer = atlas_context->GetRectPacker();
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
auto* first_texture = atlas->GetTexture().get();
// now create a new glyph atlas with a nearly identical blob.
auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
auto next_atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob2));
ASSERT_EQ(atlas, next_atlas);
auto* second_texture = next_atlas->GetTexture().get();
auto new_packer = atlas_context->GetRectPacker();
ASSERT_EQ(second_texture, first_texture);
ASSERT_EQ(old_packer, new_packer);
}
} // namespace testing
} // namespace impeller