Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Sep 10, 2024

Follow up to #55060

Currently we have multiple stages of hashing while font rendering, which is relatively expensive for the actualy required workload. First, we hash the contents of all text frames to compute the unique set of glyphs per frame. Then we diff this hash set against the hashmap of glyphs within the atlas. Finally we hash and lookup the final rendered bounds for each glyph.

We can simplify this to 2. hash lookups for glyphs not yet in the atlas and 1. hash lookup for glyphs that are in the atlas. This is done by combing the step where we uniquely compute glyphs per frame with the diff against the current atlas. When this lookup is performed, we also store the glyph position (if found) in the text_frame itself - which allows text contents to skip the last hash, as long as the glyph has already been rendered.

Before

flutter_03

After

flutter_04

Using this handy dandy test app:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Platform View'),
        ),
        body: SafeArea(child: Stack(children: [
          SizedBox(
            width: 380,
            height: 380,
            child: LinearProgressIndicator(),
          ),
          Stack(
            children: List<Widget>.generate(1000, (index) {
              // The problem already happens with a small amount of widgets.
              // Using an excessive amount of widgets is just to make the problem more evident.
              return Text("Lots of Texts represent a Widget with complex components.");
            }),
          ),

          Align(
            alignment: Alignment.bottomCenter,
            child:
            TextButton(
              child: Text("Button"),
              onPressed: () {
                print("Tap ${DateTime.now()}");
              },
            ),
          ),
        ],
        ),
        ),
      ),
    );
  }
}

@jonahwilliams jonahwilliams marked this pull request as ready for review September 11, 2024 20:48
jonahwilliams added 2 commits September 11, 2024 15:39
@@ -16,6 +16,8 @@ namespace impeller {
/// This object is typically the entrypoint in the Impeller type
/// rendering subsystem.
///
/// A text frame should not be reused in multiple places within a single frame,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this behavior is a problem, we could instead use a different object as a cache, but I think that would be substantially more refactoring for little to no gain, as this condition is always true for us.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good and thanks for the clarification.

std::vector<FontGlyphPair>& new_glyphs,
std::vector<Rect>& glyph_sizes) {
for (const auto& frame : text_frames) {
// TODO(jonahwilliams): unless we destroy the atlas (which we know about),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as the next optimizations. With a little plumbing when we recreate the atlas we can actually avoid any hashing on frames with no new text.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

jonahwilliams added 3 commits September 11, 2024 17:27
@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

Changes reported for pull request #55092 at sha c87ab8b

Copy link
Member

@jtmcdole jtmcdole left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm from me; but you asked for Chinmay

std::vector<TextRun> runs_;
Rect bounds_;
bool has_color_;

// per frame data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit for clarity: "per frame data" -> "per text frame data"

@jonahwilliams
Copy link
Contributor Author

FYI @chinmaygarde this is still waiting on a review from you.

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still waiting on a review from you

Sorry about the delay. All nits.

std::vector<FrameBounds> bound_values_;
Scalar scale_;
Point offset_;
std::optional<GlyphProperties> properties_ = std::nullopt;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= std::nullopt is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// per frame data.
std::vector<FrameBounds> bound_values_;
Scalar scale_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero out the scalar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -16,6 +16,8 @@ namespace impeller {
/// This object is typically the entrypoint in the Impeller type
/// rendering subsystem.
///
/// A text frame should not be reused in multiple places within a single frame,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good and thanks for the clarification.

}

FrameBounds TextFrame::GetFrameBounds(size_t index) {
return bound_values_[index];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use .at(index) you get bounds checking in debug modes for free.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFrameBounds can be const.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Rect glyph_bounds;
/// Whether [atlas_bounds] are still a placeholder and have
/// not yet been computed.
bool placeholder;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero out the scalar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name this is_placeholder instead? While reading this later, I thought placeholder returned a placeholder itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

SubpixelGlyph::Hash,
SubpixelGlyph::Equal>
positions_;

FontGlyphAtlas(const FontGlyphAtlas&) = delete;

FontGlyphAtlas& operator=(const FontGlyphAtlas&) = delete;
// FontGlyphAtlas& operator=(const FontGlyphAtlas&) = delete;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to comment this out? Couldn't find where you don't want to copy but do want to make it copy assigned. At least the rule of 5 thing doesn't track.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about this one. I wasn't sure if we should try to design around this. Basically with this I can't do:

  font_atlas_map_[scaled_font] = FontGlyphAtlas();
  return &font_atlas_map_[scaled_font];

and I'm not sure what the alternative is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the alternative is insert. You provide a pair and it returns an iterator.

}
}
}
CollectNewGlyphs(last_atlas, text_frames, new_glyphs, new_sizes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DCHECK that the sizes of the two out vectors are the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly partial to returning a pair of vectors instead of out variables because it makes it clear that existing values in the vectors will be not be disregarded. I didn't see a place where you didn't have to clear the vectors before calling CollectNewGlyphs. Just one less thing you don't have to remember to do. Then you can use a structured binding to do something like:

auto [new_glyphs, new_sizes] = CollectNewGlyphs(last_atlas, text_frames);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I think I one point we were incrementally adding to these but that is long since gone

std::vector<FontGlyphPair>& new_glyphs,
std::vector<Rect>& glyph_sizes) {
for (const auto& frame : text_frames) {
// TODO(jonahwilliams): unless we destroy the atlas (which we know about),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

@jonahwilliams
Copy link
Contributor Author

Thank you! :)

@jonahwilliams jonahwilliams added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 27, 2024
@auto-submit auto-submit bot merged commit d283bc8 into flutter:main Sep 27, 2024
30 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 27, 2024
@jonahwilliams
Copy link
Contributor Author

reason for revert: framework golden failures.

@jonahwilliams jonahwilliams added the revert Label used to revert changes in a closed and merged pull request. label Sep 27, 2024
auto-submit bot pushed a commit that referenced this pull request Sep 27, 2024
@auto-submit auto-submit bot removed the revert Label used to revert changes in a closed and merged pull request. label Sep 27, 2024
auto-submit bot added a commit that referenced this pull request Sep 27, 2024
Reverts: #55092
Initiated by: jonahwilliams
Reason for reverting: framework golden failures.
Original PR Author: jonahwilliams

Reviewed By: {chinmaygarde, jtmcdole}

This change reverts the following previous change:
Follow up to #55060

Currently we have multiple stages of hashing while font rendering, which is relatively expensive for the actualy required workload. First, we hash the contents of all text frames to compute the unique set of glyphs per frame. Then we diff this hash set against the hashmap of glyphs within the atlas. Finally we hash and lookup the final rendered bounds for each glyph.

We can simplify this to 2. hash lookups for glyphs not yet in the atlas and 1. hash lookup for glyphs that are in the atlas. This is done by combing the step where we uniquely compute glyphs per frame with the diff against the current atlas. When this lookup is performed, we also store the glyph position (if found) in the text_frame itself - which allows text contents to skip the last hash, as long as the glyph has already been rendered.

### Before

![flutter_03](https://github.com/user-attachments/assets/be9c4459-f0c8-426c-b152-38861acb207f)

### After

![flutter_04](https://github.com/user-attachments/assets/1aa7cbd1-6af7-4020-8966-7e3baaef102b)

Using this handy dandy test app:

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  Widget build(context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Platform View'),
        ),
        body: SafeArea(child: Stack(children: [
          SizedBox(
            width: 380,
            height: 380,
            child: LinearProgressIndicator(),
          ),
          Stack(
            children: List<Widget>.generate(1000, (index) {
              // The problem already happens with a small amount of widgets.
              // Using an excessive amount of widgets is just to make the problem more evident.
              return Text("Lots of Texts represent a Widget with complex components.");
            }),
          ),

          Align(
            alignment: Alignment.bottomCenter,
            child:
            TextButton(
              child: Text("Button"),
              onPressed: () {
                print("Tap ${DateTime.now()}");
              },
            ),
          ),
        ],
        ),
        ),
      ),
    );
  }
}
```
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 27, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 27, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 27, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Sep 27, 2024
…155843)

flutter/engine@7c603de...f21f2b2

2024-09-27 [email protected] [docs] Fix broken links in docs/ (flutter/engine#55350)
2024-09-27 [email protected] Roll Skia from e77818421e91 to 7efc11f2ea9e (6 revisions) (flutter/engine#55489)
2024-09-27 [email protected] Listen for uncaught exceptions during loading of a web test suite in Chrome (flutter/engine#55166)
2024-09-27 98614782+auto-submit[bot]@users.noreply.github.com Reverts "[Impeller] hash even less stuff per frame. (#55092)" (flutter/engine#55491)
2024-09-27 [email protected] [web] Update builder json generator to reflect recent changes (flutter/engine#55307)
2024-09-27 [email protected] [Impeller] hash even less stuff per frame. (flutter/engine#55092)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
thejitenpatel pushed a commit to thejitenpatel/flutter that referenced this pull request Oct 1, 2024
…lutter#155843)

flutter/engine@7c603de...f21f2b2

2024-09-27 [email protected] [docs] Fix broken links in docs/ (flutter/engine#55350)
2024-09-27 [email protected] Roll Skia from e77818421e91 to 7efc11f2ea9e (6 revisions) (flutter/engine#55489)
2024-09-27 [email protected] Listen for uncaught exceptions during loading of a web test suite in Chrome (flutter/engine#55166)
2024-09-27 98614782+auto-submit[bot]@users.noreply.github.com Reverts "[Impeller] hash even less stuff per frame. (flutter#55092)" (flutter/engine#55491)
2024-09-27 [email protected] [web] Update builder json generator to reflect recent changes (flutter/engine#55307)
2024-09-27 [email protected] [Impeller] hash even less stuff per frame. (flutter/engine#55092)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit bot pushed a commit that referenced this pull request Oct 25, 2024
Reverted due to missing glyph when rolling into the framework. Problem was that we should be always using the unrounded rounded scale to compute subpixel position but we sometimes used the rounded scale.

---

Currently we have multiple stages of hashing while font rendering, which is relatively expensive for the actualy required workload. First, we hash the contents of all text frames to compute the unique set of glyphs per frame. Then we diff this hash set against the hashmap of glyphs within the atlas. Finally we hash and lookup the final rendered bounds for each glyph.

We can simplify this to 2. hash lookups for glyphs not yet in the atlas and 1. hash lookup for glyphs that are in the atlas. This is done by combing the step where we uniquely compute glyphs per frame with the diff against the current atlas. When this lookup is performed, we also store the glyph position (if found) in the text_frame itself - which allows text contents to skip the last hash, as long as the glyph has already been rendered.

See #55092
nick9822 pushed a commit to nick9822/flutter that referenced this pull request Dec 18, 2024
Reverted due to missing glyph when rolling into the framework. Problem was that we should be always using the unrounded rounded scale to compute subpixel position but we sometimes used the rounded scale.

---

Currently we have multiple stages of hashing while font rendering, which is relatively expensive for the actualy required workload. First, we hash the contents of all text frames to compute the unique set of glyphs per frame. Then we diff this hash set against the hashmap of glyphs within the atlas. Finally we hash and lookup the final rendered bounds for each glyph.

We can simplify this to 2. hash lookups for glyphs not yet in the atlas and 1. hash lookup for glyphs that are in the atlas. This is done by combing the step where we uniquely compute glyphs per frame with the diff against the current atlas. When this lookup is performed, we also store the glyph position (if found) in the text_frame itself - which allows text contents to skip the last hash, as long as the glyph has already been rendered.

See flutter/engine#55092
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App e: impeller platform-ios will affect goldens
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants