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

Commit f46dde1

Browse files
authored
Make images contribute to Picture allocation size, more aggressively release references when dispose is called (#18706)
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.
1 parent cc08940 commit f46dde1

File tree

8 files changed

+84
-14
lines changed

8 files changed

+84
-14
lines changed

lib/ui/painting/canvas.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ void Canvas::drawImage(const CanvasImage* image,
293293
if (!image)
294294
Dart_ThrowException(
295295
ToDart("Canvas.drawImage called with non-genuine Image."));
296+
image_allocation_size_ += image->GetAllocationSize();
296297
canvas_->drawImage(image->image(), x, y, paint.paint());
297298
}
298299

@@ -314,6 +315,7 @@ void Canvas::drawImageRect(const CanvasImage* image,
314315
ToDart("Canvas.drawImageRect called with non-genuine Image."));
315316
SkRect src = SkRect::MakeLTRB(src_left, src_top, src_right, src_bottom);
316317
SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
318+
image_allocation_size_ += image->GetAllocationSize();
317319
canvas_->drawImageRect(image->image(), src, dst, paint.paint(),
318320
SkCanvas::kFast_SrcRectConstraint);
319321
}
@@ -339,6 +341,7 @@ void Canvas::drawImageNine(const CanvasImage* image,
339341
SkIRect icenter;
340342
center.round(&icenter);
341343
SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
344+
image_allocation_size_ += image->GetAllocationSize();
342345
canvas_->drawImageNine(image->image(), icenter, dst, paint.paint());
343346
}
344347

@@ -348,6 +351,7 @@ void Canvas::drawPicture(Picture* picture) {
348351
if (!picture)
349352
Dart_ThrowException(
350353
ToDart("Canvas.drawPicture called with non-genuine Picture."));
354+
image_allocation_size_ += picture->GetAllocationSize();
351355
canvas_->drawPicture(picture->picture().get());
352356
}
353357

@@ -402,6 +406,7 @@ void Canvas::drawAtlas(const Paint& paint,
402406
static_assert(sizeof(SkRect) == sizeof(float) * 4,
403407
"SkRect doesn't use floats.");
404408

409+
image_allocation_size_ += atlas->GetAllocationSize();
405410
canvas_->drawAtlas(
406411
skImage.get(), reinterpret_cast<const SkRSXform*>(transforms.data()),
407412
reinterpret_cast<const SkRect*>(rects.data()),

lib/ui/painting/canvas.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,16 @@ class Canvas : public RefCountedDartWrappable<Canvas> {
170170

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

173+
size_t image_allocation_size() const { return image_allocation_size_; }
174+
173175
private:
174176
explicit Canvas(SkCanvas* canvas);
175177

176178
// The SkCanvas is supplied by a call to SkPictureRecorder::beginRecording,
177179
// which does not transfer ownership. For this reason, we hold a raw
178180
// pointer and manually set to null in Clear.
179181
SkCanvas* canvas_;
182+
size_t image_allocation_size_ = 0;
180183
};
181184

182185
} // namespace flutter

lib/ui/painting/image.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Dart_Handle CanvasImage::toByteData(int format, Dart_Handle callback) {
3838

3939
void CanvasImage::dispose() {
4040
ClearDartWrapper();
41+
image_.reset();
4142
}
4243

4344
size_t CanvasImage::GetAllocationSize() const {

lib/ui/painting/image_decoder.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ static SkiaGPUObject<SkImage> UploadRasterImage(
278278
SkSafeUnref(static_cast<SkImage*>(context));
279279
},
280280
image.get());
281-
result = {texture_image, nullptr};
281+
result = {std::move(texture_image), nullptr};
282282
})
283283
.SetIfFalse([&result, context = io_manager->GetResourceContext(),
284284
&pixmap, queue = io_manager->GetSkiaUnrefQueue()] {
@@ -293,7 +293,7 @@ static SkiaGPUObject<SkImage> UploadRasterImage(
293293
FML_LOG(ERROR) << "Could not make x-context image.";
294294
result = {};
295295
} else {
296-
result = {texture_image, queue};
296+
result = {std::move(texture_image), queue};
297297
}
298298
}));
299299

lib/ui/painting/picture.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Picture);
2626

2727
DART_BIND_ALL(Picture, FOR_EACH_BINDING)
2828

29-
fml::RefPtr<Picture> Picture::Create(
30-
Dart_Handle dart_handle,
31-
flutter::SkiaGPUObject<SkPicture> picture) {
32-
auto canvas_picture = fml::MakeRefCounted<Picture>(std::move(picture));
29+
fml::RefPtr<Picture> Picture::Create(Dart_Handle dart_handle,
30+
flutter::SkiaGPUObject<SkPicture> picture,
31+
size_t image_allocation_size) {
32+
auto canvas_picture =
33+
fml::MakeRefCounted<Picture>(std::move(picture), image_allocation_size);
3334

3435
canvas_picture->AssociateWithDartWrapper(dart_handle);
3536
return canvas_picture;
3637
}
3738

38-
Picture::Picture(flutter::SkiaGPUObject<SkPicture> picture)
39-
: picture_(std::move(picture)) {}
39+
Picture::Picture(flutter::SkiaGPUObject<SkPicture> picture,
40+
size_t image_allocation_size)
41+
: picture_(std::move(picture)),
42+
image_allocation_size_(image_allocation_size) {}
4043

4144
Picture::~Picture() = default;
4245

@@ -52,11 +55,13 @@ Dart_Handle Picture::toImage(uint32_t width,
5255

5356
void Picture::dispose() {
5457
ClearDartWrapper();
58+
picture_.reset();
5559
}
5660

5761
size_t Picture::GetAllocationSize() const {
5862
if (auto picture = picture_.get()) {
59-
return picture->approximateBytesUsed();
63+
return picture->approximateBytesUsed() + sizeof(Picture) +
64+
image_allocation_size_;
6065
} else {
6166
return sizeof(Picture);
6267
}

lib/ui/painting/picture.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "flutter/flow/skia_gpu_object.h"
99
#include "flutter/lib/ui/dart_wrapper.h"
1010
#include "flutter/lib/ui/painting/image.h"
11+
#include "flutter/lib/ui/ui_dart_state.h"
1112
#include "third_party/skia/include/core/SkPicture.h"
1213

1314
namespace tonic {
@@ -24,7 +25,8 @@ class Picture : public RefCountedDartWrappable<Picture> {
2425
public:
2526
~Picture() override;
2627
static fml::RefPtr<Picture> Create(Dart_Handle dart_handle,
27-
flutter::SkiaGPUObject<SkPicture> picture);
28+
flutter::SkiaGPUObject<SkPicture> picture,
29+
size_t image_allocation_size);
2830

2931
sk_sp<SkPicture> picture() const { return picture_.get(); }
3032

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

48+
size_t image_allocation_size() const { return image_allocation_size_; }
49+
4650
private:
47-
explicit Picture(flutter::SkiaGPUObject<SkPicture> picture);
51+
Picture(flutter::SkiaGPUObject<SkPicture> picture,
52+
size_t image_allocation_size_);
4853

4954
flutter::SkiaGPUObject<SkPicture> picture_;
55+
size_t image_allocation_size_;
5056
};
5157

5258
} // namespace flutter

lib/ui/painting/picture_recorder.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ fml::RefPtr<Picture> PictureRecorder::endRecording(Dart_Handle dart_picture) {
5252
if (!isRecording())
5353
return nullptr;
5454

55-
fml::RefPtr<Picture> picture = Picture::Create(
56-
dart_picture, UIDartState::CreateGPUObject(
57-
picture_recorder_.finishRecordingAsPicture()));
55+
fml::RefPtr<Picture> picture =
56+
Picture::Create(dart_picture,
57+
UIDartState::CreateGPUObject(
58+
picture_recorder_.finishRecordingAsPicture()),
59+
canvas_->image_allocation_size());
60+
5861
canvas_->Clear();
5962
canvas_->ClearDartWrapper();
6063
canvas_ = nullptr;

testing/dart/canvas_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
// @dart = 2.6
6+
import 'dart:async';
67
import 'dart:io';
78
import 'dart:typed_data';
89
import 'dart:ui';
@@ -13,6 +14,24 @@ import 'package:test/test.dart';
1314

1415
typedef CanvasCallback = void Function(Canvas canvas);
1516

17+
Future<Image> createImage(int width, int height) {
18+
final Completer<Image> completer = Completer<Image>();
19+
decodeImageFromPixels(
20+
Uint8List.fromList(List<int>.generate(
21+
width * height * 4,
22+
(int pixel) => pixel % 255,
23+
)),
24+
width,
25+
height,
26+
PixelFormat.rgba8888,
27+
(Image image) {
28+
completer.complete(image);
29+
},
30+
);
31+
32+
return completer.future;
33+
}
34+
1635
void testCanvas(CanvasCallback callback) {
1736
try {
1837
callback(Canvas(PictureRecorder(), const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)));
@@ -198,4 +217,32 @@ void main() {
198217
await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png');
199218
expect(areEqual, true);
200219
}, skip: !Platform.isLinux); // https://github.com/flutter/flutter/issues/53784
220+
221+
test('Image size reflected in picture size for image*, drawAtlas, and drawPicture methods', () async {
222+
final Image image = await createImage(100, 100);
223+
final PictureRecorder recorder = PictureRecorder();
224+
final Canvas canvas = Canvas(recorder);
225+
const Rect rect = Rect.fromLTWH(0, 0, 100, 100);
226+
canvas.drawImage(image, Offset.zero, Paint());
227+
canvas.drawImageRect(image, rect, rect, Paint());
228+
canvas.drawImageNine(image, rect, rect, Paint());
229+
canvas.drawAtlas(image, <RSTransform>[], <Rect>[], <Color>[], BlendMode.src, rect, Paint());
230+
final Picture picture = recorder.endRecording();
231+
232+
// Some of the numbers here appear to utilize sharing/reuse of common items,
233+
// e.g. of the Paint() or same `Rect` usage, etc.
234+
// The raw utilization of a 100x100 picture here should be 53333:
235+
// 100 * 100 * 4 * (4/3) = 53333.333333....
236+
// To avoid platform specific idiosyncrasies and brittleness against changes
237+
// to Skia, we just assert this is _at least_ 4x the image size.
238+
const int minimumExpected = 53333 * 4;
239+
expect(picture.approximateBytesUsed, greaterThan(minimumExpected));
240+
241+
final PictureRecorder recorder2 = PictureRecorder();
242+
final Canvas canvas2 = Canvas(recorder2);
243+
canvas2.drawPicture(picture);
244+
final Picture picture2 = recorder2.endRecording();
245+
246+
expect(picture2.approximateBytesUsed, greaterThan(minimumExpected));
247+
});
201248
}

0 commit comments

Comments
 (0)