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
Conversation
lib/ui/painting/canvas.h
Outdated
@@ -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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
testing/dart/canvas_test.dart
Outdated
canvas.drawImageRect(image, rect, rect, Paint()); | ||
canvas.drawImageNine(image, rect, rect, Paint()); | ||
final Picture picture = recorder.endRecording(); | ||
expect(picture.approximateBytesUsed, 161235); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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 :)
lib/ui/painting/canvas.h
Outdated
@@ -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, |
There was a problem hiding this comment.
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.
lib/ui/painting/picture.h
Outdated
@@ -43,10 +45,14 @@ class Picture : public RefCountedDartWrappable<Picture> { | |||
uint32_t height, | |||
Dart_Handle raw_image_callback); | |||
|
|||
size_t ImageAllocationSize() { return image_allocation_size_; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lib/ui/painting/canvas.cc
Outdated
@@ -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) {} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
lib/ui/painting/canvas.cc
Outdated
@@ -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) {} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lib/ui/painting/canvas.h
Outdated
@@ -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, |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
anddrawPoints
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Thanks.
lib/ui/painting/canvas.h
Outdated
@@ -170,13 +170,16 @@ class Canvas : public RefCountedDartWrappable<Canvas> { | |||
|
|||
static void RegisterNatives(tonic::DartLibraryNatives* natives); | |||
|
|||
size_t ImageAllocationSize() { return image_allocation_size_; } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks.
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
…ssively release references when dispose is called (flutter/engine#18706)
PictureRecorder crash 63574 |
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