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

fix: performance improvements on SpriteBatch APIs #1637

Merged
merged 32 commits into from
Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3b3f457
test without picturerecorder
nathanaelneveux May 12, 2022
170f032
build atlas with bytes
nathanaelneveux May 15, 2022
7ca33d6
working version
nathanaelneveux May 16, 2022
9f0932e
remove bitmap dependency
nathanaelneveux May 19, 2022
80206a6
add asyncronous caching & retrieval
nathanaelneveux May 20, 2022
6e3da16
add generateAtlas variations
nathanaelneveux May 20, 2022
9c582bc
added flag for _generateAtlas
nathanaelneveux May 20, 2022
35e08d8
vetically aligned images in both for easier testing
nathanaelneveux May 20, 2022
725e8ae
Update packages/flame/lib/src/sprite_batch.dart
nathanaelneveux May 21, 2022
aefd775
Merge branch 'main' into generateAtlas-Performance
nathanaelneveux May 21, 2022
fcce7d2
Merge branch 'main' into generateAtlas-Performance
nathanaelneveux May 22, 2022
c8933f2
attempt to lazy initialize flipped atlas
nathanaelneveux May 26, 2022
55f123e
simplify and late initialzation working
nathanaelneveux May 28, 2022
1fb0ea8
update constructors
nathanaelneveux Jun 1, 2022
cc30913
Fix analyze issues and comments
spydon Jun 1, 2022
5a2dc28
Merge branch 'main' into generateAtlas-Performance
spydon Jun 1, 2022
5587943
Organize order
spydon Jun 1, 2022
423be08
ind* -> i
spydon Jun 1, 2022
2731000
useAtlas tests
nathanaelneveux Jun 2, 2022
2f52d09
fix for larger atlases
nathanaelneveux Jun 2, 2022
e21e7cf
Merge branch 'main' into generateAtlas-Performance
spydon Jun 2, 2022
2af8e16
Remove print
spydon Jun 2, 2022
006824c
Merge branch 'main' into generateAtlas-Performance
spydon Jun 2, 2022
37b2dd5
more robust flipping and testing
nathanaelneveux Jun 3, 2022
ad0b1b3
correct flips for drawImageRect
nathanaelneveux Jun 3, 2022
c410689
Fix analyze issues
spydon Jun 3, 2022
a904de5
Merge branch 'main' into generateAtlas-Performance
spydon Jun 3, 2022
d7b0940
Update packages/flame/lib/src/sprite_batch.dart
spydon Jun 3, 2022
4528470
Merge branch 'main' into generateAtlas-Performance
spydon Jun 3, 2022
4b8c71c
Remove _defaultScale
spydon Jun 3, 2022
9207e77
Fix comments
spydon Jun 4, 2022
89fdca1
Merge branch 'main' into generateAtlas-Performance
spydon Jun 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/flame/lib/src/cache/images.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ class Images {
_assets[name] = _ImageAsset.fromImage(image);
}

/// Returns the image [name] from the cache. If no image with [name] exists
/// uses [imageGenerator] to generate and then add the resulting image into
/// the cache.
/// The cache will assume the ownership of the resulting image, and will
/// properly dispose of it at the end.
spydon marked this conversation as resolved.
Show resolved Hide resolved
Future<Image> fetchOrGenerate(
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 meant to fetch and if it fails to do so then, it builds an image and also caches it?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, is that not clear from the Dartdocs? 😅

String name,
Future<Image> Function() imageGenerator,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it named "Generator" instead of "Builder"?

Copy link
Member

Choose a reason for hiding this comment

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

I didn't name it, but it's not a builder (if the builder name is used according to the builder pattern).

) {
return (_assets[name] ??= _ImageAsset.future(imageGenerator()))
.retrieveAsync();
}

/// Removes the image [name] from the cache.
///
/// No error is raised if the image [name] is not present in the cache.
Expand Down Expand Up @@ -126,6 +139,12 @@ class Images {
/// Whether the cache contains the specified [key] or not.
bool containsKey(String key) => _assets.containsKey(key);

String? findKeyForImage(Image image) {
return _assets.keys.firstWhere(
(k) => _assets[k]?.image?.isCloneOf(image) ?? false,
);
}

/// Waits until all currently pending image loading operations complete.
Future<void> ready() {
return Future.wait(_assets.values.map((asset) => asset.retrieveAsync()));
Expand Down
92 changes: 70 additions & 22 deletions packages/flame/lib/src/sprite_batch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,30 @@ class SpriteBatch {
}

/// The atlas used by the [SpriteBatch].
final Image atlas;
Image atlas;

/// The image cache used by the [SpriteBatch] to store image assets.
final Images? _imageCache;

/// When the [_imageCache] isn't specified, the global [Flame.images] is used.
Images get imageCache => _imageCache ?? Flame.images;

/// The root key use by the [SpriteBatch] to store image assets.
final String? _imageKey;

/// When the [_imageKey] isn't specified [imageKey] will return either the key
/// for the [atlas] stored in [imageCache] or a key generated from the
/// identityHashCode.
String get imageKey =>
_imageKey ??
imageCache.findKeyForImage(atlas) ??
'image[${identityHashCode(atlas)}]';

/// Whether any [BatchItem]s need a flippableAtlas.
spydon marked this conversation as resolved.
Show resolved Hide resolved
bool _hasFlips = false;

/// The status of the atlas image loading operations.
bool _atlasReady = true;

/// The default color, used as a background color for a [BatchItem].
final Color defaultColor;
Expand Down Expand Up @@ -159,7 +182,10 @@ class SpriteBatch {
this.defaultBlendMode = BlendMode.srcOver,
this.defaultTransform,
this.useAtlas = true,
});
Images? imageCache,
String? imageKey,
}) : _imageCache = imageCache,
_imageKey = imageKey;

/// Takes a path of an image, and optional arguments for the SpriteBatch.
///
Expand All @@ -172,28 +198,45 @@ class SpriteBatch {
Images? images,
bool useAtlas = true,
}) async {
final _images = images ?? Flame.images;
return SpriteBatch(
await _generateAtlas(images, path),
await _images.load(path),
defaultColor: defaultColor,
defaultTransform: defaultTransform ?? RSTransform(1, 0, 0, 0),
defaultBlendMode: defaultBlendMode,
useAtlas: useAtlas,
imageCache: _images,
imageKey: path,
);
}

static Future<Image> _generateAtlas(Images? images, String path) async {
final _images = images ?? Flame.images;
final image = await _images.load(path);
Future<void> _makeFlippedAtlas() async {
_hasFlips = true;
_atlasReady = false;
final key = '$imageKey#with-flips';
atlas = await imageCache.fetchOrGenerate(
key,
() => _generateFlippedAtlas(atlas),
);
_atlasReady = true;
}

Future<Image> _generateFlippedAtlas(
Image image,
) {
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
final _emptyPaint = Paint();
canvas.drawImage(image, Offset.zero, _emptyPaint);
canvas.scale(-1, 1);
canvas.drawImage(image, Offset(-image.width * 2, 0), _emptyPaint);
canvas.drawImage(
image,
Offset(-image.width.toDouble(), image.height.toDouble()),
_emptyPaint,
);

final picture = recorder.endRecording();
final atlas = picture.toImageSafe(image.width * 2, image.height);
return atlas;
return picture.toImageSafe(image.width, image.height * 2);
}

/// Add a new batch item using a RSTransform.
Expand Down Expand Up @@ -224,13 +267,18 @@ class SpriteBatch {
color: color ?? defaultColor,
);

if (flip && useAtlas && !_hasFlips) {
_hasFlips = true;
_makeFlippedAtlas();
}

_batchItems.add(batchItem);

_sources.add(
flip
? Rect.fromLTWH(
atlas.width - source.left - source.width,
source.top,
atlas.width - source.right,
source.top + source.height,
source.width,
source.height,
)
Expand Down Expand Up @@ -315,7 +363,17 @@ class SpriteBatch {
}) {
paint ??= _emptyPaint;

if (!useAtlas) {
if (useAtlas && _atlasReady) {
canvas.drawAtlas(
atlas,
_transforms,
_sources,
_colors,
blendMode ?? defaultBlendMode,
cullRect,
paint,
);
} else {
for (final batchItem in _batchItems) {
paint.blendMode = blendMode ?? paint.blendMode;

Expand All @@ -331,16 +389,6 @@ class SpriteBatch {
)
..restore();
}
} else {
canvas.drawAtlas(
atlas,
_transforms,
_sources,
_colors,
blendMode ?? defaultBlendMode,
cullRect,
paint,
);
}
}
}