Skip to content
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

Make images contribute to Picture allocation size, more aggressively release references when dispose is called #18706

Merged
merged 12 commits into from Jun 2, 2020

Conversation

dnfield
Copy link
Contributor

@dnfield dnfield commented Jun 1, 2020

SkImage references get held by both our Image and Picture objects. The GC has no idea about this, and so sometimes pictures look very small when they're actually holding a reference to a large chunk of memory. This patch helps make sure that the GC can more adequately catch the real size impact of Picture objects, and combined with an upstream patch in Dart allows for much more aggressive collection of unused image related memory.

Unfortunately, this may give an incorrect view of memory to devtools - /cc @liyuqian @terrylucas @devoncarew. The fact that we're reporting image memory size in two places does not necessarily mean we've allocated that much memory in the backend (e.g. Skia might have some kind of CoW feature at work, and the Image is only referenced in a single Picture).

Fixes flutter/flutter#57746
Part of flutter/flutter#56482 (needs https://dart-review.googlesource.com/c/sdk/+/149521 as well for that issue)
Fixes flutter/flutter#28912

/cc @ds84182

@@ -106,12 +106,12 @@ class Canvas : public RefCountedDartWrappable<Canvas> {
void drawPath(const CanvasPath* path,
const Paint& paint,
const PaintData& paint_data);
void drawImage(const CanvasImage* image,
void drawImage(CanvasImage* image,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason to drop const is GetAllocationSize is not const. Filed flutter/flutter#58422 to investigate making it const and adding the qualifiers back here.

Copy link
Member

Choose a reason for hiding this comment

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

Looks like you just have to add the const qualifier to GetAllocationSize. I checked out the implementations and didn't see any mutations.

Copy link
Member

Choose a reason for hiding this comment

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

Can you just address this issue in this patch? Tonic is in this repo now so it should be straightforward. You call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened #18713 so that doesn't get reverted if this does. I'll merge that one into this.

canvas.drawImageRect(image, rect, rect, Paint());
canvas.drawImageNine(image, rect, rect, Paint());
final Picture picture = recorder.endRecording();
expect(picture.approximateBytesUsed, 161235);
Copy link
Member

Choose a reason for hiding this comment

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

I'd break down the calculation of these numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That'll be more possible if we expose this on Canvas, which as I think about it, we should probably do. @chinmaygarde does that SGTY? While the Canvas is still alive, it holds a reference to the SkImage in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't really expose as much on the Dart side here either - for example, we don't expose the image allocation size

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(and if there's something I'm missing here that you think would just make the test better I'm open to suggestions)

Copy link
Member

Choose a reason for hiding this comment

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

I meant like

int expectedBytes = /*foo*/ 123 + /*bar*/ 567 + (4 * /*baz*/ 89);  
expect (picture.approximateBytesUsed, expectedBytes);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh ok, I can do that :)

@@ -106,12 +106,12 @@ class Canvas : public RefCountedDartWrappable<Canvas> {
void drawPath(const CanvasPath* path,
const Paint& paint,
const PaintData& paint_data);
void drawImage(const CanvasImage* image,
void drawImage(CanvasImage* image,
Copy link
Member

Choose a reason for hiding this comment

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

Looks like you just have to add the const qualifier to GetAllocationSize. I checked out the implementations and didn't see any mutations.

@@ -43,10 +45,14 @@ class Picture : public RefCountedDartWrappable<Picture> {
uint32_t height,
Dart_Handle raw_image_callback);

size_t ImageAllocationSize() { return image_allocation_size_; }
Copy link
Member

Choose a reason for hiding this comment

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

const method

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

@@ -86,7 +86,7 @@ fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
return canvas;
}

Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}
Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas), image_allocation_size_(0) {}
Copy link
Member

Choose a reason for hiding this comment

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

fyi some people prefer initialization like this in the header since it's less likely to cause problems in the future if someone adds a constructor. I'm fine as long as there is an error if it isn't initialized.

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

@@ -293,10 +293,11 @@ void Canvas::drawImage(const CanvasImage* image,
if (!image)
Dart_ThrowException(
ToDart("Canvas.drawImage called with non-genuine Image."));
image_allocation_size_ += image->GetAllocationSize();
canvas_->drawImage(image->image(), x, y, paint.paint());
Copy link
Member

Choose a reason for hiding this comment

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

Is this accurate? I would have expected the canvas to rasterize the drawn image at some point so it doesn't actually hold on to the full image.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's going to hold a reference to the SkImage as long as its alive. The memory is not necessarily duplicated (e.g. we don't necessarily have 2x the pixel allocation for the extra reference), but we need to do a better job of letting the GC know how expensive the object is, otherwise it will think it can skip a GC pass when it really needs one.

Copy link
Member

Choose a reason for hiding this comment

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

It might help to clarify the semantics of "allocation size". The allocation size is not the size of the allocation that will be collected when the owning object is collected. Instead, it is the size of the allocation that is guaranteed to stay alive pending the collection of the owning object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As in a doc comment in dart_wrappable.h?

Copy link
Member

Choose a reason for hiding this comment

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

Sure. I don't know of a better place.

@@ -86,7 +86,7 @@ fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
return canvas;
}

Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}
Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas), image_allocation_size_(0) {}
Copy link
Member

Choose a reason for hiding this comment

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

Can you just default this in the header please. This TU is old enough that that this wasn't possible when it was first written.

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

@@ -106,12 +106,12 @@ class Canvas : public RefCountedDartWrappable<Canvas> {
void drawPath(const CanvasPath* path,
const Paint& paint,
const PaintData& paint_data);
void drawImage(const CanvasImage* image,
void drawImage(CanvasImage* image,
Copy link
Member

Choose a reason for hiding this comment

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

Can you just address this issue in this patch? Tonic is in this repo now so it should be straightforward. You call.

@@ -348,6 +351,7 @@ void Canvas::drawPicture(Picture* picture) {
if (!picture)
Dart_ThrowException(
ToDart("Canvas.drawPicture called with non-genuine Picture."));
image_allocation_size_ += picture->GetAllocationSize();
Copy link
Member

Choose a reason for hiding this comment

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

You missed a few:

  • drawAtlas references an image.
  • drawVertices and drawPoints has vertex buffers whose sizes can be appreciable (IIRC stuff like flare uses it extensively).
  • drawPath can have custom paths that may be large. There are path iterator APIs that you can use to walk an count the verbs and points. You should probably memoize the size here too.
  • All arguments that can take a paint may have a shader set on them. These shaders can reference images. In the same vein, shaders in dart:ui should also reference the correct size.

I understand if you don't want to audit the existing codebase and track everything in this one patch. Let's file a bug to follow up on to perform correct book-keeping on the resources referenced by dart:ui call.

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'm including drawAtlas in this patch. Filed flutter/flutter#58438 for the rest.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed flutter/flutter#58440 specifically for ImageShader, since that's a little different.

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. Thanks.

@@ -170,13 +170,16 @@ class Canvas : public RefCountedDartWrappable<Canvas> {

static void RegisterNatives(tonic::DartLibraryNatives* natives);

size_t ImageAllocationSize() { return image_allocation_size_; }
Copy link
Member

Choose a reason for hiding this comment

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

Nit on naming convention (though this is a WebKit originating TU so the style is all over the place). image_allocation_size if inline and GetImageAllocationSize if OOL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Swtiching to image_allocation_size. I think this is right?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah.

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.

Looks great! Thanks.

@dnfield dnfield added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Jun 2, 2020
@dnfield dnfield merged commit f46dde1 into flutter:master Jun 2, 2020
@dnfield dnfield deleted the image_gc branch June 2, 2020 22:12
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 3, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 5, 2020
@skquo
Copy link

skquo commented Aug 17, 2020

PictureRecorder crash 63574

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
5 participants