diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 7fe1b6bcaab8d..b8a5c7dc73357 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2877,35 +2877,6 @@ 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, diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 9e9b89e25a041..82424b2c86d59 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -42,6 +42,7 @@ void Canvas::Initialize(std::optional cull_rect) { base_pass_ = std::make_unique(); current_pass_ = base_pass_.get(); xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect}); + lazy_glyph_atlas_ = std::make_shared(); FML_DCHECK(GetSaveCount() == 1u); FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u); } @@ -50,6 +51,7 @@ void Canvas::Reset() { base_pass_ = nullptr; current_pass_ = nullptr; xformation_stack_ = {}; + lazy_glyph_atlas_ = nullptr; } void Canvas::Save() { @@ -514,12 +516,15 @@ 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(); 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(); diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 84f1c486bc30f..aa3da9f0681a2 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -162,6 +162,7 @@ class Canvas { std::unique_ptr base_pass_; EntityPass* current_pass_ = nullptr; std::deque xformation_stack_; + std::shared_ptr lazy_glyph_atlas_; std::optional initial_cull_rect_; void Initialize(std::optional cull_rect); diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 9964e9e71eaf9..51ba0ea924782 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -1104,7 +1104,8 @@ void DlDispatcher::drawDisplayList( void DlDispatcher::drawTextBlob(const sk_sp blob, SkScalar x, SkScalar y) { - const auto text_frame = TextFrameFromTextBlob(blob); + Scalar scale = canvas_.GetCurrentTransformation().GetMaxBasisLengthXY(); + const auto text_frame = TextFrameFromTextBlob(blob, scale); if (paint_.style == Paint::Style::kStroke) { auto path = skia_conversions::PathDataFromTextBlob(blob); auto bounds = text_frame.GetBounds(); diff --git a/impeller/entity/contents/color_source_text_contents.cc b/impeller/entity/contents/color_source_text_contents.cc index a09e44c8dcb35..16407e0bb2da0 100644 --- a/impeller/entity/contents/color_source_text_contents.cc +++ b/impeller/entity/contents/color_source_text_contents.cc @@ -4,7 +4,6 @@ #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" @@ -34,12 +33,6 @@ void ColorSourceTextContents::SetTextPosition(Point position) { position_ = position; } -void ColorSourceTextContents::PopulateGlyphAtlas( - const std::shared_ptr& 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 { diff --git a/impeller/entity/contents/color_source_text_contents.h b/impeller/entity/contents/color_source_text_contents.h index d66a7ea313458..e93738c8f3760 100644 --- a/impeller/entity/contents/color_source_text_contents.h +++ b/impeller/entity/contents/color_source_text_contents.h @@ -32,11 +32,6 @@ class ColorSourceTextContents final : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; - // |Contents| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 7693dfd3622f7..d2e1a16c8f92c 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -696,18 +696,8 @@ class ContentContext { const SubpassCallback& subpass_callback, bool msaa_enabled = true) const; - void SetLazyGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas) { - lazy_glyph_atlas_ = lazy_glyph_atlas; - } - - std::shared_ptr GetLazyGlyphAtlas() const { - return lazy_glyph_atlas_; - } - private: std::shared_ptr context_; - std::shared_ptr lazy_glyph_atlas_; template using Variants = std::unordered_map& lazy_glyph_atlas, - Scalar scale) const {} - virtual bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const = 0; diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 5428cf830feba..a4e46641b2e14 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -29,15 +29,18 @@ void TextContents::SetTextFrame(const TextFrame& frame) { frame_ = frame; } +void TextContents::SetGlyphAtlas(std::shared_ptr atlas) { + lazy_atlas_ = std::move(atlas); +} + std::shared_ptr TextContents::ResolveAtlas( GlyphAtlas::Type type, - const std::shared_ptr& lazy_atlas, std::shared_ptr atlas_context, std::shared_ptr 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; @@ -75,43 +78,14 @@ std::optional TextContents::GetCoverage(const Entity& entity) const { return bounds->TransformBounds(entity.GetTransformation()); } -void TextContents::PopulateGlyphAtlas( - const std::shared_ptr& 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(); - +static bool CommonRender(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + const Color& color, + const TextFrame& frame, + Vector2 offset, + const std::shared_ptr& atlas, + Command& cmd) { using VS = GlyphAtlasPipeline::VertexShader; using FS = GlyphAtlasPipeline::FragmentShader; @@ -121,7 +95,7 @@ bool TextContents::Render(const ContentContext& renderer, frame_info.atlas_size = Vector2{static_cast(atlas->GetTexture()->GetSize().width), static_cast(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(); @@ -167,7 +141,7 @@ bool TextContents::Render(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; @@ -177,10 +151,10 @@ bool TextContents::Render(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, scale}; + FontGlyphPair font_glyph_pair{font, glyph_position.glyph}; auto maybe_atlas_glyph_bounds = atlas->FindFontGlyphBounds(font_glyph_pair); if (!maybe_atlas_glyph_bounds.has_value()) { @@ -217,4 +191,37 @@ bool TextContents::Render(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 diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h index fe89068ae2a14..14bd354086e72 100644 --- a/impeller/entity/contents/text_contents.h +++ b/impeller/entity/contents/text_contents.h @@ -28,14 +28,14 @@ class TextContents final : public Contents { void SetTextFrame(const TextFrame& frame); + void SetGlyphAtlas(std::shared_ptr 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,11 +45,6 @@ class TextContents final : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; - // |Contents| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, @@ -59,11 +54,11 @@ class TextContents final : public Contents { TextFrame frame_; Color color_; Scalar inherited_opacity_ = 1.0; + mutable std::shared_ptr lazy_atlas_; Vector2 offset_; std::shared_ptr ResolveAtlas( GlyphAtlas::Type type, - const std::shared_ptr& lazy_atlas, std::shared_ptr atlas_context, std::shared_ptr context) const; diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index ad6a6842d68ba..d0b2226735fc5 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -166,8 +166,4 @@ bool Entity::Render(const ContentContext& renderer, return contents_->Render(renderer, *this, parent_pass); } -Scalar Entity::DeriveTextScale() const { - return GetTransformation().GetMaxBasisLengthXY(); -} - } // namespace impeller diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 271f9a0db8248..2063213b18072 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -94,8 +94,6 @@ class Entity { std::optional AsBackgroundColor(ISize target_size) const; - Scalar DeriveTextScale() const; - private: Matrix transformation_; std::shared_ptr contents_; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index af074caebd278..19b1a2efd1a33 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -8,7 +8,6 @@ #include #include -#include "flutter/fml/closure.h" #include "flutter/fml/logging.h" #include "flutter/fml/macros.h" #include "flutter/fml/trace_event.h" @@ -253,18 +252,6 @@ bool EntityPass::Render(ContentContext& renderer, return false; } - renderer.SetLazyGlyphAtlas(std::make_shared()); - 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}}; @@ -891,28 +878,6 @@ void EntityPass::IterateAllEntities( } } -void EntityPass::IterateAllEntities( - const std::function& iterator) const { - if (!iterator) { - return; - } - - for (const auto& element : elements_) { - if (auto entity = std::get_if(&element)) { - if (!iterator(*entity)) { - return; - } - continue; - } - if (auto subpass = std::get_if>(&element)) { - const EntityPass* entity_pass = subpass->get(); - entity_pass->IterateAllEntities(iterator); - continue; - } - FML_UNREACHABLE(); - } -} - bool EntityPass::IterateUntilSubpass( const std::function& iterator) { if (!iterator) { diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index d90abfb99e568..e3dbada3d7eed 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -79,12 +79,6 @@ class EntityPass { /// of child passes. The iteration order is depth-first. void IterateAllEntities(const std::function& 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& iterator) const; - /// @brief Iterate entities in this pass up until the first subpass is found. /// This is useful for limiting look-ahead optimizations. /// diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 05818e84270f7..28bc4d8d11493 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -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(); - lazy_glyph_atlas->AddTextFrame(frame, 1.0f); + lazy_glyph_atlas->AddTextFrame(frame); auto text_contents = std::make_shared(); text_contents->SetTextFrame(frame); diff --git a/impeller/typographer/backends/skia/text_frame_skia.cc b/impeller/typographer/backends/skia/text_frame_skia.cc index cac02a80968b6..e451695dc8b6d 100644 --- a/impeller/typographer/backends/skia/text_frame_skia.cc +++ b/impeller/typographer/backends/skia/text_frame_skia.cc @@ -17,7 +17,7 @@ namespace impeller { -static Font ToFont(const SkTextBlobRunIterator& run) { +static Font ToFont(const SkTextBlobRunIterator& run, Scalar scale) { auto& font = run.font(); auto typeface = std::make_shared(font.refTypefaceOrDefault()); @@ -25,6 +25,7 @@ static Font ToFont(const SkTextBlobRunIterator& run) { font.getMetrics(&sk_metrics); Font::Metrics metrics; + metrics.scale = scale; metrics.point_size = font.getSize(); metrics.embolden = font.isEmbolden(); metrics.skewX = font.getSkewX(); @@ -37,7 +38,7 @@ static Rect ToRect(const SkRect& rect) { return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); } -TextFrame TextFrameFromTextBlob(const sk_sp& blob) { +TextFrame TextFrameFromTextBlob(const sk_sp& blob, Scalar scale) { if (!blob) { return {}; } @@ -45,7 +46,7 @@ TextFrame TextFrameFromTextBlob(const sk_sp& blob) { TextFrame frame; for (SkTextBlobRunIterator run(blob.get()); !run.done(); run.next()) { - TextRun text_run(ToFont(run)); + TextRun text_run(ToFont(run, scale)); // TODO(jonahwilliams): ask Skia for a public API to look this up. // https://github.com/flutter/flutter/issues/112005 diff --git a/impeller/typographer/backends/skia/text_frame_skia.h b/impeller/typographer/backends/skia/text_frame_skia.h index a12b0f5ec60fa..80d6c33921fa3 100644 --- a/impeller/typographer/backends/skia/text_frame_skia.h +++ b/impeller/typographer/backends/skia/text_frame_skia.h @@ -10,6 +10,7 @@ namespace impeller { -TextFrame TextFrameFromTextBlob(const sk_sp& blob); +TextFrame TextFrameFromTextBlob(const sk_sp& blob, + Scalar scale = 1.0f); } // namespace impeller diff --git a/impeller/typographer/backends/skia/text_render_context_skia.cc b/impeller/typographer/backends/skia/text_render_context_skia.cc index 8553ce87a7ed4..9028a5cba0cc3 100644 --- a/impeller/typographer/backends/skia/text_render_context_skia.cc +++ b/impeller/typographer/backends/skia/text_render_context_skia.cc @@ -40,6 +40,23 @@ TextRenderContextSkia::TextRenderContextSkia(std::shared_ptr 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, @@ -56,7 +73,8 @@ 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.scale).size); + const auto glyph_size = + ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size); IPoint16 location_in_atlas; if (!rect_packer->addRect(glyph_size.width + kPadding, // glyph_size.height + kPadding, // @@ -93,7 +111,8 @@ 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.scale).size); + const auto glyph_size = + ISize::Ceil((pair.glyph.bounds * pair.font.GetMetrics().scale).size); IPoint16 location_in_atlas; if (!rect_packer->addRect(glyph_size.width + kPadding, // glyph_size.height + kPadding, // @@ -157,8 +176,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 / font_glyph.scale, - location.origin.y / font_glyph.scale); + 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( @@ -173,7 +192,7 @@ static void DrawGlyph(SkCanvas* canvas, SkPaint glyph_paint; glyph_paint.setColor(glyph_color); canvas->resetMatrix(); - canvas->scale(font_glyph.scale, font_glyph.scale); + canvas->scale(metrics.scale, metrics.scale); canvas->drawGlyphs( 1u, // count &glyph_id, // glyphs @@ -312,19 +331,25 @@ static std::shared_ptr UploadGlyphTextureAtlas( std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( GlyphAtlas::Type type, std::shared_ptr atlas_context, - const FontGlyphPair::Set& font_glyph_pairs) const { + FrameIterator frame_iterator) const { TRACE_EVENT0("impeller", __FUNCTION__); if (!IsValid()) { return nullptr; } std::shared_ptr 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 1: Determine if the atlas type and font glyph pairs are compatible + // Step 2: Determine if the atlas type and font glyph pairs are compatible // with the current atlas and reuse if possible. // --------------------------------------------------------------------------- FontGlyphPairRefVector new_glyphs; @@ -338,7 +363,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 2: Determine if the additional missing glyphs can be appended to the + // Step 3: Determine if the additional missing glyphs can be appended to the // existing bitmap without recreating the atlas. This requires that // the type is identical. // --------------------------------------------------------------------------- @@ -351,7 +376,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( // added. // --------------------------------------------------------------------------- - // Step 3a: Record the positions in the glyph atlas of the newly 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++) { @@ -359,7 +384,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 4a: Draw new font-glyph pairs into the existing bitmap. + // Step 5: Draw new font-glyph pairs into the existing bitmap. // --------------------------------------------------------------------------- auto bitmap = atlas_context->GetBitmap(); if (!UpdateAtlasBitmap(*last_atlas, bitmap, new_glyphs)) { @@ -367,7 +392,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 5a: Update the existing texture with the updated bitmap. + // Step 6: Update the existing texture with the updated bitmap. // --------------------------------------------------------------------------- if (!UpdateGlyphTextureAtlas(bitmap, last_atlas->GetTexture())) { return nullptr; @@ -377,7 +402,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( // A new glyph atlas must be created. // --------------------------------------------------------------------------- - // Step 3b: Get the optimum size of the texture atlas. + // Step 4: Get the optimum size of the texture atlas. // --------------------------------------------------------------------------- auto glyph_atlas = std::make_shared(type); auto atlas_size = OptimumAtlasSizeForFontGlyphPairs( @@ -388,7 +413,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( return nullptr; } // --------------------------------------------------------------------------- - // Step 4b: 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. @@ -398,7 +423,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 5b: Record the positions in the glyph atlas. + // Step 6: Record the positions in the glyph atlas. // --------------------------------------------------------------------------- { size_t i = 0; @@ -409,7 +434,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 6b: 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) { @@ -418,7 +443,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( atlas_context->UpdateBitmap(bitmap); // --------------------------------------------------------------------------- - // Step 7b: Upload the atlas as a texture. + // Step 8: Upload the atlas as a texture. // --------------------------------------------------------------------------- PixelFormat format; switch (type) { @@ -436,7 +461,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( } // --------------------------------------------------------------------------- - // Step 8b: Record the texture in the glyph atlas. + // Step 9: Record the texture in the glyph atlas. // --------------------------------------------------------------------------- glyph_atlas->SetTexture(std::move(texture)); diff --git a/impeller/typographer/backends/skia/text_render_context_skia.h b/impeller/typographer/backends/skia/text_render_context_skia.h index 2e2e6ad97cf8b..0c97a8716e7ad 100644 --- a/impeller/typographer/backends/skia/text_render_context_skia.h +++ b/impeller/typographer/backends/skia/text_render_context_skia.h @@ -19,7 +19,7 @@ class TextRenderContextSkia : public TextRenderContext { std::shared_ptr CreateGlyphAtlas( GlyphAtlas::Type type, std::shared_ptr atlas_context, - const FontGlyphPair::Set& font_glyph_pairs) const override; + FrameIterator iterator) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(TextRenderContextSkia); diff --git a/impeller/typographer/font.h b/impeller/typographer/font.h index bbfc569860b72..de80478ccbaf6 100644 --- a/impeller/typographer/font.h +++ b/impeller/typographer/font.h @@ -28,6 +28,12 @@ class Font : public Comparable { /// 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. /// @@ -37,8 +43,8 @@ class Font : public Comparable { Scalar scaleX = 1.0f; constexpr bool operator==(const Metrics& o) const { - return point_size == o.point_size && embolden == o.embolden && - skewX == o.skewX && scaleX == o.scaleX; + return scale == o.scale && point_size == o.point_size && + embolden == o.embolden && skewX == o.skewX && scaleX == o.scaleX; } }; @@ -74,6 +80,6 @@ class Font : public Comparable { template <> struct std::hash { constexpr std::size_t operator()(const impeller::Font::Metrics& m) const { - return fml::HashCombine(m.point_size, m.skewX, m.scaleX); + return fml::HashCombine(m.scale, m.point_size); } }; diff --git a/impeller/typographer/font_glyph_pair.h b/impeller/typographer/font_glyph_pair.h index 0b89c803af88e..ed455095356dd 100644 --- a/impeller/typographer/font_glyph_pair.h +++ b/impeller/typographer/font_glyph_pair.h @@ -16,29 +16,28 @@ namespace impeller { //------------------------------------------------------------------------------ -/// @brief A font along with a glyph in that font rendered at a particular -/// scale. Used in glyph atlases as keys. +/// @brief A font along with a glyph in that font. Used in glyph atlases as +/// keys. /// struct FontGlyphPair { struct Hash; struct Equal; using Set = std::unordered_set; + using Vector = std::vector; 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, - p.scale); + return fml::HashCombine(p.font.GetHash(), p.glyph.index, p.glyph.type); } }; 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.scale == rhs.scale; + lhs.glyph.type == rhs.glyph.type; } }; }; diff --git a/impeller/typographer/lazy_glyph_atlas.cc b/impeller/typographer/lazy_glyph_atlas.cc index 94c738e37b798..799b56a35877f 100644 --- a/impeller/typographer/lazy_glyph_atlas.cc +++ b/impeller/typographer/lazy_glyph_atlas.cc @@ -15,12 +15,12 @@ LazyGlyphAtlas::LazyGlyphAtlas() = default; LazyGlyphAtlas::~LazyGlyphAtlas() = default; -void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame, Scalar scale) { +void LazyGlyphAtlas::AddTextFrame(const TextFrame& frame) { FML_DCHECK(atlas_map_.empty()); if (frame.GetAtlasType() == GlyphAtlas::Type::kAlphaBitmap) { - frame.CollectUniqueFontGlyphPairs(alpha_set_, scale); + alpha_frames_.emplace_back(frame); } else { - frame.CollectUniqueFontGlyphPairs(color_set_, scale); + color_frames_.emplace_back(frame); } } @@ -39,9 +39,19 @@ std::shared_ptr LazyGlyphAtlas::CreateOrGetGlyphAtlas( if (!text_context || !text_context->IsValid()) { return nullptr; } - auto& set = type == GlyphAtlas::Type::kAlphaBitmap ? alpha_set_ : color_set_; + 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 atlas = - text_context->CreateGlyphAtlas(type, std::move(atlas_context), set); + text_context->CreateGlyphAtlas(type, std::move(atlas_context), iterator); if (!atlas || !atlas->IsValid()) { VALIDATION_LOG << "Could not create valid atlas."; return nullptr; diff --git a/impeller/typographer/lazy_glyph_atlas.h b/impeller/typographer/lazy_glyph_atlas.h index f79b46db9d383..067601e65e64f 100644 --- a/impeller/typographer/lazy_glyph_atlas.h +++ b/impeller/typographer/lazy_glyph_atlas.h @@ -19,7 +19,7 @@ class LazyGlyphAtlas { ~LazyGlyphAtlas(); - void AddTextFrame(const TextFrame& frame, Scalar scale); + void AddTextFrame(const TextFrame& frame); std::shared_ptr CreateOrGetGlyphAtlas( GlyphAtlas::Type type, @@ -27,8 +27,8 @@ class LazyGlyphAtlas { std::shared_ptr context) const; private: - FontGlyphPair::Set alpha_set_; - FontGlyphPair::Set color_set_; + std::vector alpha_frames_; + std::vector color_frames_; mutable std::unordered_map> atlas_map_; diff --git a/impeller/typographer/text_frame.cc b/impeller/typographer/text_frame.cc index 4191446f05462..ed3c97cc82b3e 100644 --- a/impeller/typographer/text_frame.cc +++ b/impeller/typographer/text_frame.cc @@ -79,15 +79,4 @@ 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 diff --git a/impeller/typographer/text_frame.h b/impeller/typographer/text_frame.h index 512f7b705e3e3..a47e4c0e2252a 100644 --- a/impeller/typographer/text_frame.h +++ b/impeller/typographer/text_frame.h @@ -22,8 +22,6 @@ class TextFrame { ~TextFrame(); - void CollectUniqueFontGlyphPairs(FontGlyphPair::Set& set, Scalar scale) const; - //---------------------------------------------------------------------------- /// @brief The conservative bounding box for this text frame. /// diff --git a/impeller/typographer/text_render_context.cc b/impeller/typographer/text_render_context.cc index 1b7aba10a7b74..8cc8dbf00a02e 100644 --- a/impeller/typographer/text_render_context.cc +++ b/impeller/typographer/text_render_context.cc @@ -26,4 +26,19 @@ const std::shared_ptr& TextRenderContext::GetContext() const { return context_; } +std::shared_ptr TextRenderContext::CreateGlyphAtlas( + GlyphAtlas::Type type, + std::shared_ptr 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 diff --git a/impeller/typographer/text_render_context.h b/impeller/typographer/text_render_context.h index d909388105f93..c63ac912a1fa8 100644 --- a/impeller/typographer/text_render_context.h +++ b/impeller/typographer/text_render_context.h @@ -37,13 +37,20 @@ class TextRenderContext { /// const std::shared_ptr& GetContext() const; + using FrameIterator = std::function; + // 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 CreateGlyphAtlas( GlyphAtlas::Type type, std::shared_ptr atlas_context, - const FontGlyphPair::Set& font_glyph_pairs) const = 0; + FrameIterator iterator) const = 0; + + std::shared_ptr CreateGlyphAtlas( + GlyphAtlas::Type type, + std::shared_ptr atlas_context, + const TextFrame& frame) const; protected: //---------------------------------------------------------------------------- diff --git a/impeller/typographer/typographer_unittests.cc b/impeller/typographer/typographer_unittests.cc index e800ea258b44e..e807ac8a26d74 100644 --- a/impeller/typographer/typographer_unittests.cc +++ b/impeller/typographer/typographer_unittests.cc @@ -23,17 +23,6 @@ namespace testing { using TypographerTest = PlaygroundTest; INSTANTIATE_PLAYGROUND_SUITE(TypographerTest); -static std::shared_ptr CreateGlyphAtlas( - const TextRenderContext* context, - GlyphAtlas::Type type, - Scalar scale, - const std::shared_ptr& 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( @@ -60,8 +49,8 @@ TEST_P(TypographerTest, CanCreateGlyphAtlas) { auto blob = SkTextBlob::MakeFromString("hello", sk_font); ASSERT_TRUE(blob); auto atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap); @@ -113,13 +102,13 @@ TEST_P(TypographerTest, LazyAtlasTracksColor) { LazyGlyphAtlas lazy_atlas; - lazy_atlas.AddTextFrame(frame, 1.0f); + lazy_atlas.AddTextFrame(frame); frame = TextFrameFromTextBlob(SkTextBlob::MakeFromString("😀 ", emoji_font)); ASSERT_TRUE(frame.GetAtlasType() == GlyphAtlas::Type::kColorBitmap); - lazy_atlas.AddTextFrame(frame, 1.0f); + lazy_atlas.AddTextFrame(frame); // Creates different atlases for color and alpha bitmap. auto color_context = std::make_shared(); @@ -141,8 +130,8 @@ TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) { auto blob = SkTextBlob::MakeFromString("AGH", sk_font); ASSERT_TRUE(blob); auto atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); @@ -158,8 +147,8 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) { auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font); ASSERT_TRUE(blob); auto atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas()); @@ -167,8 +156,8 @@ TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) { // now attempt to re-create an atlas with the same text blob. auto next_atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); ASSERT_EQ(atlas, next_atlas); ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas); } @@ -177,6 +166,7 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) { auto context = TextRenderContext::Create(GetContext()); auto atlas_context = std::make_shared(); ASSERT_TRUE(context && context->IsValid()); + SkFont sk_font; const char* test_string = "QWERTYUIOPASDFGHJKLZXCVBNMqewrtyuiopasdfghjklzxcvbnm,.<>[]{};':" @@ -184,17 +174,22 @@ TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) { "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/" "* Í˝ */¸˛Ç◊ı˜Â¯˘¿"; - SkFont sk_font; auto blob = SkTextBlob::MakeFromString(test_string, sk_font); ASSERT_TRUE(blob); - 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); + 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; }; auto atlas = context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, - std::move(atlas_context), set); + atlas_context, iterator); ASSERT_NE(atlas, nullptr); ASSERT_NE(atlas->GetTexture(), nullptr); @@ -222,8 +217,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font); ASSERT_TRUE(blob); auto atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); auto old_packer = atlas_context->GetRectPacker(); ASSERT_NE(atlas, nullptr); @@ -236,8 +231,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) { auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font); auto next_atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob2)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob2)); ASSERT_EQ(atlas, next_atlas); auto* second_texture = next_atlas->GetTexture().get(); @@ -255,8 +250,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecreatedIfTypeChanges) { auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font); ASSERT_TRUE(blob); auto atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kAlphaBitmap, atlas_context, + TextFrameFromTextBlob(blob)); auto old_packer = atlas_context->GetRectPacker(); ASSERT_NE(atlas, nullptr); @@ -270,8 +265,8 @@ TEST_P(TypographerTest, GlyphAtlasTextureIsRecreatedIfTypeChanges) { auto blob2 = SkTextBlob::MakeFromString("spooky 1", sk_font); auto next_atlas = - CreateGlyphAtlas(context.get(), GlyphAtlas::Type::kColorBitmap, 1.0f, - atlas_context, TextFrameFromTextBlob(blob2)); + context->CreateGlyphAtlas(GlyphAtlas::Type::kColorBitmap, atlas_context, + TextFrameFromTextBlob(blob2)); ASSERT_NE(atlas, next_atlas); auto* second_texture = next_atlas->GetTexture().get();