[Impeller] Improve resolution of text scaling. (flutter/engine#43533)

This patch does a few things:

- Changes ownership of the LazyGlyphAtlas to ContentContext from Canvas. This means that drawings with multiple canvases (e.g. calls to DrawPicture) share the same lazy atlas.
- Moves the scale property from Font::Metrics to FontGlyphPair. Font::Metrics contains properties related to the font, whereas scale has to do with the properties of the drawing at render time and is independent of the font.
- Makes the lazy atlas manage FontGlyphPair::Set rather than vectors of TextFrames.
- Makes the determination of font scaling at EntityPass::Render rather than Canvas::DrawTextFrame. The scaling may be altered by calls to Canvas::DrawPicture by render time and otherwise get missed. This is done via a new method: Contents::PopulateGlyphAtlas, which is only implemented by TextContents in this patch.
- Fixes up some miscelleaneous bugs in Font::Metrics hashing and unused/dead code.

This improves over prior attempts: LazyGlyphAtlas ownership is now unambiguous, and glyph scaling happens correctly for all rendered glyphs regardless of the order of canvas operations in Aiks.

Fixes flutter/flutter#130142
This commit is contained in:
Dan Field 2023-07-10 21:57:07 -07:00 committed by GitHub
parent e939f38a85
commit 21bb71b627
28 changed files with 250 additions and 201 deletions

View File

@ -2851,6 +2851,35 @@ TEST_P(AiksTest, CanCanvasDrawPicture) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, DrawPictureWithText) {
Canvas subcanvas;
ASSERT_TRUE(RenderTextInCanvas(
GetContext(), subcanvas,
"the quick brown fox jumped over the lazy dog!.?", "Roboto-Regular.ttf"));
subcanvas.Translate({0, 10});
subcanvas.Scale(Vector2(3, 3));
ASSERT_TRUE(RenderTextInCanvas(
GetContext(), subcanvas,
"the quick brown fox jumped over the very big lazy dog!.?",
"Roboto-Regular.ttf"));
auto picture = subcanvas.EndRecordingAsPicture();
Canvas canvas;
canvas.Scale(Vector2(.2, .2));
canvas.Save();
canvas.Translate({200, 200});
canvas.Scale(Vector2(3.5, 3.5)); // The text must not be blurry after this.
canvas.DrawPicture(picture);
canvas.Restore();
canvas.Scale(Vector2(1.5, 1.5));
ASSERT_TRUE(RenderTextInCanvas(
GetContext(), canvas,
"the quick brown fox jumped over the smaller lazy dog!.?",
"Roboto-Regular.ttf"));
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, MatrixBackdropFilter) {
Canvas canvas;
canvas.SaveLayer({}, std::nullopt,

View File

@ -42,7 +42,6 @@ void Canvas::Initialize(std::optional<Rect> cull_rect) {
base_pass_ = std::make_unique<EntityPass>();
current_pass_ = base_pass_.get();
xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
lazy_glyph_atlas_ = std::make_shared<LazyGlyphAtlas>();
FML_DCHECK(GetSaveCount() == 1u);
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
}
@ -51,7 +50,6 @@ void Canvas::Reset() {
base_pass_ = nullptr;
current_pass_ = nullptr;
xformation_stack_ = {};
lazy_glyph_atlas_ = nullptr;
}
void Canvas::Save() {
@ -516,15 +514,12 @@ void Canvas::SaveLayer(const Paint& paint,
void Canvas::DrawTextFrame(const TextFrame& text_frame,
Point position,
const Paint& paint) {
lazy_glyph_atlas_->AddTextFrame(text_frame);
Entity entity;
entity.SetStencilDepth(GetStencilDepth());
entity.SetBlendMode(paint.blend_mode);
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(text_frame);
text_contents->SetGlyphAtlas(lazy_glyph_atlas_);
if (paint.color_source.GetType() != ColorSource::Type::kColor) {
auto color_text_contents = std::make_shared<ColorSourceTextContents>();

View File

@ -162,7 +162,6 @@ class Canvas {
std::unique_ptr<EntityPass> base_pass_;
EntityPass* current_pass_ = nullptr;
std::deque<CanvasStackEntry> xformation_stack_;
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
std::optional<Rect> initial_cull_rect_;
void Initialize(std::optional<Rect> cull_rect);

View File

@ -1104,10 +1104,9 @@ void DlDispatcher::drawDisplayList(
void DlDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) {
Scalar scale = canvas_.GetCurrentTransformation().GetMaxBasisLengthXY();
canvas_.DrawTextFrame(TextFrameFromTextBlob(blob, scale), //
impeller::Point{x, y}, //
paint_ //
canvas_.DrawTextFrame(TextFrameFromTextBlob(blob), //
impeller::Point{x, y}, //
paint_ //
);
}

View File

@ -4,6 +4,7 @@
#include "impeller/entity/contents/color_source_text_contents.h"
#include "color_source_text_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/renderer/render_pass.h"
@ -33,6 +34,12 @@ void ColorSourceTextContents::SetTextPosition(Point position) {
position_ = position;
}
void ColorSourceTextContents::PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) const {
text_contents_->PopulateGlyphAtlas(lazy_glyph_atlas, scale);
}
bool ColorSourceTextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {

View File

@ -32,6 +32,11 @@ class ColorSourceTextContents final : public Contents {
// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |Contents|
void PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) const override;
// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,

View File

@ -696,8 +696,18 @@ class ContentContext {
const SubpassCallback& subpass_callback,
bool msaa_enabled = true) const;
void SetLazyGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas) {
lazy_glyph_atlas_ = lazy_glyph_atlas;
}
std::shared_ptr<LazyGlyphAtlas> GetLazyGlyphAtlas() const {
return lazy_glyph_atlas_;
}
private:
std::shared_ptr<Context> context_;
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
template <class T>
using Variants = std::unordered_map<ContentContextOptions,

View File

@ -14,6 +14,7 @@
#include "impeller/geometry/color.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/snapshot.h"
#include "impeller/typographer/lazy_glyph_atlas.h"
namespace impeller {
@ -49,6 +50,11 @@ class Contents {
virtual ~Contents();
/// @brief Add any text data to the specified lazy atlas.
virtual void PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) const {}
virtual bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const = 0;

View File

@ -29,18 +29,15 @@ void TextContents::SetTextFrame(const TextFrame& frame) {
frame_ = frame;
}
void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
lazy_atlas_ = std::move(atlas);
}
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
GlyphAtlas::Type type,
const std::shared_ptr<LazyGlyphAtlas>& lazy_atlas,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const {
FML_DCHECK(lazy_atlas_);
if (lazy_atlas_) {
return lazy_atlas_->CreateOrGetGlyphAtlas(type, std::move(atlas_context),
std::move(context));
FML_DCHECK(lazy_atlas);
if (lazy_atlas) {
return lazy_atlas->CreateOrGetGlyphAtlas(type, std::move(atlas_context),
std::move(context));
}
return nullptr;
@ -78,14 +75,43 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
return bounds->TransformBounds(entity.GetTransformation());
}
static bool CommonRender(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass,
const Color& color,
const TextFrame& frame,
Vector2 offset,
const std::shared_ptr<GlyphAtlas>& atlas,
Command& cmd) {
void TextContents::PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) const {
lazy_glyph_atlas->AddTextFrame(frame_, scale);
}
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto color = GetColor();
if (color.IsTransparent()) {
return true;
}
auto type = frame_.GetAtlasType();
auto scale = entity.DeriveTextScale();
auto atlas =
ResolveAtlas(type, renderer.GetLazyGlyphAtlas(),
renderer.GetGlyphAtlasContext(type), 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";
auto opts = OptionsFromPassAndEntity(pass, entity);
opts.primitive_type = PrimitiveType::kTriangle;
if (type == GlyphAtlas::Type::kAlphaBitmap) {
cmd.pipeline = renderer.GetGlyphAtlasPipeline(opts);
} else {
cmd.pipeline = renderer.GetGlyphAtlasColorPipeline(opts);
}
cmd.stencil_reference = entity.GetStencilDepth();
using VS = GlyphAtlasPipeline::VertexShader;
using FS = GlyphAtlasPipeline::FragmentShader;
@ -95,7 +121,7 @@ static bool CommonRender(const ContentContext& renderer,
frame_info.atlas_size =
Vector2{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
frame_info.offset = offset;
frame_info.offset = offset_;
frame_info.is_translation_scale =
entity.GetTransformation().IsTranslationScaleOnly();
frame_info.entity_transform = entity.GetTransformation();
@ -141,7 +167,7 @@ static bool CommonRender(const ContentContext& renderer,
auto& host_buffer = pass.GetTransientsBuffer();
size_t vertex_count = 0;
for (const auto& run : frame.GetRuns()) {
for (const auto& run : frame_.GetRuns()) {
vertex_count += run.GetGlyphPositions().size();
}
vertex_count *= 6;
@ -151,10 +177,10 @@ static bool CommonRender(const ContentContext& renderer,
[&](uint8_t* contents) {
VS::PerVertexData vtx;
size_t vertex_offset = 0;
for (const auto& run : frame.GetRuns()) {
for (const auto& run : frame_.GetRuns()) {
const Font& font = run.GetFont();
for (const auto& glyph_position : run.GetGlyphPositions()) {
FontGlyphPair font_glyph_pair{font, glyph_position.glyph};
FontGlyphPair font_glyph_pair{font, glyph_position.glyph, scale};
auto maybe_atlas_glyph_bounds =
atlas->FindFontGlyphBounds(font_glyph_pair);
if (!maybe_atlas_glyph_bounds.has_value()) {
@ -191,37 +217,4 @@ static bool CommonRender(const ContentContext& renderer,
return pass.AddCommand(cmd);
}
bool TextContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
auto color = GetColor();
if (color.IsTransparent()) {
return true;
}
auto type = frame_.GetAtlasType();
auto atlas = ResolveAtlas(type, renderer.GetGlyphAtlasContext(type),
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";
auto opts = OptionsFromPassAndEntity(pass, entity);
opts.primitive_type = PrimitiveType::kTriangle;
if (type == GlyphAtlas::Type::kAlphaBitmap) {
cmd.pipeline = renderer.GetGlyphAtlasPipeline(opts);
} else {
cmd.pipeline = renderer.GetGlyphAtlasColorPipeline(opts);
}
cmd.stencil_reference = entity.GetStencilDepth();
return CommonRender(renderer, entity, pass, color, frame_, offset_, atlas,
cmd);
}
} // namespace impeller

View File

@ -28,14 +28,14 @@ class TextContents final : public Contents {
void SetTextFrame(const TextFrame& frame);
void SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas);
void SetColor(Color color);
Color GetColor() const;
// |Contents|
bool CanInheritOpacity(const Entity& entity) const override;
// |Contents|
void SetInheritedOpacity(Scalar opacity) override;
void SetOffset(Vector2 offset);
@ -45,6 +45,11 @@ class TextContents final : public Contents {
// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;
// |Contents|
void PopulateGlyphAtlas(
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
Scalar scale) const override;
// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
@ -54,11 +59,11 @@ class TextContents final : public Contents {
TextFrame frame_;
Color color_;
Scalar inherited_opacity_ = 1.0;
mutable std::shared_ptr<LazyGlyphAtlas> lazy_atlas_;
Vector2 offset_;
std::shared_ptr<GlyphAtlas> ResolveAtlas(
GlyphAtlas::Type type,
const std::shared_ptr<LazyGlyphAtlas>& lazy_atlas,
std::shared_ptr<GlyphAtlasContext> atlas_context,
std::shared_ptr<Context> context) const;

View File

@ -166,4 +166,8 @@ bool Entity::Render(const ContentContext& renderer,
return contents_->Render(renderer, *this, parent_pass);
}
Scalar Entity::DeriveTextScale() const {
return GetTransformation().GetMaxBasisLengthXY();
}
} // namespace impeller

View File

@ -94,6 +94,8 @@ class Entity {
std::optional<Color> AsBackgroundColor(ISize target_size) const;
Scalar DeriveTextScale() const;
private:
Matrix transformation_;
std::shared_ptr<Contents> contents_;

View File

@ -8,6 +8,7 @@
#include <utility>
#include <variant>
#include "flutter/fml/closure.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/trace_event.h"
@ -252,6 +253,18 @@ bool EntityPass::Render(ContentContext& renderer,
return false;
}
renderer.SetLazyGlyphAtlas(std::make_shared<LazyGlyphAtlas>());
fml::ScopedCleanupClosure reset_lazy_glyph_atlas(
[&renderer]() { renderer.SetLazyGlyphAtlas(nullptr); });
IterateAllEntities([lazy_glyph_atlas =
renderer.GetLazyGlyphAtlas()](const Entity& entity) {
if (auto contents = entity.GetContents()) {
contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale());
}
return true;
});
StencilCoverageStack stencil_coverage_stack = {StencilCoverageLayer{
.coverage = Rect::MakeSize(root_render_target.GetRenderTargetSize()),
.stencil_depth = 0}};
@ -878,6 +891,28 @@ void EntityPass::IterateAllEntities(
}
}
void EntityPass::IterateAllEntities(
const std::function<bool(const Entity&)>& iterator) const {
if (!iterator) {
return;
}
for (const auto& element : elements_) {
if (auto entity = std::get_if<Entity>(&element)) {
if (!iterator(*entity)) {
return;
}
continue;
}
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
const EntityPass* entity_pass = subpass->get();
entity_pass->IterateAllEntities(iterator);
continue;
}
FML_UNREACHABLE();
}
}
bool EntityPass::IterateUntilSubpass(
const std::function<bool(Entity&)>& iterator) {
if (!iterator) {

View File

@ -79,6 +79,12 @@ class EntityPass {
/// of child passes. The iteration order is depth-first.
void IterateAllEntities(const std::function<bool(Entity&)>& iterator);
/// @brief Iterate all entities in this pass, recursively including entities
/// of child passes. The iteration order is depth-first and does not
/// allow modification of the entities.
void IterateAllEntities(
const std::function<bool(const Entity&)>& iterator) const;
/// @brief Iterate entities in this pass up until the first subpass is found.
/// This is useful for limiting look-ahead optimizations.
///

View File

@ -2184,7 +2184,7 @@ TEST_P(EntityTest, InheritOpacityTest) {
auto blob = SkTextBlob::MakeFromString("A", font);
auto frame = TextFrameFromTextBlob(blob);
auto lazy_glyph_atlas = std::make_shared<LazyGlyphAtlas>();
lazy_glyph_atlas->AddTextFrame(frame);
lazy_glyph_atlas->AddTextFrame(frame, 1.0f);
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(frame);

View File

@ -17,7 +17,7 @@
namespace impeller {
static Font ToFont(const SkTextBlobRunIterator& run, Scalar scale) {
static Font ToFont(const SkTextBlobRunIterator& run) {
auto& font = run.font();
auto typeface = std::make_shared<TypefaceSkia>(font.refTypefaceOrDefault());
@ -25,7 +25,6 @@ static Font ToFont(const SkTextBlobRunIterator& run, Scalar scale) {
font.getMetrics(&sk_metrics);
Font::Metrics metrics;
metrics.scale = scale;
metrics.point_size = font.getSize();
metrics.embolden = font.isEmbolden();
metrics.skewX = font.getSkewX();
@ -38,7 +37,7 @@ static Rect ToRect(const SkRect& rect) {
return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
}
TextFrame TextFrameFromTextBlob(const sk_sp<SkTextBlob>& blob, Scalar scale) {
TextFrame TextFrameFromTextBlob(const sk_sp<SkTextBlob>& blob) {
if (!blob) {
return {};
}
@ -46,7 +45,7 @@ TextFrame TextFrameFromTextBlob(const sk_sp<SkTextBlob>& blob, Scalar scale) {
TextFrame frame;
for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) {
TextRun text_run(ToFont(run, scale));
TextRun text_run(ToFont(run));
// TODO(jonahwilliams): ask Skia for a public API to look this up.
// https://github.com/flutter/flutter/issues/112005

View File

@ -10,7 +10,6 @@
namespace impeller {
TextFrame TextFrameFromTextBlob(const sk_sp<SkTextBlob>& blob,
Scalar scale = 1.0f);
TextFrame TextFrameFromTextBlob(const sk_sp<SkTextBlob>& blob);
} // namespace impeller

View File

@ -40,23 +40,6 @@ TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr<Context> context)
TextRenderContextSkia::~TextRenderContextSkia() = default;
static FontGlyphPair::Set CollectUniqueFontGlyphPairs(
GlyphAtlas::Type type,
const TextRenderContext::FrameIterator& frame_iterator) {
TRACE_EVENT0("impeller", __FUNCTION__);
FontGlyphPair::Set set;
while (const TextFrame* frame = frame_iterator()) {
for (const TextRun& run : frame->GetRuns()) {
const Font& font = run.GetFont();
for (const TextRun::GlyphPosition& glyph_position :
run.GetGlyphPositions()) {
set.insert({font, glyph_position.glyph});
}
}
}
return set;
}
static size_t PairsFitInAtlasOfSize(
const FontGlyphPair::Set& pairs,
const ISize& atlas_size,
@ -73,8 +56,7 @@ static size_t PairsFitInAtlasOfSize(
for (auto it = pairs.begin(); it != pairs.end(); ++i, ++it) {
const auto& pair = *it;
const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
const auto glyph_size = ISize::Ceil((pair.glyph.bounds * pair.scale).size);
IPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
@ -111,8 +93,7 @@ static bool CanAppendToExistingAtlas(
for (size_t i = 0; i < extra_pairs.size(); i++) {
const FontGlyphPair& pair = extra_pairs[i];
const auto glyph_size =
ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size);
const auto glyph_size = ISize::Ceil((pair.glyph.bounds * pair.scale).size);
IPoint16 location_in_atlas;
if (!rect_packer->addRect(glyph_size.width + kPadding, //
glyph_size.height + kPadding, //
@ -176,8 +157,8 @@ static void DrawGlyph(SkCanvas* canvas,
const Rect& location,
bool has_color) {
const auto& metrics = font_glyph.font.GetMetrics();
const auto position = SkPoint::Make(location.origin.x / metrics.scale,
location.origin.y / metrics.scale);
const auto position = SkPoint::Make(location.origin.x / font_glyph.scale,
location.origin.y / font_glyph.scale);
SkGlyphID glyph_id = font_glyph.glyph.index;
SkFont sk_font(
@ -192,7 +173,7 @@ static void DrawGlyph(SkCanvas* canvas,
SkPaint glyph_paint;
glyph_paint.setColor(glyph_color);
canvas->resetMatrix();
canvas->scale(metrics.scale, metrics.scale);
canvas->scale(font_glyph.scale, font_glyph.scale);
canvas->drawGlyphs(
1u, // count
&glyph_id, // glyphs
@ -331,25 +312,19 @@ 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 {
const FontGlyphPair::Set& font_glyph_pairs) const {
TRACE_EVENT0("impeller", __FUNCTION__);
if (!IsValid()) {
return nullptr;
}
std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas();
// ---------------------------------------------------------------------------
// Step 1: Collect unique font-glyph pairs in the frame.
// ---------------------------------------------------------------------------
FontGlyphPair::Set font_glyph_pairs =
CollectUniqueFontGlyphPairs(type, frame_iterator);
if (font_glyph_pairs.empty()) {
return last_atlas;
}
// ---------------------------------------------------------------------------
// Step 2: Determine if the atlas type and font glyph pairs are compatible
// Step 1: Determine if the atlas type and font glyph pairs are compatible
// with the current atlas and reuse if possible.
// ---------------------------------------------------------------------------
FontGlyphPairRefVector new_glyphs;
@ -363,7 +338,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 3: Determine if the additional missing glyphs can be appended to the
// Step 2: Determine if the additional missing glyphs can be appended to the
// existing bitmap without recreating the atlas. This requires that
// the type is identical.
// ---------------------------------------------------------------------------
@ -376,7 +351,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
// added.
// ---------------------------------------------------------------------------
// Step 4: Record the positions in the glyph atlas of the newly added
// Step 3a: Record the positions in the glyph atlas of the newly added
// glyphs.
// ---------------------------------------------------------------------------
for (size_t i = 0, count = glyph_positions.size(); i < count; i++) {
@ -384,7 +359,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 5: Draw new font-glyph pairs into the existing bitmap.
// Step 4a: Draw new font-glyph pairs into the existing bitmap.
// ---------------------------------------------------------------------------
auto bitmap = atlas_context->GetBitmap();
if (!UpdateAtlasBitmap(*last_atlas, bitmap, new_glyphs)) {
@ -392,7 +367,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 6: Update the existing texture with the updated bitmap.
// Step 5a: Update the existing texture with the updated bitmap.
// ---------------------------------------------------------------------------
if (!UpdateGlyphTextureAtlas(bitmap, last_atlas->GetTexture())) {
return nullptr;
@ -402,7 +377,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
// A new glyph atlas must be created.
// ---------------------------------------------------------------------------
// Step 4: Get the optimum size of the texture atlas.
// Step 3b: Get the optimum size of the texture atlas.
// ---------------------------------------------------------------------------
auto glyph_atlas = std::make_shared<GlyphAtlas>(type);
auto atlas_size = OptimumAtlasSizeForFontGlyphPairs(
@ -413,7 +388,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
return nullptr;
}
// ---------------------------------------------------------------------------
// Step 5: Find location of font-glyph pairs in the atlas. We have this from
// Step 4b: 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.
@ -423,7 +398,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 6: Record the positions in the glyph atlas.
// Step 5b: Record the positions in the glyph atlas.
// ---------------------------------------------------------------------------
{
size_t i = 0;
@ -434,7 +409,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 7: Draw font-glyph pairs in the correct spot in the atlas.
// Step 6b: Draw font-glyph pairs in the correct spot in the atlas.
// ---------------------------------------------------------------------------
auto bitmap = CreateAtlasBitmap(*glyph_atlas, atlas_size);
if (!bitmap) {
@ -443,7 +418,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
atlas_context->UpdateBitmap(bitmap);
// ---------------------------------------------------------------------------
// Step 8: Upload the atlas as a texture.
// Step 7b: Upload the atlas as a texture.
// ---------------------------------------------------------------------------
PixelFormat format;
switch (type) {
@ -461,7 +436,7 @@ std::shared_ptr<GlyphAtlas> TextRenderContextSkia::CreateGlyphAtlas(
}
// ---------------------------------------------------------------------------
// Step 9: Record the texture in the glyph atlas.
// Step 8b: Record the texture in the glyph atlas.
// ---------------------------------------------------------------------------
glyph_atlas->SetTexture(std::move(texture));

View File

@ -19,7 +19,7 @@ class TextRenderContextSkia : public TextRenderContext {
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
FrameIterator iterator) const override;
const FontGlyphPair::Set& font_glyph_pairs) const override;
private:
FML_DISALLOW_COPY_AND_ASSIGN(TextRenderContextSkia);

View File

@ -28,12 +28,6 @@ class Font : public Comparable<Font> {
/// the baseline with an upper-left-origin coordinate system.
///
struct Metrics {
//--------------------------------------------------------------------------
/// The scaling factor that should be used when rendering this font to an
/// atlas. This should normally be set in accordance with the transformation
/// matrix that will be used to position glyph geometry.
///
Scalar scale = 1.0f;
//--------------------------------------------------------------------------
/// The point size of the font.
///
@ -43,8 +37,8 @@ class Font : public Comparable<Font> {
Scalar scaleX = 1.0f;
constexpr bool operator==(const Metrics& o) const {
return scale == o.scale && point_size == o.point_size &&
embolden == o.embolden && skewX == o.skewX && scaleX == o.scaleX;
return point_size == o.point_size && embolden == o.embolden &&
skewX == o.skewX && scaleX == o.scaleX;
}
};
@ -80,6 +74,6 @@ class Font : public Comparable<Font> {
template <>
struct std::hash<impeller::Font::Metrics> {
constexpr std::size_t operator()(const impeller::Font::Metrics& m) const {
return fml::HashCombine(m.scale, m.point_size);
return fml::HashCombine(m.point_size, m.skewX, m.scaleX);
}
};

View File

@ -16,28 +16,29 @@
namespace impeller {
//------------------------------------------------------------------------------
/// @brief A font along with a glyph in that font. Used in glyph atlases as
/// keys.
/// @brief A font along with a glyph in that font rendered at a particular
/// scale. Used in glyph atlases as keys.
///
struct FontGlyphPair {
struct Hash;
struct Equal;
using Set = std::unordered_set<FontGlyphPair, Hash, Equal>;
using Vector = std::vector<FontGlyphPair>;
Font font;
Glyph glyph;
Scalar scale;
struct Hash {
std::size_t operator()(const FontGlyphPair& p) const {
return fml::HashCombine(p.font.GetHash(), p.glyph.index, p.glyph.type);
return fml::HashCombine(p.font.GetHash(), p.glyph.index, p.glyph.type,
p.scale);
}
};
struct Equal {
bool operator()(const FontGlyphPair& lhs, const FontGlyphPair& rhs) const {
return lhs.font.IsEqual(rhs.font) && lhs.glyph.index == rhs.glyph.index &&
lhs.glyph.type == rhs.glyph.type;
lhs.glyph.type == rhs.glyph.type && lhs.scale == rhs.scale;
}
};
};

View File

@ -15,12 +15,12 @@ LazyGlyphAtlas::LazyGlyphAtlas() = default;
LazyGlyphAtlas::~LazyGlyphAtlas() = default;
void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame) {
void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame, Scalar scale) {
FML_DCHECK(atlas_map_.empty());
if (frame.GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) {
alpha_frames_.emplace_back(frame);
frame.CollectUniqueFontGlyphPairs(alpha_set_, scale);
} else {
color_frames_.emplace_back(frame);
frame.CollectUniqueFontGlyphPairs(color_set_, scale);
}
}
@ -39,19 +39,9 @@ std::shared_ptr<GlyphAtlas> LazyGlyphAtlas::CreateOrGetGlyphAtlas(
if (!text_context || !text_context->IsValid()) {
return nullptr;
}
size_t i = 0;
auto frames =
type == GlyphAtlas::Type::kAlphaBitmap ? alpha_frames_ : color_frames_;
TextRenderContext::FrameIterator iterator = [&]() -> const TextFrame* {
if (i >= frames.size()) {
return nullptr;
}
const auto& result = frames[i];
i++;
return &result;
};
auto& set = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_set_ : color_set_;
auto atlas =
text_context->CreateGlyphAtlas(type, std::move(atlas_context), iterator);
text_context->CreateGlyphAtlas(type, std::move(atlas_context), set);
if (!atlas || !atlas->IsValid()) {
VALIDATION_LOG << "Could not create valid atlas.";
return nullptr;

View File

@ -19,7 +19,7 @@ class LazyGlyphAtlas {
~LazyGlyphAtlas();
void AddTextFrame(const TextFrame& frame);
void AddTextFrame(const TextFrame& frame, Scalar scale);
std::shared_ptr<GlyphAtlas> CreateOrGetGlyphAtlas(
GlyphAtlas::Type type,
@ -27,8 +27,8 @@ class LazyGlyphAtlas {
std::shared_ptr<Context> context) const;
private:
std::vector<TextFrame> alpha_frames_;
std::vector<TextFrame> color_frames_;
FontGlyphPair::Set alpha_set_;
FontGlyphPair::Set color_set_;
mutable std::unordered_map<GlyphAtlas::Type, std::shared_ptr<GlyphAtlas>>
atlas_map_;

View File

@ -79,4 +79,15 @@ bool TextFrame::MaybeHasOverlapping() const {
return false;
}
void TextFrame::CollectUniqueFontGlyphPairs(FontGlyphPair::Set& set,
Scalar scale) const {
for (const TextRun& run : GetRuns()) {
const Font& font = run.GetFont();
for (const TextRun::GlyphPosition& glyph_position :
run.GetGlyphPositions()) {
set.insert({font, glyph_position.glyph, scale});
}
}
}
} // namespace impeller

View File

@ -22,6 +22,8 @@ class TextFrame {
~TextFrame();
void CollectUniqueFontGlyphPairs(FontGlyphPair::Set& set, Scalar scale) const;
//----------------------------------------------------------------------------
/// @brief The conservative bounding box for this text frame.
///

View File

@ -26,19 +26,4 @@ const std::shared_ptr<Context>& TextRenderContext::GetContext() const {
return context_;
}
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* {
count++;
if (count == 1) {
return &frame;
}
return nullptr;
};
return CreateGlyphAtlas(type, std::move(atlas_context), iterator);
}
} // namespace impeller

View File

@ -37,20 +37,13 @@ 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,
std::shared_ptr<GlyphAtlasContext> atlas_context,
FrameIterator iterator) const = 0;
std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
GlyphAtlas::Type type,
std::shared_ptr<GlyphAtlasContext> atlas_context,
const TextFrame& frame) const;
const FontGlyphPair::Set& font_glyph_pairs) const = 0;
protected:
//----------------------------------------------------------------------------

View File

@ -23,6 +23,17 @@ namespace testing {
using TypographerTest = PlaygroundTest;
INSTANTIATE_PLAYGROUND_SUITE(TypographerTest);
static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
const TextRenderContext* context,
GlyphAtlas::Type type,
Scalar scale,
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
const TextFrame& frame) {
FontGlyphPair::Set set;
frame.CollectUniqueFontGlyphPairs(set, scale);
return context->CreateGlyphAtlas(type, atlas_context, set);
}
TEST_P(TypographerTest, CanConvertTextBlob) {
SkFont font;
auto blob = SkTextBlob::MakeFromString(
@ -49,8 +60,8 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) {
auto blob = SkTextBlob::MakeFromString("hello", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap);
@ -102,13 +113,13 @@ TEST_P(TypographerTest, LazyAtlasTracksColor) {
LazyGlyphAtlas lazy_atlas;
lazy_atlas.AddTextFrame(frame);
lazy_atlas.AddTextFrame(frame, 1.0f);
frame = TextFrameFromTextBlob(SkTextBlob::MakeFromString("😀 ", emoji_font));
ASSERT_TRUE(frame.GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
lazy_atlas.AddTextFrame(frame);
lazy_atlas.AddTextFrame(frame, 1.0f);
// Creates different atlases for color and alpha bitmap.
auto color_context = std::make_shared<GlyphAtlasContext>();
@ -130,8 +141,8 @@ TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
auto blob = SkTextBlob::MakeFromString("AGH", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
@ -147,8 +158,8 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
@ -156,8 +167,8 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
// now attempt to re-create an atlas with the same text blob.
auto next_atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
ASSERT_EQ(atlas, next_atlas);
ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
}
@ -166,7 +177,6 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
auto context = TextRenderContext::Create(GetContext());
auto atlas_context = std::make_shared<GlyphAtlasContext>();
ASSERT_TRUE(context && context->IsValid());
SkFont sk_font;
const char* test_string =
"QWERTYUIOPASDFGHJKLZXCVBNMqewrtyuiopasdfghjklzxcvbnm,.<>[]{};':"
@ -174,22 +184,17 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
"œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/"
"* Í˝ */¸˛Ç◊ı˜Â¯˘¿";
SkFont sk_font;
auto blob = SkTextBlob::MakeFromString(test_string, sk_font);
ASSERT_TRUE(blob);
TextFrame frame;
size_t count = 0;
const int size_count = 8;
TextRenderContext::FrameIterator iterator = [&]() -> const TextFrame* {
if (count < size_count) {
count++;
frame = TextFrameFromTextBlob(blob, 0.6 * count);
return &frame;
}
return nullptr;
FontGlyphPair::Set set;
size_t size_count = 8;
for (size_t index = 0; index < size_count; index += 1) {
TextFrameFromTextBlob(blob).CollectUniqueFontGlyphPairs(set, 0.6 * index);
};
auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap,
atlas_context, iterator);
std::move(atlas_context), set);
ASSERT_NE(atlas, nullptr);
ASSERT_NE(atlas->GetTexture(), nullptr);
@ -217,8 +222,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
auto old_packer = atlas_context->GetRectPacker();
ASSERT_NE(atlas, nullptr);
@ -231,8 +236,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
auto next_atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob2));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob2));
ASSERT_EQ(atlas, next_atlas);
auto* second_texture = next_atlas->GetTexture().get();
@ -250,8 +255,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecreatedIfTypeChanges) {
auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
ASSERT_TRUE(blob);
auto atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context,
TextFrameFromTextBlob(blob));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob));
auto old_packer = atlas_context->GetRectPacker();
ASSERT_NE(atlas, nullptr);
@ -265,8 +270,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecreatedIfTypeChanges) {
auto blob2 = SkTextBlob::MakeFromString("spooky 1", sk_font);
auto next_atlas =
context->CreateGlyphAtlas(GlyphAtlas::Type::kColorBitmap, atlas_context,
TextFrameFromTextBlob(blob2));
CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kColorBitmap, 1.0f,
atlas_context, TextFrameFromTextBlob(blob2));
ASSERT_NE(atlas, next_atlas);
auto* second_texture = next_atlas->GetTexture().get();