mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-09 00:30:12 +08:00
[flutter] Reduce allocations by using a block/bump allocator for rendering commands.
This commit is contained in:
parent
40df75abf4
commit
28312004e3
@ -6,13 +6,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:esotericsoftware_spine_flutter/spine_flutter.dart';
|
||||
|
||||
class SpineComponent extends PositionComponent {
|
||||
static final Finalizer<SpineComponent> _finalizer = Finalizer((spineComponent) => spineComponent.dispose());
|
||||
final BoundsProvider _boundsProvider;
|
||||
final SkeletonDrawable _drawable;
|
||||
late final Bounds _bounds;
|
||||
final bool _ownsDrawable;
|
||||
|
||||
SpineComponent(this._drawable, {
|
||||
bool ownsDrawable = false,
|
||||
bool ownsDrawable = true,
|
||||
BoundsProvider boundsProvider = const SetupPoseBounds(),
|
||||
super.position,
|
||||
super.scale,
|
||||
@ -26,6 +27,7 @@ class SpineComponent extends PositionComponent {
|
||||
_drawable.update(0);
|
||||
_bounds = _boundsProvider.computeBounds(_drawable);
|
||||
size = Vector2(_bounds.width, _bounds.height);
|
||||
_finalizer.attach(this, this);
|
||||
}
|
||||
|
||||
static Future<SpineComponent> fromAssets(String atlasFile, String skeletonFile, {
|
||||
@ -50,9 +52,11 @@ class SpineComponent extends PositionComponent {
|
||||
priority: priority);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
if (_ownsDrawable) _drawable.dispose();
|
||||
void dispose() {
|
||||
if (_ownsDrawable) {
|
||||
_drawable.dispose();
|
||||
}
|
||||
_finalizer.detach(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -92,12 +96,18 @@ class SimpleFlameExample extends FlameGame {
|
||||
spineboy.animationState.setAnimationByName(0, "walk", true);
|
||||
await add(spineboy);
|
||||
}
|
||||
|
||||
@override
|
||||
void onDetach() {
|
||||
// Dispose the native resources that have been loaded for spineboy.
|
||||
spineboy.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class PreloadAndShareSpineDataExample extends FlameGame {
|
||||
late final SkeletonData cachedSkeletonData;
|
||||
late final Atlas cachedAtlas;
|
||||
late final List<SpineComponent> spineboys;
|
||||
late final List<SpineComponent> spineboys = [];
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
@ -109,7 +119,7 @@ class PreloadAndShareSpineDataExample extends FlameGame {
|
||||
// gets their own SkeletonDrawable copy derived from the cached data. The
|
||||
// SkeletonDrawable copies do not own the underlying skeleton data and atlas.
|
||||
final rng = Random();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
|
||||
final scale = 0.1 + rng.nextDouble() * 0.2;
|
||||
final position = Vector2(rng.nextDouble() * size.x, rng.nextDouble() * size.y);
|
||||
@ -119,15 +129,19 @@ class PreloadAndShareSpineDataExample extends FlameGame {
|
||||
position: position
|
||||
);
|
||||
spineboy.animationState.setAnimationByName(0, "walk", true);
|
||||
spineboys.add(spineboy);
|
||||
await add(spineboy);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
void onDetach() {
|
||||
// Dispose the pre-loaded atlas and skeleton data when the game/scene is removed
|
||||
cachedAtlas.dispose();
|
||||
cachedSkeletonData.dispose();
|
||||
for (final spineboy in spineboys) {
|
||||
spineboy.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -126,7 +126,7 @@ class ExampleSelector extends StatelessWidget {
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await initSpineFlutter();
|
||||
await initSpineFlutter(enableMemoryDebugging: false);
|
||||
runApp(const MaterialApp(
|
||||
title: "Spine Examples",
|
||||
home: ExampleSelector()
|
||||
|
||||
@ -18,10 +18,11 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
late SpineFlutterBindings _bindings;
|
||||
late Allocator _allocator;
|
||||
|
||||
Future<void> initSpineFlutter() async {
|
||||
Future<void> initSpineFlutter({bool enableMemoryDebugging = false}) async {
|
||||
final ffi = await initSpineFlutterFFI();
|
||||
_bindings = SpineFlutterBindings(ffi.dylib);
|
||||
_allocator = ffi.allocator;
|
||||
if (enableMemoryDebugging) _bindings.spine_enable_debug_extension(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,20 @@ class SpineFlutterBindings {
|
||||
late final _spine_minor_version =
|
||||
_spine_minor_versionPtr.asFunction<int Function()>();
|
||||
|
||||
void spine_enable_debug_extension(
|
||||
int enable,
|
||||
) {
|
||||
return _spine_enable_debug_extension(
|
||||
enable,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_enable_debug_extensionPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>(
|
||||
'spine_enable_debug_extension');
|
||||
late final _spine_enable_debug_extension =
|
||||
_spine_enable_debug_extensionPtr.asFunction<void Function(int)>();
|
||||
|
||||
void spine_report_leaks() {
|
||||
return _spine_report_leaks();
|
||||
}
|
||||
|
||||
@ -224,10 +224,8 @@ class _SpineWidgetState extends State<SpineWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget._controller._drawable != null) {
|
||||
print("Skeleton loaded, rebuilding painter");
|
||||
return _SpineRenderObjectWidget(widget._controller._drawable!, widget._controller, widget._fit, widget._alignment, widget._boundsProvider, widget._sizedByBounds);
|
||||
} else {
|
||||
print("Skeleton not loaded yet");
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +19,10 @@ set_target_properties(esotericsoftware_spine_flutter PROPERTIES
|
||||
target_include_directories(esotericsoftware_spine_flutter PUBLIC spine-cpp/include)
|
||||
target_compile_definitions(esotericsoftware_spine_flutter PUBLIC DART_SHARED_LIB)
|
||||
|
||||
if(${SPINE_FLUTTER_TESTBED})
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined")
|
||||
add_executable(spine_flutter_test main.cpp spine_flutter.cpp ${SPINE_SOURCES})
|
||||
include_directories(spine_flutter_test PUBLIC spine-cpp/include)
|
||||
endif()
|
||||
22
spine-flutter/src/main.cpp
Normal file
22
spine-flutter/src/main.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "spine/spine.h"
|
||||
#include "spine_flutter.h"
|
||||
|
||||
using namespace spine;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int atlasLength = 0;
|
||||
void* atlasData = SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/spineboy.atlas", &atlasLength);
|
||||
uint8_t* cstringAtlas = SpineExtension::calloc<uint8_t>(atlasLength + 1, __FILE__, __LINE__);
|
||||
memcpy(cstringAtlas, atlasData, atlasLength);
|
||||
int dataLength = 0;
|
||||
uint8_t* data = (uint8_t*)SpineExtension::getInstance()->_readFile("/Users/badlogic/workspaces/spine-runtimes/spine-flutter/example/assets/spineboy-pro.skel", &dataLength);
|
||||
|
||||
spine_atlas atlas = spine_atlas_load((const utf8*)cstringAtlas);
|
||||
spine_skeleton_data_result result = spine_skeleton_data_load_binary(atlas, data, dataLength);
|
||||
spine_skeleton_drawable drawable = spine_skeleton_drawable_create(spine_skeleton_data_result_get_data(result));
|
||||
spine_render_command cmd = spine_skeleton_drawable_render(drawable);
|
||||
while (cmd) {
|
||||
uint16_t *indices = spine_render_command_get_indices(cmd);
|
||||
cmd = spine_render_command_get_next(cmd);
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,75 @@
|
||||
|
||||
using namespace spine;
|
||||
|
||||
struct Block {
|
||||
int size;
|
||||
int allocated;
|
||||
uint8_t* memory;
|
||||
|
||||
int free() {
|
||||
return size - allocated;
|
||||
}
|
||||
|
||||
bool canFit(int numBytes) {
|
||||
return free() >= numBytes;
|
||||
}
|
||||
|
||||
uint8_t* allocate(int numBytes) {
|
||||
uint8_t *ptr = memory + allocated;
|
||||
memset(ptr, 0, numBytes);
|
||||
allocated += numBytes;
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
class BlockAllocator : public SpineObject{
|
||||
int initialBlockSize;
|
||||
Vector<Block> blocks;
|
||||
|
||||
public:
|
||||
BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) {
|
||||
blocks.add(newBlock(initialBlockSize));
|
||||
}
|
||||
|
||||
~BlockAllocator() {
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
Block newBlock(int numBytes) {
|
||||
Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr};
|
||||
block.memory = SpineExtension::alloc<uint8_t>(block.size, __FILE__, __LINE__);
|
||||
return block;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *allocate(size_t num) {
|
||||
return (T *) _allocate((int)(sizeof(T) * num));
|
||||
}
|
||||
|
||||
void compress() {
|
||||
int totalSize = 0;
|
||||
for (int i = 0; i < blocks.size(); i++) {
|
||||
totalSize += blocks[i].size;
|
||||
SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
|
||||
}
|
||||
blocks.clear();
|
||||
blocks.add(newBlock(totalSize));
|
||||
}
|
||||
private:
|
||||
void *_allocate(int numBytes) {
|
||||
// 16-byte align allocations
|
||||
int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0);
|
||||
Block *block = &blocks[blocks.size() - 1];
|
||||
if (!block->canFit(alignedNumBytes)) {
|
||||
blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes)));
|
||||
block = &blocks[blocks.size() - 1];
|
||||
}
|
||||
return block->allocate(alignedNumBytes);
|
||||
}
|
||||
};
|
||||
|
||||
struct AnimationStateEvent {
|
||||
EventType type;
|
||||
TrackEntry *entry;
|
||||
@ -88,6 +157,7 @@ typedef struct _spine_skeleton_drawable {
|
||||
spine_animation_state_events animationStateEvents;
|
||||
void *clipping;
|
||||
_spine_render_command *renderCommand;
|
||||
BlockAllocator *allocator;
|
||||
} _spine_skeleton_drawable;
|
||||
|
||||
typedef struct _spine_skin_entry {
|
||||
@ -103,9 +173,24 @@ typedef struct _spine_skin_entries {
|
||||
|
||||
static Color NULL_COLOR(0, 0, 0, 0);
|
||||
|
||||
static SpineExtension *defaultExtension = nullptr;
|
||||
static DebugExtension *debugExtension = nullptr;
|
||||
|
||||
static void initExtensions() {
|
||||
if (defaultExtension == nullptr) {
|
||||
defaultExtension = new DefaultSpineExtension();
|
||||
debugExtension = new DebugExtension(defaultExtension);
|
||||
}
|
||||
}
|
||||
|
||||
spine::SpineExtension *spine::getDefaultExtension() {
|
||||
// return new spine::DebugExtension(new spine::DefaultSpineExtension());
|
||||
return new spine::DefaultSpineExtension();
|
||||
initExtensions();
|
||||
return defaultExtension;
|
||||
}
|
||||
|
||||
void spine_enable_debug_extension(int enable) {
|
||||
initExtensions();
|
||||
SpineExtension::setInstance(enable ? debugExtension : defaultExtension);
|
||||
}
|
||||
|
||||
int32_t spine_major_version() {
|
||||
@ -117,7 +202,9 @@ int32_t spine_minor_version() {
|
||||
}
|
||||
|
||||
void spine_report_leaks() {
|
||||
// ((DebugExtension*)spine::SpineExtension::getInstance())->reportLeaks();
|
||||
initExtensions();
|
||||
debugExtension->reportLeaks();
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Color
|
||||
@ -515,13 +602,13 @@ void spine_skeleton_data_dispose(spine_skeleton_data data) {
|
||||
}
|
||||
|
||||
// RenderCommand
|
||||
_spine_render_command *spine_render_command_create(int numVertices, int32_t numIndices, spine_blend_mode blendMode, int32_t pageIndex) {
|
||||
_spine_render_command *cmd = SpineExtension::alloc<_spine_render_command>(1, __FILE__, __LINE__);
|
||||
cmd->positions = SpineExtension::alloc<float>(numVertices << 1, __FILE__, __LINE__);
|
||||
cmd->uvs = SpineExtension::alloc<float>(numVertices << 1, __FILE__, __LINE__);
|
||||
cmd->colors = SpineExtension::alloc<int32_t>(numVertices, __FILE__, __LINE__);
|
||||
static _spine_render_command *spine_render_command_create(BlockAllocator &allocator, int numVertices, int32_t numIndices, spine_blend_mode blendMode, int32_t pageIndex) {
|
||||
_spine_render_command *cmd = allocator.allocate<_spine_render_command>(1);
|
||||
cmd->positions = allocator.allocate<float>(numVertices << 1);
|
||||
cmd->uvs = allocator.allocate<float>(numVertices << 1);
|
||||
cmd->colors = allocator.allocate<int32_t>(numVertices);
|
||||
cmd->numVertices = numVertices;
|
||||
cmd->indices = SpineExtension::alloc<uint16_t>(numIndices, __FILE__, __LINE__);
|
||||
cmd->indices = allocator.allocate<uint16_t>(numIndices);
|
||||
cmd->numIndices = numIndices;
|
||||
cmd->blendMode = blendMode;
|
||||
cmd->atlasPage = pageIndex;
|
||||
@ -529,15 +616,6 @@ _spine_render_command *spine_render_command_create(int numVertices, int32_t numI
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void spine_render_command_dispose(_spine_render_command *cmd) {
|
||||
if (!cmd) return;
|
||||
if (cmd->positions) SpineExtension::free(cmd->positions, __FILE__, __LINE__);
|
||||
if (cmd->uvs) SpineExtension::free(cmd->uvs, __FILE__, __LINE__);
|
||||
if (cmd->colors) SpineExtension::free(cmd->colors, __FILE__, __LINE__);
|
||||
if (cmd->indices) SpineExtension::free(cmd->indices, __FILE__, __LINE__);
|
||||
SpineExtension::free(cmd, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
// SkeletonDrawable
|
||||
|
||||
spine_skeleton_drawable spine_skeleton_drawable_create(spine_skeleton_data skeletonData) {
|
||||
@ -552,6 +630,7 @@ spine_skeleton_drawable spine_skeleton_drawable_create(spine_skeleton_data skele
|
||||
drawable->animationStateEvents = (spine_animation_state_events) listener;
|
||||
state->setListener(listener);
|
||||
drawable->clipping = new (__FILE__, __LINE__) SkeletonClipping();
|
||||
drawable->allocator = new (__FILE__, __LINE__) BlockAllocator(2048);
|
||||
return (spine_skeleton_drawable) drawable;
|
||||
}
|
||||
|
||||
@ -563,11 +642,7 @@ void spine_skeleton_drawable_dispose(spine_skeleton_drawable drawable) {
|
||||
if (_drawable->animationStateData) delete (AnimationStateData *) _drawable->animationStateData;
|
||||
if (_drawable->animationStateEvents) delete (Vector<AnimationStateEvent> *) (_drawable->animationStateEvents);
|
||||
if (_drawable->clipping) delete (SkeletonClipping *) _drawable->clipping;
|
||||
while (_drawable->renderCommand) {
|
||||
_spine_render_command *cmd = _drawable->renderCommand;
|
||||
_drawable->renderCommand = cmd->next;
|
||||
spine_render_command_dispose(cmd);
|
||||
}
|
||||
if (_drawable->allocator) delete (BlockAllocator *) _drawable->allocator;
|
||||
SpineExtension::free(drawable, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
@ -576,11 +651,8 @@ spine_render_command spine_skeleton_drawable_render(spine_skeleton_drawable draw
|
||||
if (!_drawable) return nullptr;
|
||||
if (!_drawable->skeleton) return nullptr;
|
||||
|
||||
while (_drawable->renderCommand) {
|
||||
_spine_render_command *cmd = _drawable->renderCommand;
|
||||
_drawable->renderCommand = cmd->next;
|
||||
spine_render_command_dispose(cmd);
|
||||
}
|
||||
_drawable->allocator->compress();
|
||||
_drawable->renderCommand = nullptr;
|
||||
|
||||
Vector<unsigned short> quadIndices;
|
||||
quadIndices.add(0);
|
||||
@ -671,7 +743,7 @@ spine_render_command spine_skeleton_drawable_render(spine_skeleton_drawable draw
|
||||
indicesCount = (int32_t) (clipper.getClippedTriangles().size());
|
||||
}
|
||||
|
||||
_spine_render_command *cmd = spine_render_command_create(verticesCount, indicesCount, (spine_blend_mode) slot.getData().getBlendMode(), pageIndex);
|
||||
_spine_render_command *cmd = spine_render_command_create(*_drawable->allocator, verticesCount, indicesCount, (spine_blend_mode) slot.getData().getBlendMode(), pageIndex);
|
||||
|
||||
memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float));
|
||||
memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float));
|
||||
@ -1345,7 +1417,7 @@ spine_path_constraint spine_skeleton_find_path_constraint(spine_skeleton skeleto
|
||||
}
|
||||
|
||||
spine_bounds spine_skeleton_get_bounds(spine_skeleton skeleton) {
|
||||
_spine_bounds *bounds = SpineExtension::calloc<_spine_bounds>(1, __FILE__, __LINE__);
|
||||
_spine_bounds *bounds = (_spine_bounds*)malloc(sizeof(_spine_bounds));
|
||||
if (skeleton == nullptr) return (spine_bounds) bounds;
|
||||
Skeleton *_skeleton = (Skeleton *) skeleton;
|
||||
Vector<float> vertices;
|
||||
|
||||
@ -169,6 +169,7 @@ typedef enum spine_rotate_mode {
|
||||
|
||||
SPINE_FLUTTER_EXPORT int32_t spine_major_version();
|
||||
SPINE_FLUTTER_EXPORT int32_t spine_minor_version();
|
||||
SPINE_FLUTTER_EXPORT void spine_enable_debug_extension(int enable);
|
||||
SPINE_FLUTTER_EXPORT void spine_report_leaks();
|
||||
|
||||
SPINE_FLUTTER_EXPORT float spine_color_get_r(spine_color color);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user