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 some bugs in spine webassembly #15569

Merged
merged 11 commits into from
Jun 30, 2023
20 changes: 14 additions & 6 deletions cocos/spine/assembler/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ let _useTint = false;
const _byteStrideOneColor = getAttributeStride(vfmtPosUvColor4B);
const _byteStrideTwoColor = getAttributeStride(vfmtPosUvTwoColor4B);

const DEBUG_TYPE_REGION = 0;
const DEBUG_TYPE_MESH = 1;

function _getSlotMaterial (blendMode: number, comp: Skeleton) {
let src: BlendFactor;
let dst: BlendFactor;
Expand Down Expand Up @@ -136,7 +139,7 @@ function updateComponentRenderData (comp: Skeleton, batcher: Batcher2D) {
}
const rd = comp.renderData!;
const accessor = _useTint ? _tintAccessor : _accessor;
accessor.getMeshBuffer(rd.chunk.bufferId).setDirty();
if (rd.vertexCount > 0 || rd.indexCount > 0) accessor.getMeshBuffer(rd.chunk.bufferId).setDirty();
}

function realTimeTraverse (comp: Skeleton) {
Expand All @@ -151,6 +154,8 @@ function realTimeTraverse (comp: Skeleton) {
rd.resize(vc, ic);
rd.indices = new Uint16Array(ic);
}
if (vc < 1 || ic < 1) return;

const vbuf = rd.chunk.vb;
const vUint8Buf = new Uint8Array(vbuf.buffer, vbuf.byteOffset, Float32Array.BYTES_PER_ELEMENT * vbuf.length);

Expand Down Expand Up @@ -179,8 +184,9 @@ function realTimeTraverse (comp: Skeleton) {
for (let i = 0; i < count; i++) {
const mesh = meshes.get(i);
const material = _getSlotMaterial(mesh.blendMode, comp);
const textureID = mesh.textureID;
indexCount = mesh.iCount;
comp.requestDrawData(material, indexOffset, indexCount);
comp.requestDrawData(material, textureID, indexOffset, indexCount);
indexOffset += indexCount;
}
// if enableBatch apply worldMatrix
Expand Down Expand Up @@ -211,8 +217,7 @@ function realTimeTraverse (comp: Skeleton) {
const shapeCount = debugShapes.size();
for (let i = 0; i < shapeCount; i++) {
const shape = debugShapes.get(i);
const type = shape.type;
if (type.value === 0 && comp.debugSlots) {
if (shape.type === DEBUG_TYPE_REGION && comp.debugSlots) {
graphics.strokeColor = _slotColor;
const vertexFloatOffset = shape.vOffset * floatStride;
const vertexFloatCount = shape.vCount * floatStride;
Expand All @@ -222,7 +227,7 @@ function realTimeTraverse (comp: Skeleton) {
}
graphics.close();
graphics.stroke();
} else if (type.value === 1 && comp.debugMesh) {
} else if (shape.type === DEBUG_TYPE_MESH && comp.debugMesh) {
// draw debug mesh if enabled graphics
graphics.strokeColor = _meshColor;
const iCount = shape.iCount as number;
Expand Down Expand Up @@ -277,6 +282,8 @@ function cacheTraverse (comp: Skeleton) {
rd.resize(vc, ic);
rd.indices = new Uint16Array(ic);
}
if (vc < 1 || ic < 1) return;

const vbuf = rd.chunk.vb;
const vUint8Buf = new Uint8Array(vbuf.buffer, vbuf.byteOffset, Float32Array.BYTES_PER_ELEMENT * vbuf.length);
vUint8Buf.set(model.vData);
Expand Down Expand Up @@ -321,8 +328,9 @@ function cacheTraverse (comp: Skeleton) {
for (let i = 0; i < count; i++) {
const mesh = meshes[i];
const material = _getSlotMaterial(mesh.blendMode, comp);
const textureID = mesh.textureID;
indexCount = mesh.iCount;
comp.requestDrawData(material, indexOffset, indexCount);
comp.requestDrawData(material, textureID, indexOffset, indexCount);
indexOffset += indexCount;
}

Expand Down
4 changes: 3 additions & 1 deletion cocos/spine/lib/spine-core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,7 @@ declare namespace spine {
class SkeletonInstance {
initSkeleton(data: SkeletonData);
getAnimationState();
setAnimation(trackIndex: number, name: string, loop: boolean);
setAnimation(trackIndex: number, name: string, loop: boolean): spine.TrackEntry;
zhakesi marked this conversation as resolved.
Show resolved Hide resolved
setSkin(name: string);
setPremultipliedAlpha(usePremultipliedAlpha: boolean);
setColor(r: number, g: number, b: number, a: number);
Expand All @@ -1291,6 +1291,8 @@ declare namespace spine {
setListener(id: number, type: number);
setDebugMode(debug: boolean);
getDebugShapes();
resizeSlotRegion(slotName: string, width: number, height: number, createNew: boolean);
setSlotTexture(slotName: string, index: number);
}

class wasmUtil {
Expand Down
136 changes: 122 additions & 14 deletions cocos/spine/skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { TrackEntryListeners } from './track-entry-listeners';

const spineTag = SPINE_WASM;
const CachedFrameTime = 1 / 60;
let _slotTextureID = 100;

type TrackListener = (x: spine.TrackEntry) => void;
type TrackListener2 = (x: spine.TrackEntry, ev: spine.Event) => void;
Expand Down Expand Up @@ -116,6 +117,7 @@ interface AnimationItem {
*/
export interface SkeletonDrawData {
material: Material | null;
texture: Texture2D | null;
indexOffset: number;
indexCount: number;
}
Expand Down Expand Up @@ -223,12 +225,13 @@ export class Skeleton extends UIRenderer {
public _skeleton: spine.Skeleton = null!;
protected _instance: spine.SkeletonInstance = null!;
protected _state: spine.AnimationState = null!;
protected _texture: Texture2D | null = null;
protected _textures: Texture2D[] = [];
// Animation name
protected _animationName = '';
protected _skinName = '';
protected _drawList = new RecyclePool<SkeletonDrawData>(() => ({
material: null,
texture: null,
indexOffset: 0,
indexCount: 0,
}), 1);
Expand Down Expand Up @@ -261,6 +264,8 @@ export class Skeleton extends UIRenderer {
*/
public _debugRenderer: Graphics | null = null;

private _slotTextures: Map<number, Texture2D> | null = null;

constructor () {
super();
this._useVertexOpacity = true;
Expand Down Expand Up @@ -299,6 +304,8 @@ export class Skeleton extends UIRenderer {
this._skeletonData = value as any;
this.defaultSkin = '';
this.defaultAnimation = '';
this._animationName = '';
this._skinName = '';
this._updateSkeletonData();
this._updateUITransform();
}
Expand Down Expand Up @@ -586,6 +593,8 @@ export class Skeleton extends UIRenderer {

public __preload () {
super.__preload();
this._updateSkeletonData();
this._updateDebugDraw();
}

public onRestore () {
Expand All @@ -597,7 +606,6 @@ export class Skeleton extends UIRenderer {
*/
public onEnable () {
super.onEnable();
this._updateSkeletonData();
this._flushAssembler();
SkeletonSystem.getInstance().add(this);
}
Expand Down Expand Up @@ -632,10 +640,13 @@ export class Skeleton extends UIRenderer {
protected _updateSkeletonData () {
const skeletonData = this._skeletonData;
if (!skeletonData) {
this._texture = null;
this._runtimeData = null!;
this._state = null!;
this._skeleton = null!;
this._textures = [];
return;
}
this._texture = skeletonData.textures[0];
this._textures = skeletonData.textures;

this._runtimeData = skeletonData.getRuntimeData();
if (!this._runtimeData) return;
Expand Down Expand Up @@ -693,15 +704,15 @@ export class Skeleton extends UIRenderer {
* @param name @en The name of animation. @zh 动画名称。
* @param loop @en Use loop mode or not. @zh 是否使用循环播放模式。
*/
public setAnimation (trackIndex: number, name: string, loop?: boolean) {
public setAnimation (trackIndex: number, name: string, loop?: boolean): spine.TrackEntry | null {
let trackEntry: spine.TrackEntry | null = null;
if (loop === undefined) loop = true;

if (this.isAnimationCached()) {
if (trackIndex !== 0) {
warn('Track index can not greater than 0 in cached mode.');
}
this._animationName = name;
if (!this._skeletonCache) return;
if (!this._skeletonCache) return trackEntry;
let cache = this._skeletonCache.getAnimationCache(this._skeletonData!._uuid, this._animationName);
if (!cache) {
cache = this._skeletonCache.initAnimationCache(this._skeletonData!, this._animationName);
Expand All @@ -715,9 +726,65 @@ export class Skeleton extends UIRenderer {
this._playCount = 0;
} else {
this._animationName = name;
this._instance.setAnimation(trackIndex, name, loop);
trackEntry = this._instance.setAnimation(trackIndex, name, loop);
}
this.markForUpdateRenderData();
return trackEntry;
}

/**
* @en Adds an animation to be played delay seconds after the current or last queued animation.<br>
* Returns a {{#crossLinkModule "sp.spine"}}sp.spine{{/crossLinkModule}}.TrackEntry object.
* @zh 添加一个动画到动画队列尾部,还可以延迟指定的秒数。<br>
* 返回一个 {{#crossLinkModule "sp.spine"}}sp.spine{{/crossLinkModule}}.TrackEntry 对象。
* @param trackIndex @en Index of trackEntry. @zh TrackEntry 索引。
* @param name @en The name of animation. @zh 动画名称。
* @param loop @en Set play animation in a loop. @zh 是否循环播放。
* @param delay @en Delay time of animation start. @zh 动画开始的延迟时间。
* @return {sp.spine.TrackEntry}
*/
public addAnimation (trackIndex: number, name: string, loop: boolean, delay?: number) {
delay = delay || 0;
if (this.isAnimationCached()) {
warn(`Cached mode not support addAnimation.`);
return null;
} else if (this._skeleton) {
const animation = this._skeleton.data.findAnimation(name);
if (!animation) {
error(`Not find animation named ${name}`);
return null;
}
return this._state?.addAnimationWith(trackIndex, animation, loop, delay);
}
return null;
}
/**
* @en Find animation with specified name.
* @zh 查找指定名称的动画
* @param name @en The name of animation. @zh 动画名称。
* @returns {sp.spine.Animation}
*/
public findAnimation (name: string) {
if (this._skeleton) {
return this._skeleton.data.findAnimation(name);
}
return null;
}
/**
* @en Returns track entry by trackIndex.<br>
* Returns a {{#crossLinkModule "sp.spine"}}sp.spine{{/crossLinkModule}}.TrackEntry object.
* @zh 通过 track 索引获取 TrackEntry。<br>
* 返回一个 {{#crossLinkModule "sp.spine"}}sp.spine{{/crossLinkModule}}.TrackEntry 对象。
* @param trackIndex @en The index of trackEntry. @zh TrackEntry 索引。
* @return {sp.spine.TrackEntry}
*/
public getCurrent (trackIndex: number) {
if (this.isAnimationCached()) {
warn('\'getCurrent\' interface can not be invoked in cached mode.');
} else if (this._state) {
return this._state.getCurrent(trackIndex);
}
return null;
}

/**
Expand Down Expand Up @@ -792,7 +859,7 @@ export class Skeleton extends UIRenderer {

protected _render (batcher: Batcher2D) {
let indicesCount = 0;
if (this.renderData && this._drawList) {
if (this.renderData && this._drawList.length > 0) {
const rd = this.renderData;
const chunk = rd.chunk;
const accessor = chunk.vertexAccessor;
Expand All @@ -801,9 +868,9 @@ export class Skeleton extends UIRenderer {
// Fill index buffer
for (let i = 0; i < this._drawList.length; i++) {
const dc = this._drawList.data[i];
if (this._texture) {
if (dc.texture) {
batcher.commitMiddleware(this, meshBuffer, origin + dc.indexOffset,
dc.indexCount, this._texture, dc.material!, this._enableBatch);
dc.indexCount, dc.texture, dc.material!, this._enableBatch);
}
indicesCount += dc.indexCount;
}
Expand All @@ -815,9 +882,16 @@ export class Skeleton extends UIRenderer {
/**
* @engineInternal
*/
public requestDrawData (material: Material, indexOffset: number, indexCount: number) {
public requestDrawData (material: Material, texureID: number, indexOffset: number, indexCount: number) {
const draw = this._drawList.add();
draw.material = material;
if (texureID === 0) {
draw.texture = this._textures[0];
zhakesi marked this conversation as resolved.
Show resolved Hide resolved
} else {
const texture = this._slotTextures?.get(texureID);
if (texture) draw.texture = texture;
else draw.texture = this._textures[0];
}
draw.indexOffset = indexOffset;
draw.indexCount = indexCount;
return draw;
Expand Down Expand Up @@ -1243,10 +1317,9 @@ export class Skeleton extends UIRenderer {
this._debugRenderer = debugDraw;
debugDrawNode.parent = this.node;
}

if (this.isAnimationCached()) {
warn('Debug bones or slots is invalid in cached mode');
} else {
} else if (!JSB) {
this._instance.setDebugMode(true);
}
} else if (this._debugRenderer) {
Expand Down Expand Up @@ -1453,6 +1526,41 @@ export class Skeleton extends UIRenderer {
public getDebugShapes (): any {
return this._instance.getDebugShapes();
}

/**
* @en Set texture for slot, this function can be use to changing local skin.
* @zh 为 slot 设置贴图纹理,可使用该该方法实现局部换装功能。
* @param slotName @en The name of slot. @zh Slot 名字。
* @param tex2d @en The texture will show on the slot. @zh 在该 Slot 上显示的 2D 纹理。
* @param createNew @en Whether to create new Attachment. If value is false, all sp.Skeleton share the
* same attachment will be changed. @zh 是否需要创建新的 attachment,如果值为 false, 所有共享相同 attachment
* 的组件都将受影响。
*/
public setSlotTexture (slotName: string, tex2d: Texture2D, createNew?: boolean) {
if (this.isAnimationCached()) {
error(`Cached mode can't change texture of slot`);
return;
}
const slot = this.findSlot(slotName);
if (!slot) {
error(`No slot named:${slotName}`);
return;
}
const width = tex2d.width;
const height = tex2d.height;
const createNewAttachment = createNew || false;
this._instance.resizeSlotRegion(slotName, width, height, createNewAttachment);
if (!this._slotTextures) this._slotTextures = new Map<number, Texture2D>();
let textureID = 0;
this._slotTextures.forEach((value, key) => {
if (value === tex2d) textureID = key;
});
if (textureID === 0) {
textureID = _slotTextureID++;
this._slotTextures.set(textureID, tex2d);
}
this._instance.setSlotTexture(slotName, textureID);
}
}

legacyCC.internal.SpineSkeleton = Skeleton;
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ AttachmentVertices::~AttachmentVertices() {
if (_texture) _texture->release();
}

AttachmentVertices *AttachmentVertices::copy() {
AttachmentVertices *atv = new AttachmentVertices(nullptr, _triangles->vertCount, _triangles->indices, _triangles->indexCount);
return atv;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Where to delete the memeory?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

统一解释下这里的内存问题:
AttachmentVertices 是多个组件共享的。因此在接口上层提供了createNew的选项,用于避免在多个共享数据的组件时,更改一个组件对其他组件产生影响。在createNew 为false时,在原本的数据上做修改即可,不用创建新数据。
数据会在组件销毁时释放。

这里是把释放需要新建数据的选择交给了用户。

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok.


} // namespace spine
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class AttachmentVertices {
public:
AttachmentVertices(cc::middleware::Texture2D *texture, int verticesCount, uint16_t *triangles, int trianglesCount);
virtual ~AttachmentVertices();
AttachmentVertices *copy();

cc::middleware::Texture2D *_texture = nullptr;
cc::middleware::Triangles *_triangles = nullptr;
Expand Down
Loading