Skip to content

Refactor text and runtime effect to separate skia and impeller implementations. #174219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 22, 2025

Conversation

eyebrowsoffire
Copy link
Contributor

This makes two refactors to the display list architecture:

  • The concrete implementations of DlRuntimeEffect for skia and impeller are separated, and the impeller implementation put into the impeller/display_list target. This makes sure that a client can link against the main display_list library without actually pulling in all of impeller. (This is needed for [skwasm] Port to DisplayList objects #172314)
  • The DrawTextBlob and DrawTextFrame methods are consolidated into one DrawText call, and that takes a DlText object. The DlText object has two implementations, one for skia and one for impeller, and the impeller one is moved into impeller/display_list for the same reason mentioned above.

@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems engine flutter/engine repository. See also e: labels. e: impeller Impeller rendering backend issues and features requests labels Aug 21, 2025
gemini-code-assist[bot]

This comment was marked as outdated.

Copy link
Contributor

@flar flar left a comment

Choose a reason for hiding this comment

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

Some things I should have noted before, but most of them are style and better testing related...

@@ -5,5 +5,57 @@
"githubPullRequests.ignoredPullRequestBranches": [
"master"
],
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"files.associations": {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this part of the other PR? It doesn't make sense as part of this PR unless I've missed something...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gahhh this freaking file keeps changing itself and I accidentally added it. I will unstage this change.

receiver.drawTextFrame(text_frame, x, y);
DisplayListCompare equals(const DrawTextOp* other) const {
return text->getTextBlob() == other->text->getTextBlob() &&
text->getTextFrame() == other->text->getTextFrame() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps put this pointer comparison inside a DlText::== operator to hide the details (and in case we eventually have some better way to compare them than by (pair of) subsumed pointer(s)?)

namespace flutter {
class DlText {
public:
virtual DlRect getBounds() const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I know there is a mixed style in DL objects, but I think we are moving towards capitalized getters like GetBounds, GetTextFrame, etc. I wasn't doing that until I started integrating with Impeller, but I think it is better matched to the C++ style guidelines so I'd like to move in that direction.

Technically you can have a strict getter that is lower case, but I don't think either of getTextFoo() are "strict getters" since they are only defined for specific subclasses...

#define FLUTTER_DISPLAY_LIST_DL_TEXT_H_

#include "flutter/display_list/geometry/dl_geometry_types.h"
#include "third_party/skia/include/core/SkRefCnt.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can change the getter to SkTextBlob* and we won't need SkRefCnt included here...?

public:
virtual DlRect getBounds() const = 0;
virtual std::shared_ptr<impeller::TextFrame> getTextFrame() const = 0;
virtual sk_sp<SkTextBlob> getTextBlob() const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

One of the magic things about sk_sp is that you can just use a raw pointer in cases where you are just passing it along (because the ref count is built-in and there is free "shared from this"). So, this could return SkTextBlob*. Indeed, if you look at SkCanvas::drawTextBlob, the real method takes the ptr and the version that takes an sk_sp does a get() on it and passes it to the pointer version...

@@ -12,6 +12,8 @@
#include "flutter/flow/surface_frame.h"
#include "flutter/fml/macros.h"

#include "third_party/skia/include/core/SkData.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this for the DlText changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SurfaceData below uses an SkData. I think we previously got away with it through some kind of transitive include.

}
// 2x3 2D affine subset of a 4x4 transform in row major order
void FirstPassDispatcher::transform2DAffine(
DlScalar mxx, DlScalar mxy, DlScalar mxt,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think your indent is off by 2 for these. Argument continuations are indented 4 spots, not 2. Same for FullPerspective below...

Copy link
Contributor

Choose a reason for hiding this comment

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

These still look off by 2. You got the ones inside the method body, but these argument methods should also be indented by 4 as well.

@@ -1224,7 +1230,8 @@ TEST_F(DisplayListTest, SingleOpsMightSupportGroupOpacityBlendMode) {
static auto display_list = builder.Build();
RUN_TESTS2(canvas.DrawDisplayList(display_list);, false);
}
RUN_TESTS2(canvas.DrawTextBlob(GetTestTextBlob(1), 0, 0, paint);, false);
RUN_TESTS2(canvas.DrawText(DlTextSkia::Make(GetTestTextBlob(1)), 0, 0, paint);
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no test for Impeller Text here (and the one test for it down below is testing a different condition). While the builder code is the same for both, it couldn't hurt to have both tested here so that when we eventually delete Skia we aren't left without any "text" case for this test.

@@ -3516,7 +3523,7 @@ TEST_F(DisplayListTest, NopOperationsOmittedFromRecords) {
builder.DrawAtlas(kTestImage1, xforms, rects, nullptr, 2,
DlBlendMode::kSrcOver, DlImageSampling::kLinear,
nullptr, &paint);
builder.DrawTextBlob(GetTestTextBlob(1), 10, 10, paint);
builder.DrawText(DlTextSkia::Make(GetTestTextBlob(1)), 10, 10, paint);
Copy link
Contributor

Choose a reason for hiding this comment

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

Also need a test to make sure that DrawText(Impeller) is also nop'd.

@@ -5505,13 +5514,16 @@ TEST_F(DisplayListTest, BoundedRenderOpsDoNotReportUnbounded) {
ASSERT_LT(blob->bounds().width(), draw_rect.GetWidth());
ASSERT_LT(blob->bounds().height(), draw_rect.GetHeight());

auto text = DlTextImpeller::Make(frame);
Copy link
Contributor

Choose a reason for hiding this comment

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

Or, makefromblob above instead of blob->frame->text.

@@ -120,8 +120,10 @@ class DisplayListParagraphPainter : public skt::ParagraphPainter {
}
std::shared_ptr<DlText> text;
if (impeller_enabled_) {
#if IMPELLER_SUPPORTS_RENDERING
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have the if inside it as well? Otherwise we might have a mismatch? Or is that possible?

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 don't think it's possible for that to happen, but I can switch this up to cover the whole if statement.

return text->GetTextBlob() == other->text->GetTextBlob() &&
text->GetTextFrame() == other->text->GetTextFrame() &&
x == other->x && y == other->y
return text == other->text && x == other->x && y == other->y
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this only compares the shared pointers. You can use the Equals mechanism (see SetSharedImageFilterOp) which does pointer comparisons and then also *foo == *bar comparisons if needed. You could also do *text == *(other->text), but that gets dangerous if one of them is null. The Equals family of methods protects against that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Equals(text, other->text)

// clang-format off
// full 4x4 transform in row major order
void FirstPassDispatcher::transformFullPerspective(
DlScalar mxx, DlScalar mxy, DlScalar mxz, DlScalar mxt,
Copy link
Contributor

Choose a reason for hiding this comment

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

These argument methods are also indented off by 2.

@@ -1013,7 +1013,7 @@ struct DrawTextOp final : DrawOpBase {
void dispatch(DlOpReceiver& receiver) const { receiver.drawText(text, x, y); }

DisplayListCompare equals(const DrawTextOp* other) const {
return text == other->text && x == other->x && y == other->y
return *text == *other->text && x == other->x && y == other->y
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer Equals(text, other->text), but this is likely safe in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I just reread your comment, I switched it to use Equals() instead

@eyebrowsoffire eyebrowsoffire added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 22, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Aug 22, 2025
Merged via the queue into flutter:master with commit a24dbd5 Aug 22, 2025
186 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems e: impeller Impeller rendering backend issues and features requests engine flutter/engine repository. See also e: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants