mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[flutter] More refactoring, rendering vertices. Memory corruption.
This commit is contained in:
parent
656e672ee0
commit
45d9c0ede8
@ -7,21 +7,66 @@ void main() {
|
|||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class SpineWidget extends StatefulWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
final String skeletonFile;
|
||||||
|
final String atlasFile;
|
||||||
|
|
||||||
|
const SpineWidget(this.skeletonFile, this.atlasFile, {super.key});
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
var paint = Paint()
|
print("painting");
|
||||||
..color = Colors.teal
|
final drawable = state.skeletonDrawable;
|
||||||
..strokeWidth = 5
|
if (drawable == null) return;
|
||||||
..strokeCap = StrokeCap.round;
|
final commands = drawable.render();
|
||||||
canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint);
|
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
|
@override
|
||||||
@ -30,32 +75,11 @@ class SpinePainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpineWidget extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
String skeletonFile;
|
const MyApp({Key? key}) : super(key: key);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
_MyAppState createState() => _MyAppState();
|
||||||
return CustomPaint(
|
|
||||||
painter: SpinePainter(),
|
|
||||||
child: Container()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class _MyAppState extends State<MyApp> {
|
||||||
@ -68,8 +92,8 @@ class _MyAppState extends State<MyApp> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const textStyle = TextStyle(fontSize: 25);
|
const textStyle = TextStyle(fontSize: 25);
|
||||||
const spacerSmall = SizedBox(height: 10);
|
const spacerSmall = SizedBox(height: 10);
|
||||||
return MaterialApp(
|
return const MaterialApp(
|
||||||
home: SpineWidget("assets/skeleton.json", "assets/skeleton.atlas")
|
home: SpineWidget("assets/spineboy-pro.json", "assets/spineboy.atlas")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'spine_flutter_bindings_generated.dart';
|
import 'spine_flutter_bindings_generated.dart';
|
||||||
import 'package:path/path.dart' as Path;
|
import 'package:path/path.dart' as Path;
|
||||||
|
|
||||||
@ -16,8 +15,9 @@ int minorVersion() => _bindings.spine_minor_version();
|
|||||||
class SpineAtlas {
|
class SpineAtlas {
|
||||||
Pointer<spine_atlas> _atlas;
|
Pointer<spine_atlas> _atlas;
|
||||||
List<Image> _atlasPages;
|
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 {
|
static Future<SpineAtlas> fromAsset(AssetBundle assetBundle, String atlasFileName) async {
|
||||||
final atlasData = await assetBundle.loadString(atlasFileName);
|
final atlasData = await assetBundle.loadString(atlasFileName);
|
||||||
@ -33,13 +33,19 @@ class SpineAtlas {
|
|||||||
|
|
||||||
final atlasDir = Path.dirname(atlasFileName);
|
final atlasDir = Path.dirname(atlasFileName);
|
||||||
List<Image> atlasPages = [];
|
List<Image> atlasPages = [];
|
||||||
|
List<Paint> atlasPagePaints = [];
|
||||||
for (int i = 0; i < atlas.ref.numImagePaths; i++) {
|
for (int i = 0; i < atlas.ref.numImagePaths; i++) {
|
||||||
final Pointer<Utf8> atlasPageFile = atlas.ref.imagePaths[i].cast();
|
final Pointer<Utf8> atlasPageFile = atlas.ref.imagePaths[i].cast();
|
||||||
final imagePath = Path.join(atlasDir, atlasPageFile.toDartString());
|
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);
|
Pointer<spine_render_command> nativeCmd = _bindings.spine_skeleton_drawable_render(_drawable);
|
||||||
List<SpineRenderCommand> commands = [];
|
List<SpineRenderCommand> commands = [];
|
||||||
while(nativeCmd.address != nullptr.address) {
|
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;
|
nativeCmd = nativeCmd.ref.next;
|
||||||
}
|
}
|
||||||
return commands;
|
return commands;
|
||||||
@ -103,19 +110,28 @@ class SpineRenderCommand {
|
|||||||
late Vertices vertices;
|
late Vertices vertices;
|
||||||
late int atlasPageIndex;
|
late int atlasPageIndex;
|
||||||
|
|
||||||
SpineRenderCommand(Pointer<spine_render_command> nativeCmd) {
|
SpineRenderCommand(Pointer<spine_render_command> nativeCmd, double pageWidth, double pageHeight) {
|
||||||
atlasPageIndex = nativeCmd.ref.atlasPage;
|
atlasPageIndex = nativeCmd.ref.atlasPage;
|
||||||
int numVertices = nativeCmd.ref.numVertices;
|
int numVertices = nativeCmd.ref.numVertices;
|
||||||
int numIndices = nativeCmd.ref.numIndices;
|
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
|
// 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
|
// 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:
|
// render call. See the implementation of Vertices.raw() here:
|
||||||
// https://github.com/flutter/engine/blob/5c60785b802ad2c8b8899608d949342d5c624952/lib/ui/painting/vertices.cc#L21
|
// https://github.com/flutter/engine/blob/5c60785b802ad2c8b8899608d949342d5c624952/lib/ui/painting/vertices.cc#L21
|
||||||
vertices = Vertices.raw(VertexMode.triangles,
|
vertices = Vertices.raw(VertexMode.triangles,
|
||||||
nativeCmd.ref.positions.asTypedList(numVertices * 2),
|
positions,
|
||||||
textureCoordinates: nativeCmd.ref.uvs.asTypedList(numVertices * 2),
|
textureCoordinates: uvs,
|
||||||
colors: nativeCmd.ref.colors.asTypedList(numVertices),
|
colors: colors,
|
||||||
indices: nativeCmd.ref.indices.asTypedList(numIndices));
|
indices: indices
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
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) return nullptr;
|
||||||
if (!atlas->atlas) return nullptr;
|
if (!atlas->atlas) return nullptr;
|
||||||
if (!skeletonData) 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) {
|
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) return nullptr;
|
||||||
if (!atlas->atlas) return nullptr;
|
if (!atlas->atlas) return nullptr;
|
||||||
if (!skeletonData) 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__);
|
spine_skeleton_drawable *drawable = SpineExtension::calloc<spine_skeleton_drawable>(1, __FILE__, __LINE__);
|
||||||
drawable->skeleton = new Skeleton((SkeletonData*)skeletonData->skeletonData);
|
drawable->skeleton = new Skeleton((SkeletonData*)skeletonData->skeletonData);
|
||||||
drawable->animationState = new AnimationState(new AnimationStateData((SkeletonData*)skeletonData->skeletonData));
|
drawable->animationState = new AnimationState(new AnimationStateData((SkeletonData*)skeletonData->skeletonData));
|
||||||
|
drawable->clipping = new SkeletonClipping();
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +86,7 @@ FFI_PLUGIN_EXPORT void spine_skeleton_drawable_update(spine_skeleton_drawable *d
|
|||||||
if (!drawable) return;
|
if (!drawable) return;
|
||||||
if (!drawable->skeleton) return;
|
if (!drawable->skeleton) return;
|
||||||
if (!drawable->animationState) return;
|
if (!drawable->animationState) return;
|
||||||
|
if (!drawable->clipping) return;
|
||||||
|
|
||||||
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
|
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
|
||||||
AnimationState *animationState = (AnimationState*)drawable->animationState;
|
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(3);
|
||||||
quadIndices.add(0);
|
quadIndices.add(0);
|
||||||
Vector<float> worldVertices;
|
Vector<float> worldVertices;
|
||||||
SkeletonClipping clipper;
|
SkeletonClipping &clipper = *(SkeletonClipping*)drawable->clipping;
|
||||||
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
|
Skeleton *skeleton = (Skeleton*)drawable->skeleton;
|
||||||
spine_render_command *lastCommand = nullptr;
|
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) return;
|
||||||
if (drawable->skeleton) delete (Skeleton*)drawable->skeleton;
|
if (drawable->skeleton) delete (Skeleton*)drawable->skeleton;
|
||||||
if (drawable->animationState) delete (AnimationState*)drawable->animationState;
|
if (drawable->animationState) delete (AnimationState*)drawable->animationState;
|
||||||
|
if (drawable->clipping) delete (SkeletonClipping*)drawable->clipping;
|
||||||
while (drawable->renderCommand) {
|
while (drawable->renderCommand) {
|
||||||
spine_render_command *cmd = drawable->renderCommand;
|
spine_render_command *cmd = drawable->renderCommand;
|
||||||
drawable->renderCommand = cmd->next;
|
drawable->renderCommand = cmd->next;
|
||||||
|
|||||||
@ -67,6 +67,7 @@ typedef struct spine_render_command {
|
|||||||
typedef struct spine_skeleton_drawable {
|
typedef struct spine_skeleton_drawable {
|
||||||
void *skeleton;
|
void *skeleton;
|
||||||
void *animationState;
|
void *animationState;
|
||||||
|
void *clipping;
|
||||||
spine_render_command *renderCommand;
|
spine_render_command *renderCommand;
|
||||||
} spine_skeleton_drawable;
|
} spine_skeleton_drawable;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user