From 923a81b5c74bff7bdc4ad4b0728d47360a1017d5 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 18 Feb 2026 12:20:32 +0100 Subject: [PATCH] [ts][pixi-v8] Implemented canvas rendering (no support for slot objects). Pixi min version 8.16. See #3023. --- spine-ts/package-lock.json | 34 ++++++++++++++------ spine-ts/spine-pixi-v8/package.json | 4 +-- spine-ts/spine-pixi-v8/src/SpinePipe.ts | 41 ++++++++++++++++++++++++- spine-ts/spine-pixi-v8/tsconfig.json | 6 ++-- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/spine-ts/package-lock.json b/spine-ts/package-lock.json index 724b6e66c..6aa6a23f0 100644 --- a/spine-ts/package-lock.json +++ b/spine-ts/package-lock.json @@ -313,7 +313,9 @@ "license": "BSD-3-Clause" }, "node_modules/@xmldom/xmldom": { - "version": "0.8.10", + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", "license": "MIT", "peer": true, "engines": { @@ -1559,20 +1561,22 @@ } }, "node_modules/pixi.js": { - "version": "8.12.0", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.16.0.tgz", + "integrity": "sha512-gu2xw3sZGAn3cWBtk0HqTQT+v19YAfiaYXwUGgWoJl5NKz4cEZJUgWrwkmdfDszGyYBAGqOvJNbd2M9+vzLLMg==", "license": "MIT", "peer": true, "dependencies": { "@pixi/colord": "^2.9.6", - "@types/css-font-loading-module": "^0.0.12", "@types/earcut": "^3.0.0", - "@webgpu/types": "^0.1.40", - "@xmldom/xmldom": "^0.8.10", + "@webgpu/types": "^0.1.69", + "@xmldom/xmldom": "^0.8.11", "earcut": "^3.0.2", "eventemitter3": "^5.0.1", "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", - "parse-svg-path": "^0.1.2" + "parse-svg-path": "^0.1.2", + "tiny-lru": "^11.4.7" }, "funding": { "type": "opencollective", @@ -1585,7 +1589,9 @@ "peer": true }, "node_modules/pixi.js/node_modules/@webgpu/types": { - "version": "0.1.60", + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", "license": "BSD-3-Clause", "peer": true }, @@ -1995,6 +2001,16 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tiny-lru": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz", + "integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -2297,10 +2313,10 @@ "version": "4.2.102", "license": "LicenseRef-LICENSE", "dependencies": { - "@esotericsoftware/spine-core": "4.2.102" + "@esotericsoftware/spine-canvas": "4.2.102" }, "peerDependencies": { - "pixi.js": "^8.12.0" + "pixi.js": "^8.16.0" } }, "spine-player": { diff --git a/spine-ts/spine-pixi-v8/package.json b/spine-ts/spine-pixi-v8/package.json index fff553992..ae6147dac 100644 --- a/spine-ts/spine-pixi-v8/package.json +++ b/spine-ts/spine-pixi-v8/package.json @@ -31,9 +31,9 @@ }, "homepage": "https://github.com/esotericsoftware/spine-runtimes#readme", "dependencies": { - "@esotericsoftware/spine-core": "4.2.102" + "@esotericsoftware/spine-canvas": "4.2.102" }, "peerDependencies": { - "pixi.js": "^8.12.0" + "pixi.js": "^8.16.0" } } diff --git a/spine-ts/spine-pixi-v8/src/SpinePipe.ts b/spine-ts/spine-pixi-v8/src/SpinePipe.ts index 2b01dab4e..658ef9fba 100644 --- a/spine-ts/spine-pixi-v8/src/SpinePipe.ts +++ b/spine-ts/spine-pixi-v8/src/SpinePipe.ts @@ -27,14 +27,16 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import { MeshAttachment, RegionAttachment } from '@esotericsoftware/spine-core'; +import { SkeletonRenderer as CanvasSkeletonRenderer, MeshAttachment, RegionAttachment } from '@esotericsoftware/spine-canvas'; import { type BLEND_MODES, + CanvasRenderer, type Container, ExtensionType, extensions, type InstructionSet, type Renderer, type RenderPipe, + RendererType, } from 'pixi.js'; import { BatchableSpineSlot } from './BatchableSpineSlot.js'; import type { Spine } from './Spine.js'; @@ -61,6 +63,7 @@ export class SpinePipe implements RenderPipe { } as const; renderer: Renderer; + private canvasSkeletonRenderer!: CanvasSkeletonRenderer; private gpuSpineData: Record = {}; private readonly _destroyRenderableBound = this.destroyRenderable.bind(this) as (renderable: Container) => void; @@ -70,6 +73,10 @@ export class SpinePipe implements RenderPipe { } validateRenderable (spine: Spine): boolean { + if (this.renderer.type === RendererType.CANVAS) { + return true; + } + spine._validateAndTransformAttachments(); // if spine attachments have changed or destroyed, we need to rebuild the batch! @@ -105,7 +112,38 @@ export class SpinePipe implements RenderPipe { return false; } + execute (spine: Spine) { + if (this.renderer.type === RendererType.CANVAS) { + const renderer = (this.renderer as CanvasRenderer); + const groupAlpha = ((spine.groupColorAlpha >>> 24) & 0xFF) / 255; + const contextSystem = renderer.canvasContext; + const context = contextSystem.activeContext; + + context.save(); + + if (!this.canvasSkeletonRenderer) { + this.canvasSkeletonRenderer = new CanvasSkeletonRenderer(context as unknown as CanvasRenderingContext2D); + this.canvasSkeletonRenderer.triangleRendering = true; + } + + contextSystem.setContextTransform(spine.groupTransform, (renderer._roundPixels | spine._roundPixels) === 1); + + const oldAlpha = spine.skeleton.color.a; + spine.skeleton.color.a *= groupAlpha; + this.canvasSkeletonRenderer.draw(spine.skeleton); + spine.skeleton.color.a = oldAlpha; + + context.restore(); + } + } + addRenderable (spine: Spine, instructionSet: InstructionSet) { + if (this.renderer.type === RendererType.CANVAS) { + this.renderer.renderPipes.batch.break(instructionSet); + instructionSet.add(spine); + return; + } + const gpuSpine = this._getSpineData(spine); const batcher = this.renderer.renderPipes.batch; @@ -192,6 +230,7 @@ export class SpinePipe implements RenderPipe { destroy () { this.gpuSpineData = null as any; this.renderer = null as any; + this.canvasSkeletonRenderer = null as any; } private _getSpineData (spine: Spine): GpuSpineDataElement { diff --git a/spine-ts/spine-pixi-v8/tsconfig.json b/spine-ts/spine-pixi-v8/tsconfig.json index 5de998882..e342bb2aa 100644 --- a/spine-ts/spine-pixi-v8/tsconfig.json +++ b/spine-ts/spine-pixi-v8/tsconfig.json @@ -5,8 +5,8 @@ "rootDir": "./src", "outDir": "./dist", "paths": { - "@esotericsoftware/spine-core": [ - "../spine-core/src" + "@esotericsoftware/spine-canvas": [ + "../spine-canvas/src" ] } }, @@ -19,7 +19,7 @@ ], "references": [ { - "path": "../spine-core" + "path": "../spine-canvas" } ] } \ No newline at end of file