Эх сурвалжийг харах

[flutter] Reduce allocations by using a block/bump allocator for rendering commands.

Mario Zechner 2 жил өмнө
parent
commit
28312004e3

+ 21 - 7
spine-flutter/example/lib/flame_example.dart

@@ -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();
+    }
   }
 }
 

+ 1 - 1
spine-flutter/example/lib/main.dart

@@ -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()

+ 2 - 1
spine-flutter/lib/spine_flutter.dart

@@ -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;
 }
 

+ 14 - 0
spine-flutter/lib/spine_flutter_bindings_generated.dart

@@ -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();
   }

+ 0 - 2
spine-flutter/lib/spine_widget.dart

@@ -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();
     }
   }

+ 7 - 0
spine-flutter/src/CMakeLists.txt

@@ -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 - 0
spine-flutter/src/main.cpp

@@ -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);
+    }
+}

+ 102 - 30
spine-flutter/src/spine_flutter.cpp

@@ -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;

+ 1 - 0
spine-flutter/src/spine_flutter.h

@@ -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);