[flutter] More refactoring, rendering vertices. Memory corruption.

This commit is contained in:
Mario Zechner 2022-08-23 12:40:14 +02:00
parent 656e672ee0
commit 45d9c0ede8
4 changed files with 93 additions and 47 deletions

View File

@ -7,21 +7,66 @@ void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
class SpineWidget extends StatefulWidget {
final String skeletonFile;
final String atlasFile;
const SpineWidget(this.skeletonFile, this.atlasFile, {super.key});
@override
_MyAppState createState() => _MyAppState();
State<SpineWidget> createState() => _SpineWidgetState();
}
class SpinePainter extends CustomPainter {
class _SpineWidgetState extends State<SpineWidget> {
spine_flutter.SpineSkeletonDrawable? skeletonDrawable;
@override
void initState() {
super.initState();
loadSkeleton(widget.skeletonFile, widget.atlasFile);
}
void loadSkeleton(String skeletonFile, String atlasFile) async {
final atlas = await spine_flutter.SpineAtlas.fromAsset(rootBundle, atlasFile);
final skeletonData = skeletonFile.endsWith(".json") ?
spine_flutter.SpineSkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile))
: spine_flutter.SpineSkeletonData.fromBinary(atlas, await rootBundle.load(skeletonFile));
skeletonDrawable = spine_flutter.SpineSkeletonDrawable(atlas, skeletonData);
skeletonDrawable?.update(0.016);
setState(() {});
}
@override
Widget build(BuildContext context) {
if (skeletonDrawable != null) {
print("Skeleton loaded, creating painter");
return CustomPaint(
painter: _SpinePainter(this),
child: Container()
);
} else {
print("Skeleton not loaded yet");
return Container();
}
}
}
class _SpinePainter extends CustomPainter {
final _SpineWidgetState state;
_SpinePainter(this.state);
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.teal
..strokeWidth = 5
..strokeCap = StrokeCap.round;
canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint);
print("painting");
final drawable = state.skeletonDrawable;
if (drawable == null) return;
final commands = drawable.render();
canvas.translate(size.width / 2, size.height);
for (final cmd in commands) {
canvas.drawVertices(cmd.vertices, BlendMode.srcOut, Paint()..color = Colors.white); //drawable.atlas.atlasPagePaints[cmd.atlasPageIndex]);
}
canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), Paint()..color = Colors.blue);
}
@override
@ -30,32 +75,11 @@ class SpinePainter extends CustomPainter {
}
}
class SpineWidget extends StatelessWidget {
String skeletonFile;
String atlasFile;
late spine_flutter.SpineSkeletonDrawable skeletonDrawable;
SpineWidget(this.skeletonFile, this.atlasFile) {
loadSkeleton();
}
void loadSkeleton() async {
final atlas = await spine_flutter.SpineAtlas.fromAsset(rootBundle, atlasFile);
final skeletonData = skeletonFile.endsWith(".json") ?
spine_flutter.SpineSkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile))
: spine_flutter.SpineSkeletonData.fromBinary(atlas, await rootBundle.load(skeletonFile));
skeletonDrawable = spine_flutter.SpineSkeletonDrawable(atlas, skeletonData);
skeletonDrawable.update(0.016);
print("Loaded skeleton");
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: SpinePainter(),
child: Container()
);
}
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@ -68,8 +92,8 @@ class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: SpineWidget("assets/skeleton.json", "assets/skeleton.atlas")
return const MaterialApp(
home: SpineWidget("assets/spineboy-pro.json", "assets/spineboy.atlas")
);
}
}

View File

@ -1,12 +1,11 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:ffi/ffi.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'spine_flutter_bindings_generated.dart';
import 'package:path/path.dart' as Path;
@ -16,8 +15,9 @@ int minorVersion() => _bindings.spine_minor_version();
class SpineAtlas {
Pointer<spine_atlas> _atlas;
List<Image> _atlasPages;
List<Paint> atlasPagePaints;
SpineAtlas(this._atlas, this._atlasPages);
SpineAtlas(this._atlas, this._atlasPages, this.atlasPagePaints);
static Future<SpineAtlas> fromAsset(AssetBundle assetBundle, String atlasFileName) async {
final atlasData = await assetBundle.loadString(atlasFileName);
@ -33,13 +33,19 @@ class SpineAtlas {
final atlasDir = Path.dirname(atlasFileName);
List<Image> atlasPages = [];
List<Paint> atlasPagePaints = [];
for (int i = 0; i < atlas.ref.numImagePaths; i++) {
final Pointer<Utf8> atlasPageFile = atlas.ref.imagePaths[i].cast();
final imagePath = Path.join(atlasDir, atlasPageFile.toDartString());
atlasPages.add(Image(image: AssetImage(imagePath)));
var imageData = await assetBundle.load(imagePath);
final Codec codec = await instantiateImageCodec(imageData.buffer.asUint8List());
final FrameInfo frameInfo = await codec.getNextFrame();
final Image image = frameInfo.image;
atlasPages.add(image);
atlasPagePaints.add(Paint()..shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Matrix4.identity().storage));
}
return SpineAtlas(atlas, atlasPages);
return SpineAtlas(atlas, atlasPages, atlasPagePaints);
}
}
@ -92,7 +98,8 @@ class SpineSkeletonDrawable {
Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable);
List<SpineRenderCommand> commands = [];
while(nativeCmd.address != nullptr.address) {
commands.add(SpineRenderCommand(nativeCmd));
final atlasPage = atlas._atlasPages[nativeCmd.ref.atlasPage];
commands.add(SpineRenderCommand(nativeCmd, atlasPage.width.toDouble(), atlasPage.height.toDouble()));
nativeCmd = nativeCmd.ref.next;
}
return commands;
@ -103,19 +110,28 @@ class SpineRenderCommand {
late Vertices vertices;
late int atlasPageIndex;
SpineRenderCommand(Pointer<spine_render_command> nativeCmd) {
SpineRenderCommand(Pointer<spine_render_command> nativeCmd, double pageWidth, double pageHeight) {
atlasPageIndex = nativeCmd.ref.atlasPage;
int numVertices = nativeCmd.ref.numVertices;
int numIndices = nativeCmd.ref.numIndices;
final positions = Float32List.fromList(nativeCmd.ref.positions.asTypedList(numVertices * 2));
final uvs = Float32List.fromList(nativeCmd.ref.uvs.asTypedList(numVertices * 2));
final colors = Int32List.fromList(nativeCmd.ref.colors.asTypedList(numVertices));
final indices = Uint16List.fromList(nativeCmd.ref.indices.asTypedList(numIndices));
for (int i = 0; i < numVertices * 2; i += 2) {
uvs[i] *= pageWidth;
uvs[i+1] *= pageHeight;
}
// We pass the native data as views directly to Vertices.raw. According to the sources, the data
// is copied, so it doesn't matter that we free up the underlying memory on the next
// render call. See the implementation of Vertices.raw() here:
// https://github.com/flutter/engine/blob/5c60785b802ad2c8b8899608d949342d5c624952/lib/ui/painting/vertices.cc#L21
vertices = Vertices.raw(VertexMode.triangles,
nativeCmd.ref.positions.asTypedList(numVertices * 2),
textureCoordinates: nativeCmd.ref.uvs.asTypedList(numVertices * 2),
colors: nativeCmd.ref.colors.asTypedList(numVertices),
indices: nativeCmd.ref.indices.asTypedList(numIndices));
positions,
textureCoordinates: uvs,
colors: colors,
indices: indices
);
}
}

View File

@ -37,6 +37,7 @@ FFI_PLUGIN_EXPORT void spine_atlas_dispose(spine_atlas *atlas) {
}
FFI_PLUGIN_EXPORT spine_skeleton_data *spine_skeleton_data_load_json(spine_atlas *atlas, const char *skeletonData) {
Bone::setYDown(true);
if (!atlas) return nullptr;
if (!atlas->atlas) return nullptr;
if (!skeletonData) return nullptr;
@ -51,6 +52,7 @@ FFI_PLUGIN_EXPORT spine_skeleton_data *spine_skeleton_data_load_json(spine_atlas
}
FFI_PLUGIN_EXPORT spine_skeleton_data* spine_skeleton_data_load_binary(spine_atlas *atlas, const unsigned char *skeletonData, int32_t length) {
Bone::setYDown(true);
if (!atlas) return nullptr;
if (!atlas->atlas) return nullptr;
if (!skeletonData) return nullptr;
@ -76,6 +78,7 @@ FFI_PLUGIN_EXPORT spine_skeleton_drawable *spine_skeleton_drawable_create(spine_
spine_skeleton_drawable *drawable = SpineExtension::calloc<spine_skeleton_drawable>(1, __FILE__, __LINE__);
drawable->skeleton = new Skeleton((SkeletonData*)skeletonData->skeletonData);
drawable->animationState = new AnimationState(new AnimationStateData((SkeletonData*)skeletonData->skeletonData));
drawable->clipping = new SkeletonClipping();
return drawable;
}
@ -83,6 +86,7 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *d
if (!drawable) return;
if (!drawable->skeleton) return;
if (!drawable->animationState) return;
if (!drawable->clipping) return;
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
AnimationState *animationState = (AnimationState*)drawable->animationState;
@ -132,7 +136,7 @@ FFI_PLUGIN_EXPORT spine_render_command *spine_skeleton_drawable_render(spine_ske
quadIndices.add(3);
quadIndices.add(0);
Vector<float> worldVertices;
SkeletonClipping clipper;
SkeletonClipping &clipper = *(SkeletonClipping*)drawable->clipping;
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
spine_render_command *lastCommand = nullptr;
@ -238,6 +242,7 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_dispose(spine_skeleton_drawable *
if (!drawable) return;
if (drawable->skeleton) delete (Skeleton*)drawable->skeleton;
if (drawable->animationState) delete (AnimationState*)drawable->animationState;
if (drawable->clipping) delete (SkeletonClipping*)drawable->clipping;
while (drawable->renderCommand) {
spine_render_command *cmd = drawable->renderCommand;
drawable->renderCommand = cmd->next;

View File

@ -67,6 +67,7 @@ typedef struct spine_render_command {
typedef struct spine_skeleton_drawable {
void *skeleton;
void *animationState;
void *clipping;
spine_render_command *renderCommand;
} spine_skeleton_drawable;