[Impeller] cache glyph atlas if contents are unchanged (flutter/engine#36719)

This commit is contained in:
Jonah Williams
2022-10-24 10:53:47 -07:00
committed by GitHub
parent a5e8714ac1
commit 387d57dcc7
13 changed files with 140 additions and 25 deletions

View File

@@ -143,7 +143,8 @@ static std::unique_ptr<PipelineT> CreateDefaultPipeline(
ContentContext::ContentContext(std::shared_ptr<Context> context)
: context_(std::move(context)),
tessellator_(std::make_shared<Tessellator>()) {
tessellator_(std::make_shared<Tessellator>()),
glyph_atlas_context_(std::make_shared<GlyphAtlasContext>()) {
if (!context_ || !context_->IsValid()) {
return;
}
@@ -291,6 +292,11 @@ std::shared_ptr<Tessellator> ContentContext::GetTessellator() const {
return tessellator_;
}
std::shared_ptr<GlyphAtlasContext> ContentContext::GetGlyphAtlasContext()
const {
return glyph_atlas_context_;
}
std::shared_ptr<Context> ContentContext::GetContext() const {
return context_;
}

View File

@@ -68,6 +68,8 @@
#include "impeller/entity/position_color.vert.h"
#include "impeller/entity/position_uv.vert.h"
#include "impeller/typographer/glyph_atlas.h"
namespace impeller {
using LinearGradientFillPipeline =
@@ -376,6 +378,8 @@ class ContentContext {
std::shared_ptr<Context> GetContext() const;
std::shared_ptr<GlyphAtlasContext> GetGlyphAtlasContext() const;
using SubpassCallback =
std::function<bool(const ContentContext&, RenderPass&)>;
@@ -465,6 +469,7 @@ class ContentContext {
bool is_valid_ = false;
std::shared_ptr<Tessellator> tessellator_;
std::shared_ptr<GlyphAtlasContext> glyph_atlas_context_;
FML_DISALLOW_COPY_AND_ASSIGN(ContentContext);
};

View File

@@ -34,10 +34,11 @@ void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const {
FML_DCHECK(lazy_atlas_);
if (lazy_atlas_) {
return lazy_atlas_->CreateOrGetGlyphAtlas(type, context);
return lazy_atlas_->CreateOrGetGlyphAtlas(type, atlas_context, context);
}
return nullptr;
@@ -172,8 +173,9 @@ static bool CommonRender(const ContentContext& renderer,
bool TextContents::RenderSdf(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto atlas = ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
renderer.GetContext());
auto atlas =
ResolveAtlas(GlyphAtlas::Type::kSignedDistanceField,
renderer.GetGlyphAtlasContext(), renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
@@ -208,7 +210,7 @@ bool TextContents::Render(const ContentContext& renderer,
auto atlas =
ResolveAtlas(lazy_atlas_->HasColor() ? GlyphAtlas::Type::kColorBitmap
: GlyphAtlas::Type::kAlphaBitmap,
renderer.GetContext());
renderer.GetGlyphAtlasContext(), renderer.GetContext());
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";

View File

@@ -52,6 +52,7 @@ class TextContents final : public Contents {
std::shared_ptr<GlyphAtlas> ResolveAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const;
FML_DISALLOW_COPY_AND_ASSIGN(TextContents);

View File

@@ -340,13 +340,13 @@ static std::shared_ptr<Texture> UploadGlyphTextureAtlas(
std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
FrameIterator frame_iterator) const {
TRACE_EVENT0("impeller", __FUNCTION__);
if (!IsValid()) {
return nullptr;
}
auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
auto last_atlas = atlas_context->GetGlyphAtlas();
// ---------------------------------------------------------------------------
// Step 1: Collect unique font-glyph pairs in the frame.
@@ -354,11 +354,23 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
auto font_glyph_pairs = CollectUniqueFontGlyphPairs(type, frame_iterator);
if (font_glyph_pairs.empty()) {
return glyph_atlas;
return last_atlas;
}
// ---------------------------------------------------------------------------
// Step 2: Get the optimum size of the texture atlas.
// 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)) {
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.
// ---------------------------------------------------------------------------
std::vector<Rect> glyph_positions;
const auto atlas_size =
@@ -368,7 +380,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 3: Find location of font-glyph pairs in the atlas. We have this from
// Step 4: 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.
@@ -378,7 +390,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 4: Record the positions in the glyph atlas.
// Step 5: 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],
@@ -386,7 +398,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 5: Draw font-glyph pairs in the correct spot in the atlas.
// Step 6: Draw font-glyph pairs in the correct spot in the atlas.
// ---------------------------------------------------------------------------
auto bitmap = CreateAtlasBitmap(*glyph_atlas, atlas_size);
if (!bitmap) {
@@ -394,7 +406,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 6: Upload the atlas as a texture.
// Step 7: Upload the atlas as a texture.
// ---------------------------------------------------------------------------
PixelFormat format;
switch (type) {
@@ -416,7 +428,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 7: Record the texture in the glyph atlas.
// Step 8: Record the texture in the glyph atlas.
// ---------------------------------------------------------------------------
glyph_atlas->SetTexture(std::move(texture));

View File

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

View File

@@ -6,6 +6,19 @@
namespace impeller {
GlyphAtlasContext::GlyphAtlasContext()
: atlas_(std::make_shared<GlyphAtlas>(GlyphAtlas::Type::kAlphaBitmap)) {}
GlyphAtlasContext::~GlyphAtlasContext() {}
std::shared_ptr<GlyphAtlas> GlyphAtlasContext::GetGlyphAtlas() const {
return atlas_;
}
void GlyphAtlasContext::UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas) {
atlas_ = atlas;
}
GlyphAtlas::GlyphAtlas(Type type) : type_(type) {}
GlyphAtlas::~GlyphAtlas() = default;
@@ -61,4 +74,13 @@ size_t GlyphAtlas::IterateGlyphs(
return count;
}
bool GlyphAtlas::HasSamePairs(const FontGlyphPair::Vector& new_glyphs) {
for (const auto& pair : new_glyphs) {
if (positions_.find(pair) == positions_.end()) {
return false;
}
}
return true;
}
} // namespace impeller

View File

@@ -116,6 +116,16 @@ class GlyphAtlas {
///
std::optional<Rect> FindFontGlyphPosition(const FontGlyphPair& pair) const;
//----------------------------------------------------------------------------
/// @brief whether this atlas contains all of the same font-glyph pairs
/// as the vector.
///
/// @param[in] new_glyphs The full set of new glyphs
///
/// @return Whether this atlas contains all passed pairs.
///
bool HasSamePairs(const FontGlyphPair::Vector& new_glyphs);
private:
const Type type_;
std::shared_ptr<Texture> texture_;
@@ -129,4 +139,27 @@ class GlyphAtlas {
FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlas);
};
//------------------------------------------------------------------------------
/// @brief A container for caching a glyph atlas across frames.
///
class GlyphAtlasContext {
public:
GlyphAtlasContext();
~GlyphAtlasContext();
//----------------------------------------------------------------------------
/// @brief Retrieve the current glyph atlas.
std::shared_ptr<GlyphAtlas> GetGlyphAtlas() const;
//----------------------------------------------------------------------------
/// @brief Update the context with a newly constructed glyph atlas.
void UpdateGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
private:
std::shared_ptr<GlyphAtlas> atlas_;
FML_DISALLOW_COPY_AND_ASSIGN(GlyphAtlasContext);
};
} // namespace impeller

View File

@@ -26,6 +26,7 @@ bool LazyGlyphAtlas::HasColor() const {
std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const {
{
auto atlas_it = atlas_map_.find(type);
@@ -47,7 +48,7 @@ std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
i++;
return &result;
};
auto atlas = text_context->CreateGlyphAtlas(type, iterator);
auto atlas = text_context->CreateGlyphAtlas(type, atlas_context, iterator);
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Could not create valid atlas.";
return nullptr;

View File

@@ -23,6 +23,7 @@ class LazyGlyphAtlas {
std::shared_ptr<GlyphAtlas> CreateOrGetGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const;
bool HasColor() const;

View File

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

View File

@@ -44,10 +44,13 @@ class TextRenderContext {
virtual std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
FrameIterator iterator) const = 0;
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(GlyphAtlas::Type type,
const TextFrame& frame) const;
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
const TextFrame& frame) const;
protected:
//----------------------------------------------------------------------------

View File

@@ -36,12 +36,14 @@ TEST_P(TypographerTest, CanCreateRenderContext) {
TEST_P(TypographerTest, CanCreateGlyphAtlas) {
auto context = TextRenderContext::Create(GetContext());
auto atlas_context = std::make_shared<GlyphAtlasContext>();
ASSERT_TRUE(context && context->IsValid());
SkFont sk_font;
auto blob = SkTextBlob::MakeFromString("hello", sk_font);
ASSERT_TRUE(blob);
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
TextFrameFromTextBlob(blob));
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
OpenPlaygroundHere([](RenderTarget&) { return true; });
}
@@ -95,12 +97,14 @@ TEST_P(TypographerTest, LazyAtlasTracksColor) {
TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
auto context = TextRenderContext::Create(GetContext());
auto atlas_context = std::make_shared<GlyphAtlasContext>();
ASSERT_TRUE(context && context->IsValid());
SkFont sk_font;
auto blob = SkTextBlob::MakeFromString("AGH", sk_font);
ASSERT_TRUE(blob);
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
TextFrameFromTextBlob(blob));
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
@@ -110,8 +114,32 @@ TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
atlas->GetTexture()->GetSize().height);
}
TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
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 skellingtons", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
// now attempt to re-create an atlas with the same text blob.
auto next_atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
ASSERT_EQ(atlas, next_atlas);
ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
}
TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
auto context = TextRenderContext::Create(GetContext());
auto atlas_context = std::make_shared<GlyphAtlasContext>();
ASSERT_TRUE(context && context->IsValid());
SkFont sk_font;
@@ -133,9 +161,8 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
}
return nullptr;
};
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, iterator);
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
atlas_context, iterator);
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);