From 632fb227b6f0210d502fe220d5a019be8fce1c0b Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Fri, 12 Dec 2025 10:58:13 +0100 Subject: [PATCH] [ts] Add support across all runtimes for premultiplying non-PMA textures on upload. --- CHANGELOG.md | 15 +++ spine-ts/spine-core/src/AssetManagerBase.ts | 49 +++++---- spine-ts/spine-core/src/SkeletonClipping.ts | 68 +++++++------ .../spine-core/src/SkeletonRendererCore.ts | 99 +++++++++++++------ .../example/inline-loading.html | 2 +- .../spine-phaser-v3/src/SpineGameObject.ts | 3 - spine-ts/spine-phaser-v3/src/SpinePlugin.ts | 51 ++++------ spine-ts/spine-phaser-v3/src/index.ts | 2 +- .../example/inline-loading.html | 2 +- .../spine-phaser-v4/src/SpineGameObject.ts | 3 - spine-ts/spine-phaser-v4/src/SpinePlugin.ts | 52 ++++------ spine-ts/spine-phaser-v4/src/index.ts | 2 +- spine-ts/spine-pixi-v7/src/Spine.ts | 1 - spine-ts/spine-player/example/dispose.html | 1 - spine-ts/spine-player/example/editor.html | 2 +- .../example/embedding-binary-example.html | 1 - .../example/embedding-json-example.html | 1 - spine-ts/spine-player/example/example.html | 2 - spine-ts/spine-player/src/Player.ts | 8 +- spine-ts/spine-threejs/example/physics.html | 5 +- spine-ts/spine-threejs/example/shadows.html | 4 +- spine-ts/spine-threejs/src/AssetManager.ts | 10 +- .../src/SpineWebComponentOverlay.ts | 4 +- .../src/SpineWebComponentSkeleton.ts | 9 -- .../spine-webgl/demos/additiveblending.js | 2 +- spine-ts/spine-webgl/demos/clipping.js | 4 +- spine-ts/spine-webgl/demos/hoverboard.js | 4 +- spine-ts/spine-webgl/demos/imagechanges.js | 2 +- spine-ts/spine-webgl/demos/meshes.js | 2 +- spine-ts/spine-webgl/demos/skins.js | 2 +- spine-ts/spine-webgl/demos/spritesheets.js | 4 +- spine-ts/spine-webgl/demos/stretchyman.js | 4 +- spine-ts/spine-webgl/demos/tank.js | 4 +- spine-ts/spine-webgl/demos/transforms.js | 4 +- spine-ts/spine-webgl/demos/transitions.js | 4 +- spine-ts/spine-webgl/demos/vine.js | 2 +- .../spine-webgl/example/barebones-dragon.html | 2 +- spine-ts/spine-webgl/example/barebones.html | 2 +- .../spine-webgl/example/bone-dragging.html | 2 +- .../example/custom-attachment.html | 2 +- .../spine-webgl/example/drag-and-drop.html | 2 - spine-ts/spine-webgl/example/drag-and-drop.js | 9 +- spine-ts/spine-webgl/example/dress-up.html | 4 +- spine-ts/spine-webgl/example/index.html | 41 ++++---- .../spine-webgl/example/mix-and-match.html | 4 +- spine-ts/spine-webgl/example/physics.html | 4 +- spine-ts/spine-webgl/example/physics2.html | 2 +- spine-ts/spine-webgl/example/physics3.html | 4 +- spine-ts/spine-webgl/example/physics4.html | 2 +- spine-ts/spine-webgl/src/AssetManager.ts | 8 +- spine-ts/spine-webgl/src/GLTexture.ts | 12 ++- spine-ts/spine-webgl/src/LoadingScreen.ts | 12 +-- spine-ts/spine-webgl/src/PolygonBatcher.ts | 17 ++-- spine-ts/spine-webgl/src/SceneRenderer.ts | 6 +- .../spine-webgl/src/SkeletonDebugRenderer.ts | 4 +- spine-ts/spine-webgl/src/SkeletonRenderer.ts | 34 +++---- 56 files changed, 296 insertions(+), 311 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5b10cee..0639860cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -758,6 +758,8 @@ - Added `Skeleton` properties `windX`, `windY`, `gravityX`, `gravityY` to allow rotating physics force directions - Added `SequenceTimeline` for sequence animation - Added `allowMissingRegions` parameter to `AtlasAttachmentLoader` constructor to support skeletons exported with per-skin atlases + - Added `TextureLoader` type with optional `pma?: boolean` parameter to `AssetManagerBase`. `AssetManagerBase` now tracks and passes PMA metadata from atlas pages to texture loaders, allowing runtimes to automatically premultiply textures on upload + - Added `SkeletonRendererCore` class to reduce complexity of runtime-specific render code - **Breaking changes** - `Bone` now extends `PosedActive` with separate pose, constrained, and applied states @@ -869,6 +871,12 @@ - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime + - `GLTexture` constructor now requires `pma: boolean` parameter (automatically read from atlas page metadata) + - Removed `GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL` static property + - `SkeletonRenderer` and `SkeletonDebugRenderer` no longer have `premultipliedAlpha` property - PMA is handled automatically + - `SceneRenderer.drawSkeleton()` and `drawSkeletonDebug()` no longer take `premultipliedAlpha` parameter + - `PolygonBatcher.setBlendMode()` no longer takes `premultipliedAlpha` parameter + - `LoadingScreen` no longer accepts PMA parameters ### Canvas backend @@ -885,11 +893,13 @@ - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime + - `AssetManager` constructor no longer takes `pma` parameter - PMA is handled automatically ### Player - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime + - Removed `premultipliedAlpha` option from `SpinePlayerConfig` - PMA is now handled automatically ### Pixi v7 @@ -917,17 +927,22 @@ - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime + - `SpinePlugin.spineAtlas()` loader no longer takes `premultipliedAlpha` parameter - PMA is handled automatically + - `SpinePlugin.createSkeleton()` no longer takes `premultipliedAlpha` parameter ### Phaser v4 - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime + - `SpinePlugin.spineAtlas()` loader no longer takes `premultipliedAlpha` parameter - PMA is handled automatically + - `SpinePlugin.createSkeleton()` no longer takes `premultipliedAlpha` parameter ### Web Components - **Breaking changes** - Updated to use new TypeScript/JavaScript runtime - Updated skeleton and overlay component implementations + - Removed `pma` property from `SpineWebComponentSkeleton` - PMA is handled automatically # 4.2 diff --git a/spine-ts/spine-core/src/AssetManagerBase.ts b/spine-ts/spine-core/src/AssetManagerBase.ts index ad95c0316..117148adb 100644 --- a/spine-ts/spine-core/src/AssetManagerBase.ts +++ b/spine-ts/spine-core/src/AssetManagerBase.ts @@ -35,20 +35,19 @@ type AssetData = (Uint8Array | string | Texture | TextureAtlas | object) & Parti type AssetCallback = (path: string, data: T) => void; type ErrorCallback = (path: string, message: string) => void; +export type TextureLoader = (image: HTMLImageElement | ImageBitmap, pma?: boolean) => Texture; + export class AssetManagerBase implements Disposable { - private pathPrefix: string = ""; - private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture; - private downloader: Downloader; - private cache: AssetCache; private errors: StringMap = {}; private toLoad = 0; private loaded = 0; + private texturePmaInfo: Record = {}; - constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader = new Downloader(), cache = new AssetCache()) { - this.textureLoader = textureLoader; - this.pathPrefix = pathPrefix; - this.downloader = downloader; - this.cache = cache; + constructor ( + private textureLoader: TextureLoader, + private pathPrefix: string = "", + private downloader = new Downloader(), + private cache = new AssetCache()) { } private start (path: string): string { @@ -175,6 +174,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; + const pma = this.texturePmaInfo[path]; this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { const isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document); const isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined'; @@ -188,7 +188,7 @@ export class AssetManagerBase implements Disposable { return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null; }).then((bitmap) => { if (bitmap) { - const texture = this.createTexture(path, bitmap); + const texture = this.createTexture(path, pma, bitmap); this.success(success, path, texture); resolve(texture); }; @@ -197,7 +197,7 @@ export class AssetManagerBase implements Disposable { const image = new Image(); image.crossOrigin = "anonymous"; image.onload = () => { - const texture = this.createTexture(path, image); + const texture = this.createTexture(path, pma, image); this.success(success, path, texture); resolve(texture); }; @@ -216,7 +216,7 @@ export class AssetManagerBase implements Disposable { path: string, success: AssetCallback = () => { }, error: ErrorCallback = () => { }, - fileAlias?: { [keyword: string]: string } + fileAlias?: Record ) { const index = path.lastIndexOf("/"); const parent = index >= 0 ? path.substring(0, index + 1) : ""; @@ -227,10 +227,11 @@ export class AssetManagerBase implements Disposable { this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadText(path, (atlasText: string): void => { try { - const atlas = this.createTextureAtlas(path, atlasText); + const atlas = this.createTextureAtlas(atlasText, parent, path, fileAlias); let toLoad = atlas.pages.length, abort = false; for (const page of atlas.pages) { - this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name], + this.loadTexture( + this.texturePath(parent, page.name, fileAlias), (imagePath: string, texture: Texture) => { if (!abort) { page.setTexture(texture); @@ -268,6 +269,8 @@ export class AssetManagerBase implements Disposable { success: AssetCallback = () => { }, error: ErrorCallback = () => { }, ) { + const index = path.lastIndexOf("/"); + const parent = index >= 0 ? path.substring(0, index + 1) : ""; path = this.start(path); if (this.reuseAssets(path, success, error)) return; @@ -275,7 +278,7 @@ export class AssetManagerBase implements Disposable { this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadText(path, (atlasText: string): void => { try { - const atlas = this.createTextureAtlas(path, atlasText); + const atlas = this.createTextureAtlas(atlasText, parent, path); this.success(success, path, atlas); resolve(atlas); } catch (e) { @@ -291,7 +294,6 @@ export class AssetManagerBase implements Disposable { }); } - // Promisified versions of load function async loadBinaryAsync (path: string) { return new Promise((resolve, reject) => { this.loadBinary(path, @@ -413,7 +415,7 @@ export class AssetManagerBase implements Disposable { } } - private createTextureAtlas (path: string, atlasText: string): TextureAtlas { + private createTextureAtlas (atlasText: string, parentPath: string, path: string, fileAlias?: Record): TextureAtlas { const atlas = new TextureAtlas(atlasText); atlas.dispose = () => { if (this.cache.assetsRefCount[path] <= 0) return; @@ -422,17 +424,26 @@ export class AssetManagerBase implements Disposable { page.texture?.dispose(); } } + for (const page of atlas.pages) { + const texturePath = this.texturePath(parentPath, page.name, fileAlias); + this.texturePmaInfo[this.pathPrefix + texturePath] = page.pma; + } return atlas; } - private createTexture (path: string, image: HTMLImageElement | ImageBitmap): Texture { - const texture = this.textureLoader(image); + private createTexture (path: string, pma: boolean, image: HTMLImageElement | ImageBitmap): Texture { + const texture = this.textureLoader(image, pma); const textureDispose = texture.dispose.bind(texture); texture.dispose = () => { if (this.disposeAssetInternal(path)) textureDispose(); } return texture; } + + private texturePath (parentPath: string, pageName: string, fileAlias?: Record) { + if (!fileAlias) return parentPath + pageName; + return fileAlias[pageName]; + } } export class AssetCache { diff --git a/spine-ts/spine-core/src/SkeletonClipping.ts b/spine-ts/spine-core/src/SkeletonClipping.ts index 91b6dde71..271cc6668 100644 --- a/spine-ts/spine-core/src/SkeletonClipping.ts +++ b/spine-ts/spine-core/src/SkeletonClipping.ts @@ -324,7 +324,7 @@ export class SkeletonClipping { return clipOutputItems != null; } - public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) { + public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike, stride = 2) { const clipOutput = this.clipOutput; let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped; // biome-ignore lint/style/noNonNullAssertion: clipStart define it @@ -343,17 +343,23 @@ export class SkeletonClipping { let clipped = false; for (let i = 0; i < trianglesLength; i += 3) { - let v = triangles[i] << 1; + let t = triangles[i]; + let v = t * stride; const x1 = vertices[v], y1 = vertices[v + 1]; - const u1 = uvs[v], v1 = uvs[v + 1]; + let uv = t << 1; + const u1 = uvs[uv], v1 = uvs[uv + 1]; - v = triangles[i + 1] << 1; + t = triangles[i + 1]; + v = t * stride; const x2 = vertices[v], y2 = vertices[v + 1]; - const u2 = uvs[v], v2 = uvs[v + 1]; + uv = t << 1; + const u2 = uvs[uv], v2 = uvs[uv + 1]; - v = triangles[i + 2] << 1; + t = triangles[i + 2]; + v = t * stride; const x3 = vertices[v], y3 = vertices[v + 1]; - const u3 = uvs[v], v3 = uvs[v + 1]; + uv = t << 1; + const u3 = uvs[uv], v3 = uvs[uv + 1]; for (let p = 0; p < polygonsCount; p++) { let s = this.clippedVerticesLength; @@ -367,20 +373,22 @@ export class SkeletonClipping { let clipOutputCount = clipOutputLength >> 1; const clipOutputItems = this.clipOutput; - const newLength = s + clipOutputCount * 2; + const newLength = s + clipOutputCount * stride; if (clippedVertices.length < newLength) { this._clippedVerticesTyped = new Float32Array(newLength * 2); this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); - this._clippedUVsTyped = new Float32Array(newLength * 2); - this._clippedUVsTyped.set(clippedUVs.subarray(0, s)); + this._clippedUVsTyped = new Float32Array((this.clippedUVsLength + clipOutputCount * 2) * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); clippedVertices = this._clippedVerticesTyped; clippedUVs = this._clippedUVsTyped; } const clippedVerticesItems = clippedVertices; const clippedUVsItems = clippedUVs; this.clippedVerticesLength = newLength; - this.clippedUVsLength = newLength; - for (let ii = 0; ii < clipOutputLength; ii += 2, s += 2) { + + let uvIndex = this.clippedUVsLength; + this.clippedUVsLength = uvIndex + clipOutputCount * 2; + for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride, uvIndex += 2) { const x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; clippedVerticesItems[s] = x; clippedVerticesItems[s + 1] = y; @@ -388,8 +396,8 @@ export class SkeletonClipping { const a = (d0 * c0 + d1 * c1) * d; const b = (d4 * c0 + d2 * c1) * d; const c = 1 - a - b; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + clippedUVsItems[uvIndex] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[uvIndex + 1] = v1 * a + v2 * b + v3 * c; } s = this.clippedTrianglesLength; @@ -411,7 +419,7 @@ export class SkeletonClipping { } else { - let newLength = s + 3 * 2; + let newLength = s + 3 * stride; if (clippedVertices.length < newLength) { this._clippedVerticesTyped = new Float32Array(newLength * 2); this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); @@ -419,25 +427,27 @@ export class SkeletonClipping { } clippedVertices[s] = x1; clippedVertices[s + 1] = y1; - clippedVertices[s + 2] = x2; - clippedVertices[s + 3] = y2; - clippedVertices[s + 4] = x3; - clippedVertices[s + 5] = y3; + clippedVertices[s + stride] = x2; + clippedVertices[s + stride + 1] = y2; + clippedVertices[s + stride * 2] = x3; + clippedVertices[s + stride * 2 + 1] = y3; - if (clippedUVs.length < newLength) { - this._clippedUVsTyped = new Float32Array(newLength * 2); - this._clippedUVsTyped.set(clippedUVs.subarray(0, s)); + let uvLength = this.clippedUVsLength + 3 * 2; + if (clippedUVs.length < uvLength) { + this._clippedUVsTyped = new Float32Array(uvLength * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); clippedUVs = this._clippedUVsTyped; } - clippedUVs[s] = u1; - clippedUVs[s + 1] = v1; - clippedUVs[s + 2] = u2; - clippedUVs[s + 3] = v2; - clippedUVs[s + 4] = u3; - clippedUVs[s + 5] = v3; + let uvIndex = this.clippedUVsLength; + clippedUVs[uvIndex] = u1; + clippedUVs[uvIndex + 1] = v1; + clippedUVs[uvIndex + 2] = u2; + clippedUVs[uvIndex + 3] = v2; + clippedUVs[uvIndex + 4] = u3; + clippedUVs[uvIndex + 5] = v3; this.clippedVerticesLength = newLength; - this.clippedUVsLength = newLength; + this.clippedUVsLength = uvLength; s = this.clippedTrianglesLength; newLength = s + 3; diff --git a/spine-ts/spine-core/src/SkeletonRendererCore.ts b/spine-ts/spine-core/src/SkeletonRendererCore.ts index bbf3b727b..b4fdc426c 100644 --- a/spine-ts/spine-core/src/SkeletonRendererCore.ts +++ b/spine-ts/spine-core/src/SkeletonRendererCore.ts @@ -40,7 +40,7 @@ export class SkeletonRendererCore { private clipping = new SkeletonClipping(); private renderCommands: RenderCommand[] = []; - render (skeleton: Skeleton): RenderCommand | undefined { + render (skeleton: Skeleton, pma = false, inColor?: [number, number, number, number], stride = 2): RenderCommand | undefined { this.commandPool.reset(); this.renderCommands.length = 0; @@ -56,8 +56,8 @@ export class SkeletonRendererCore { } const slotApplied = slot.applied; - const color = slotApplied.color; - const alpha = color.a; + const slotColor = slotApplied.color; + const alpha = slotColor.a; if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) { clipper.clipEnd(slot); continue; @@ -80,7 +80,7 @@ export class SkeletonRendererCore { continue; } - attachment.computeWorldVertices(slot, this.worldVertices, 0, 2); + attachment.computeWorldVertices(slot, this.worldVertices, 0, stride); vertices = this.worldVertices; verticesCount = 4; uvs = attachment.uvs as Float32Array; @@ -99,7 +99,7 @@ export class SkeletonRendererCore { if (this.worldVertices.length < attachment.worldVerticesLength) this.worldVertices = new Float32Array(attachment.worldVerticesLength); - attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, 2); + attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride); vertices = this.worldVertices; verticesCount = attachment.worldVerticesLength >> 1; uvs = attachment.uvs as Float32Array; @@ -115,38 +115,71 @@ export class SkeletonRendererCore { } const skelColor = skeleton.color; - const r = Math.floor(skelColor.r * slotApplied.color.r * attachmentColor.r * 255); - const g = Math.floor(skelColor.g * slotApplied.color.g * attachmentColor.g * 255); - const b = Math.floor(skelColor.b * slotApplied.color.b * attachmentColor.b * 255); - const a = Math.floor(skelColor.a * slotApplied.color.a * attachmentColor.a * 255); + let color: number, darkColor: number; + if (pma) { + let a: number; + if (inColor) { + a = Math.floor(inColor[3] * skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(a * inColor[0] * skelColor.r * slotColor.r * attachmentColor.r); + const g = Math.floor(a * inColor[1] * skelColor.g * slotColor.g * attachmentColor.g); + const b = Math.floor(a * inColor[2] * skelColor.b * slotColor.b * attachmentColor.b); + color = (a << 24) | (r << 16) | (g << 8) | b; + } else { + a = Math.floor(skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(a * skelColor.r * slotColor.r * attachmentColor.r); + const g = Math.floor(a * skelColor.g * slotColor.g * attachmentColor.g); + const b = Math.floor(a * skelColor.b * slotColor.b * attachmentColor.b); + color = (a << 24) | (r << 16) | (g << 8) | b; + } - let darkColor = 0xff000000; - if (slotApplied.darkColor) { - const { r, g, b } = slotApplied.darkColor; - darkColor = 0xff000000 | - (Math.floor(r * 255) << 16) | - (Math.floor(g * 255) << 8) | - Math.floor(b * 255); + darkColor = 0xff000000; + if (slotApplied.darkColor) { + const { r, g, b } = slotApplied.darkColor; + darkColor = 0xff000000 | + (Math.floor(r * a) << 16) | + (Math.floor(g * a) << 8) | + Math.floor(b * a); + } + } else { + if (inColor) { + const a = Math.floor(inColor[3] * skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(inColor[0] * skelColor.r * slotColor.r * attachmentColor.r * 255); + const g = Math.floor(inColor[1] * skelColor.g * slotColor.g * attachmentColor.g * 255); + const b = Math.floor(inColor[2] * skelColor.b * slotColor.b * attachmentColor.b * 255); + color = (a << 24) | (r << 16) | (g << 8) | b; + } else { + const a = Math.floor(skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(skelColor.r * slotColor.r * attachmentColor.r * 255); + const g = Math.floor(skelColor.g * slotColor.g * attachmentColor.g * 255); + const b = Math.floor(skelColor.b * slotColor.b * attachmentColor.b * 255); + color = (a << 24) | (r << 16) | (g << 8) | b; + } + + darkColor = 0; + if (slotApplied.darkColor) { + const { r, g, b } = slotApplied.darkColor; + darkColor = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255); + } } if (clipper.isClipping()) { - clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs); + clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs, stride); vertices = clipper.clippedVerticesTyped; - verticesCount = clipper.clippedVerticesLength >> 1; + verticesCount = clipper.clippedVerticesLength / stride; uvs = clipper.clippedUVsTyped; indices = clipper.clippedTrianglesTyped; indicesCount = clipper.clippedTrianglesLength; } - const cmd = this.commandPool.getCommand(verticesCount, indicesCount); + const cmd = this.commandPool.getCommand(verticesCount, indicesCount, stride); cmd.blendMode = slot.data.blendMode; cmd.texture = texture; - cmd.positions.set(vertices.subarray(0, verticesCount << 1)); + cmd.positions.set(vertices.subarray(0, verticesCount * stride)); cmd.uvs.set(uvs.subarray(0, verticesCount << 1)); for (let j = 0; j < verticesCount; j++) { - cmd.colors[j] = (a << 24) | (r << 16) | (g << 8) | b; + cmd.colors[j] = color; cmd.darkColors[j] = darkColor; } @@ -161,14 +194,14 @@ export class SkeletonRendererCore { } clipper.clipEnd(); - return this.batchCommands(); + return this.batchCommands(stride); } private batchSubCommands (commands: RenderCommand[], first: number, last: number, - numVertices: number, numIndices: number): RenderCommand { + numVertices: number, numIndices: number, stride: number): RenderCommand { const firstCmd = commands[first]; - const batched = this.commandPool.getCommand(numVertices, numIndices); + const batched = this.commandPool.getCommand(numVertices, numIndices, stride); batched.blendMode = firstCmd.blendMode; batched.texture = firstCmd.texture; @@ -183,7 +216,7 @@ export class SkeletonRendererCore { const cmd = commands[i]; batched.positions.set(cmd.positions, positionsOffset); - positionsOffset += cmd.numVertices << 1; + positionsOffset += cmd.numVertices * stride; batched.uvs.set(cmd.uvs, uvsOffset); uvsOffset += cmd.numVertices << 1; @@ -203,7 +236,7 @@ export class SkeletonRendererCore { return batched; } - private batchCommands (): RenderCommand | undefined { + private batchCommands (stride: number): RenderCommand | undefined { if (this.renderCommands.length === 0) return undefined; let root: RenderCommand | undefined; @@ -234,7 +267,7 @@ export class SkeletonRendererCore { numIndices += cmd.numIndices; } else { const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1, - numVertices, numIndices); + numVertices, numIndices, stride); if (!last) { root = last = batched; @@ -257,6 +290,8 @@ export class SkeletonRendererCore { } } +// values with under score is the original sized array, bigger than necessary +// values without under score is a view of the orignal array, sized as needed interface RenderCommand { positions: Float32Array; uvs: Float32Array; @@ -280,17 +315,17 @@ class CommandPool { private pool: RenderCommand[] = []; private inUse: RenderCommand[] = []; - getCommand (numVertices: number, numIndices: number): RenderCommand { + getCommand (numVertices: number, numIndices: number, stride: number): RenderCommand { let cmd: RenderCommand | undefined; for (const c of this.pool) { - if (c._positions.length >= numVertices << 1 && c._indices.length >= numIndices) { + if (c._positions.length >= numVertices * stride && c._indices.length >= numIndices) { cmd = c; break; } } if (!cmd) { - const _positions = new Float32Array(numVertices << 1); + const _positions = new Float32Array(numVertices * stride); const _uvs = new Float32Array(numVertices << 1); const _colors = new Uint32Array(numVertices); const _darkColors = new Uint32Array(numVertices); @@ -317,8 +352,8 @@ class CommandPool { cmd.numVertices = numVertices; cmd.numIndices = numIndices; - cmd.positions = cmd._positions.subarray(0, numVertices << 1); - cmd.uvs = cmd._uvs.subarray(0, numVertices * 2); + cmd.positions = cmd._positions.subarray(0, numVertices * stride); + cmd.uvs = cmd._uvs.subarray(0, numVertices << 1); cmd.colors = cmd._colors.subarray(0, numVertices); cmd.darkColors = cmd._darkColors.subarray(0, numVertices); cmd.indices = cmd._indices.subarray(0, numIndices); diff --git a/spine-ts/spine-phaser-v3/example/inline-loading.html b/spine-ts/spine-phaser-v3/example/inline-loading.html index 1e3c7fa17..c57e440ab 100644 --- a/spine-ts/spine-phaser-v3/example/inline-loading.html +++ b/spine-ts/spine-phaser-v3/example/inline-loading.html @@ -93,7 +93,7 @@ class BasicExample extends Phaser.Scene { async preload() { // manually add the text atlas to the game cache - this.game.cache.text.add(this.atlasKey, { data: atlasString, premultipliedAlpha: true }); + this.game.cache.text.add(this.atlasKey, atlasString); // manually add the json skeleton to the game cache this.game.cache.json.add("spineboy-data", skeletonJson); diff --git a/spine-ts/spine-phaser-v3/src/SpineGameObject.ts b/spine-ts/spine-phaser-v3/src/SpineGameObject.ts index 4f9114494..99df76868 100644 --- a/spine-ts/spine-phaser-v3/src/SpineGameObject.ts +++ b/spine-ts/spine-phaser-v3/src/SpineGameObject.ts @@ -226,7 +226,6 @@ export class SpineGameObject extends DepthMixin( animationState: AnimationState; beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { }; afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { }; - private premultipliedAlpha = false; private offsetX = 0; private offsetY = 0; @@ -243,7 +242,6 @@ export class SpineGameObject extends DepthMixin( super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE); this.setPosition(x, y); - this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey); this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey); this.animationStateData = new AnimationStateData(this.skeleton.data); this.animationState = new AnimationState(this.animationStateData); @@ -374,7 +372,6 @@ export class SpineGameObject extends DepthMixin( sceneRenderer.drawSkeleton( src.skeleton, - src.premultipliedAlpha, -1, -1, (vertices, numVertices, stride) => { diff --git a/spine-ts/spine-phaser-v3/src/SpinePlugin.ts b/spine-ts/spine-phaser-v3/src/SpinePlugin.ts index 73595f692..143498cf3 100644 --- a/spine-ts/spine-phaser-v3/src/SpinePlugin.ts +++ b/spine-ts/spine-phaser-v3/src/SpinePlugin.ts @@ -58,7 +58,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje * The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions: * * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`. * * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`. - * * `spineAtlas(key: string, url: string, premultipliedAlpha: boolean = true, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images. + * * `spineAtlas(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images. * * The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions: * * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`: @@ -70,7 +70,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje * The plugin has additional public methods to work with Spine Runtime core API objects: * * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key. * * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key. - * * `createSkeleton(skeletonDataKey: string, atlasKey: string, premultipliedAlpha: boolean = true)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key. + * * `createSkeleton(skeletonDataKey: string, atlasKey: string)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key. * * `isPremultipliedAlpha(atlasKey: string)`: returns `true` if the atlas with the given key has premultiplied alpha. */ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { @@ -119,9 +119,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string, url: string, - premultipliedAlpha: boolean, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) { - const file = new SpineAtlasFile(this, key, url, premultipliedAlpha, xhrSettings); + const file = new SpineAtlasFile(this, key, url, xhrSettings); this.addFile(file.files); return this; }; @@ -225,35 +224,26 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { /** Returns the TextureAtlas instance for the given key */ getAtlas (atlasKey: string) { - let atlas: TextureAtlas; - if (this.atlasCache.exists(atlasKey)) { - atlas = this.atlasCache.get(atlasKey); + if (this.atlasCache.exists(atlasKey)) return this.atlasCache.get(atlasKey); + + const atlas = new TextureAtlas(this.game.cache.text.get(atlasKey)); + if (this.isWebGL && this.gl) { + const gl = this.gl; + for (const atlasPage of atlas.pages) + atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, atlasPage.pma, false)); } else { - const atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean }; - atlas = new TextureAtlas(atlasFile.data); - if (this.isWebGL && this.gl) { - const gl = this.gl; - const phaserUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL); - if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); - for (const atlasPage of atlas.pages) { - atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, false)); - } - if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); - } else { - for (const atlasPage of atlas.pages) { - atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap)); - } - } - this.atlasCache.add(atlasKey, atlas); + for (const atlasPage of atlas.pages) + atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap)); } + this.atlasCache.add(atlasKey, atlas); return atlas; } /** Returns whether the TextureAtlas uses premultiplied alpha */ isAtlasPremultiplied (atlasKey: string) { - const atlasFile = this.game.cache.text.get(atlasKey); - if (!atlasFile) return false; - return atlasFile.premultipliedAlpha; + const atlas: TextureAtlas = this.atlasCache.get(atlasKey); + if (!atlas || atlas.pages.length === 0) return false; + return atlas.pages[0].pma; } /** Returns the SkeletonData instance for the given data and atlas key */ @@ -337,17 +327,15 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile { interface SpineAtlasFileConfig { key: string; url: string; - premultipliedAlpha?: boolean; xhrSettings?: Phaser.Types.Loader.XHRSettingsObject; } class SpineAtlasFile extends Phaser.Loader.MultiFile { - constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, public premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) { + constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) { if (typeof key !== "string") { const config = key; key = config.key; url = config.url; - premultipliedAlpha = config.premultipliedAlpha; xhrSettings = config.xhrSettings; } @@ -406,11 +394,6 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile { textureManager.addImage(file.key, file.data); } } else { - this.premultipliedAlpha = this.premultipliedAlpha ?? (file.data.indexOf("pma: true") >= 0 || file.data.indexOf("pma:true") >= 0); - file.data = { - data: file.data, - premultipliedAlpha: this.premultipliedAlpha, - }; file.addToCache(); } } diff --git a/spine-ts/spine-phaser-v3/src/index.ts b/spine-ts/spine-phaser-v3/src/index.ts index 3ad594624..0db576eb0 100644 --- a/spine-ts/spine-phaser-v3/src/index.ts +++ b/spine-ts/spine-phaser-v3/src/index.ts @@ -48,7 +48,7 @@ declare global { export interface LoaderPlugin { spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; - spineAtlas (key: string, url: string, premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; + spineAtlas (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; } } diff --git a/spine-ts/spine-phaser-v4/example/inline-loading.html b/spine-ts/spine-phaser-v4/example/inline-loading.html index a9c1bca49..43e5a3790 100644 --- a/spine-ts/spine-phaser-v4/example/inline-loading.html +++ b/spine-ts/spine-phaser-v4/example/inline-loading.html @@ -93,7 +93,7 @@ class BasicExample extends Phaser.Scene { async preload() { // manually add the text atlas to the game cache - this.game.cache.text.add(this.atlasKey, { data: atlasString, premultipliedAlpha: true }); + this.game.cache.text.add(this.atlasKey, atlasString); // manually add the json skeleton to the game cache this.game.cache.json.add("spineboy-data", skeletonJson); diff --git a/spine-ts/spine-phaser-v4/src/SpineGameObject.ts b/spine-ts/spine-phaser-v4/src/SpineGameObject.ts index c5235042d..169788d4a 100644 --- a/spine-ts/spine-phaser-v4/src/SpineGameObject.ts +++ b/spine-ts/spine-phaser-v4/src/SpineGameObject.ts @@ -226,7 +226,6 @@ export class SpineGameObject extends DepthMixin( animationState: AnimationState; beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { }; afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { }; - private premultipliedAlpha = false; private offsetX = 0; private offsetY = 0; @@ -243,7 +242,6 @@ export class SpineGameObject extends DepthMixin( super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE); this.setPosition(x, y); - this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey); this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey); this.animationStateData = new AnimationStateData(this.skeleton.data); this.animationState = new AnimationState(this.animationStateData); @@ -393,7 +391,6 @@ export class SpineGameObject extends DepthMixin( sceneRenderer.drawSkeleton( src.skeleton, - src.premultipliedAlpha, -1, -1, (vertices, numVertices, stride) => { diff --git a/spine-ts/spine-phaser-v4/src/SpinePlugin.ts b/spine-ts/spine-phaser-v4/src/SpinePlugin.ts index c1aab557f..fb3ce9a9e 100644 --- a/spine-ts/spine-phaser-v4/src/SpinePlugin.ts +++ b/spine-ts/spine-phaser-v4/src/SpinePlugin.ts @@ -58,7 +58,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje * The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions: * * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`. * * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`. - * * `spineAtlas(key: string, url: string, premultipliedAlpha: boolean = true, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images. + * * `spineAtlas(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images. * * The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions: * * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`: @@ -70,8 +70,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje * The plugin has additional public methods to work with Spine Runtime core API objects: * * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key. * * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key. - * * `createSkeleton(skeletonDataKey: string, atlasKey: string, premultipliedAlpha: boolean = true)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key. - * * `isPremultipliedAlpha(atlasKey: string)`: returns `true` if the atlas with the given key has premultiplied alpha. + * * `createSkeleton(atlasKey: string)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key. */ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { game: Phaser.Game; @@ -116,9 +115,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string, url: string, - premultipliedAlpha: boolean, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) { - const file = new SpineAtlasFile(this, key, url, premultipliedAlpha, xhrSettings); + const file = new SpineAtlasFile(this, key, url, xhrSettings); this.addFile(file.files); return this; }; @@ -204,35 +202,26 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { /** Returns the TextureAtlas instance for the given key */ getAtlas (atlasKey: string) { - let atlas: TextureAtlas; - if (this.atlasCache.exists(atlasKey)) { - atlas = this.atlasCache.get(atlasKey); + if (this.atlasCache.exists(atlasKey)) return this.atlasCache.get(atlasKey); + + const atlas = new TextureAtlas(this.game.cache.text.get(atlasKey)); + if (this.isWebGL && this.gl) { + const gl = this.gl; + for (const atlasPage of atlas.pages) + atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, atlasPage.pma, false)); } else { - const atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean }; - atlas = new TextureAtlas(atlasFile.data); - if (this.isWebGL && this.gl) { - const gl = this.gl; - const phaserUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL); - if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); - for (const atlasPage of atlas.pages) { - atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, false)); - } - if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); - } else { - for (const atlasPage of atlas.pages) { - atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap)); - } - } - this.atlasCache.add(atlasKey, atlas); + for (const atlasPage of atlas.pages) + atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap)); } + this.atlasCache.add(atlasKey, atlas); return atlas; } /** Returns whether the TextureAtlas uses premultiplied alpha */ isAtlasPremultiplied (atlasKey: string) { - const atlasFile = this.game.cache.text.get(atlasKey); - if (!atlasFile) return false; - return atlasFile.premultipliedAlpha; + const atlas: TextureAtlas = this.atlasCache.get(atlasKey); + if (!atlas || atlas.pages.length === 0) return false; + return atlas.pages[0].pma; } /** Returns the SkeletonData instance for the given data and atlas key */ @@ -326,17 +315,15 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile { interface SpineAtlasFileConfig { key: string; url: string; - premultipliedAlpha?: boolean; xhrSettings?: Phaser.Types.Loader.XHRSettingsObject; } class SpineAtlasFile extends Phaser.Loader.MultiFile { - constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, public premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) { + constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) { if (typeof key !== "string") { const config = key; key = config.key; url = config.url; - premultipliedAlpha = config.premultipliedAlpha; xhrSettings = config.xhrSettings; } @@ -395,11 +382,6 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile { textureManager.addImage(file.key, file.data); } } else { - this.premultipliedAlpha = this.premultipliedAlpha ?? (file.data.indexOf("pma: true") >= 0 || file.data.indexOf("pma:true") >= 0); - file.data = { - data: file.data, - premultipliedAlpha: this.premultipliedAlpha, - }; file.addToCache(); } } diff --git a/spine-ts/spine-phaser-v4/src/index.ts b/spine-ts/spine-phaser-v4/src/index.ts index 3ad594624..0db576eb0 100644 --- a/spine-ts/spine-phaser-v4/src/index.ts +++ b/spine-ts/spine-phaser-v4/src/index.ts @@ -48,7 +48,7 @@ declare global { export interface LoaderPlugin { spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; - spineAtlas (key: string, url: string, premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; + spineAtlas (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin; } } diff --git a/spine-ts/spine-pixi-v7/src/Spine.ts b/spine-ts/spine-pixi-v7/src/Spine.ts index eded67945..9a3cb4ec2 100644 --- a/spine-ts/spine-pixi-v7/src/Spine.ts +++ b/spine-ts/spine-pixi-v7/src/Spine.ts @@ -671,7 +671,6 @@ export class Spine extends Container { const skeletonColor = skeleton.color; const slotColor = pose.color; const alpha = skeletonColor.a * slotColor.a * attachmentColor.a; - // cannot premultiply the colors because the default mesh renderer already does that this.lightColor.set( skeletonColor.r * slotColor.r * attachmentColor.r, skeletonColor.g * slotColor.g * attachmentColor.g, diff --git a/spine-ts/spine-player/example/dispose.html b/spine-ts/spine-player/example/dispose.html index 67c4d8168..397c90866 100644 --- a/spine-ts/spine-player/example/dispose.html +++ b/spine-ts/spine-player/example/dispose.html @@ -27,7 +27,6 @@ skeleton: "/assets/spineboy-pro.skel", atlas: "/assets/spineboy-pma.atlas", animation: "run", - premultipliedAlpha: true, backgroundColor: "#cccccc", viewport: { debugRender: true, diff --git a/spine-ts/spine-player/example/editor.html b/spine-ts/spine-player/example/editor.html index 670975f76..12d273ce9 100644 --- a/spine-ts/spine-player/example/editor.html +++ b/spine-ts/spine-player/example/editor.html @@ -15,7 +15,7 @@ diff --git a/spine-ts/spine-webgl/example/drag-and-drop.js b/spine-ts/spine-webgl/example/drag-and-drop.js index 393b12a03..4935d5e40 100644 --- a/spine-ts/spine-webgl/example/drag-and-drop.js +++ b/spine-ts/spine-webgl/example/drag-and-drop.js @@ -3,7 +3,6 @@ class App { this.skeleton = null; this.animationState = null; this.canvas = null; - this.pma = true; } loadAssets(canvas) { @@ -24,12 +23,6 @@ class App { this.animationState.setAnimation(0, animationSelectBox.value, true); } - // Setup listener for the PMA checkbox - let pmaCheckbox = document.body.querySelector("#pma"); - pmaCheckbox.onchange = () => { - this.pma = pmaCheckbox.checked; - } - // Setup the drag and drop listener new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files)) @@ -163,7 +156,7 @@ class App { renderer.begin(); renderer.line(-10000, 0, 10000, 0, spine.Color.RED); renderer.line(0, -10000, 0, 10000, spine.Color.GREEN); - renderer.drawSkeleton(this.skeleton, this.pma); + renderer.drawSkeleton(this.skeleton); renderer.end(); } } diff --git a/spine-ts/spine-webgl/example/dress-up.html b/spine-ts/spine-webgl/example/dress-up.html index 31c881cb0..469ea2e30 100644 --- a/spine-ts/spine-webgl/example/dress-up.html +++ b/spine-ts/spine-webgl/example/dress-up.html @@ -173,7 +173,7 @@ // Clear the canvas and render the skeleton canvas.clear(0.5, 0.5, 0.5, 1); renderer.begin(); - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); renderer.end(); // Get the image data and convert it to an img element @@ -236,7 +236,7 @@ canvas.clear(0.2, 0.2, 0.2, 1); renderer.begin(); - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); renderer.end(); } } diff --git a/spine-ts/spine-webgl/example/index.html b/spine-ts/spine-webgl/example/index.html index bdf0ec38f..36c40bb17 100644 --- a/spine-ts/spine-webgl/example/index.html +++ b/spine-ts/spine-webgl/example/index.html @@ -117,36 +117,36 @@ if (assetManager.isLoadingComplete()) { skeletons = { coin: { - Binary: loadSkeleton("coin-pro.skel", "animation", true), - JSON: loadSkeleton("coin-pro.json", "animation", true) + Binary: loadSkeleton("coin-pro.skel", "animation"), + JSON: loadSkeleton("coin-pro.json", "animation") }, goblins: { - Binary: loadSkeleton("goblins-pro.skel", "walk", true, "goblin"), - JSON: loadSkeleton("goblins-pro.json", "walk", true, "goblin") + Binary: loadSkeleton("goblins-pro.skel", "walk", "goblin"), + JSON: loadSkeleton("goblins-pro.json", "walk", "goblin") }, "mix-and-match-pro": { - Binary: loadSkeleton("mix-and-match-pro.skel", "dance", true, "full-skins/girl-blue-cape"), - JSON: loadSkeleton("mix-and-match-pro.json", "dance", true, "full-skins/girl-blue-cape") + Binary: loadSkeleton("mix-and-match-pro.skel", "dance", "full-skins/girl-blue-cape"), + JSON: loadSkeleton("mix-and-match-pro.json", "dance", "full-skins/girl-blue-cape") }, raptor: { - Binary: loadSkeleton("raptor-pro.skel", "walk", true), - JSON: loadSkeleton("raptor-pro.json", "walk", true) + Binary: loadSkeleton("raptor-pro.skel", "walk"), + JSON: loadSkeleton("raptor-pro.json", "walk") }, spineboy: { - Binary: loadSkeleton("spineboy-pro.skel", "run", true), - JSON: loadSkeleton("spineboy-pro.json", "run", true) + Binary: loadSkeleton("spineboy-pro.skel", "run"), + JSON: loadSkeleton("spineboy-pro.json", "run") }, stretchyman: { - Binary: loadSkeleton("stretchyman-pro.skel", "sneak", true), - JSON: loadSkeleton("stretchyman-pro.json", "sneak", true) + Binary: loadSkeleton("stretchyman-pro.skel", "sneak"), + JSON: loadSkeleton("stretchyman-pro.json", "sneak") }, tank: { - Binary: loadSkeleton("tank-pro.skel", "drive", true), - JSON: loadSkeleton("tank-pro.json", "drive", true) + Binary: loadSkeleton("tank-pro.skel", "drive"), + JSON: loadSkeleton("tank-pro.json", "drive") }, vine: { - Binary: loadSkeleton("vine-pro.skel", "grow", true), - JSON: loadSkeleton("vine-pro.json", "grow", true) + Binary: loadSkeleton("vine-pro.skel", "grow"), + JSON: loadSkeleton("vine-pro.json", "grow") } }; setupUI(); @@ -156,11 +156,11 @@ requestAnimationFrame(load); } - function loadSkeleton(name, initialAnimation, premultipliedAlpha, skin) { + function loadSkeleton(name, initialAnimation, skin) { if (skin === undefined) skin = "default"; // Load the texture atlas using name.atlas from the AssetManager. - let atlas = assetManager.require(name.replace(/(?:-ess|-pro)\.(skel|json)/, "") + (premultipliedAlpha ? "-pma" : "") + ".atlas"); + let atlas = assetManager.require(name.replace(/(?:-ess|-pro)\.(skel|json)/, "") + "-pma.atlas"); // Create an AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments let atlasLoader = new spine.AtlasAttachmentLoader(atlas); @@ -219,7 +219,7 @@ }) // Pack everything up and return to caller. - return { skeleton: skeleton, state: animationState, bounds: bounds, premultipliedAlpha: premultipliedAlpha }; + return { skeleton: skeleton, state: animationState, bounds: bounds }; } function calculateSetupPoseBounds(skeleton) { @@ -318,7 +318,6 @@ let skeleton = skeletons[activeSkeleton][format].skeleton; let state = skeletons[activeSkeleton][format].state; let bounds = skeletons[activeSkeleton][format].bounds; - let premultipliedAlpha = skeletons[activeSkeleton][format].premultipliedAlpha; state.update(delta); state.apply(skeleton); skeleton.updateWorldTransform(spine.Physics.update); @@ -331,7 +330,6 @@ // Start the batch and tell the SkeletonRenderer to render the active skeleton. batcher.begin(shader); - skeletonRenderer.premultipliedAlpha = premultipliedAlpha; skeletonRenderer.draw(batcher, skeleton); batcher.end(); @@ -342,7 +340,6 @@ if (debug) { debugShader.bind(); debugShader.setUniform4x4f(spine.Shader.MVP_MATRIX, mvp.values); - debugRenderer.premultipliedAlpha = premultipliedAlpha; shapes.begin(debugShader); debugRenderer.draw(shapes, skeleton); shapes.end(); diff --git a/spine-ts/spine-webgl/example/mix-and-match.html b/spine-ts/spine-webgl/example/mix-and-match.html index 176f16652..6d00cdc4c 100644 --- a/spine-ts/spine-webgl/example/mix-and-match.html +++ b/spine-ts/spine-webgl/example/mix-and-match.html @@ -37,7 +37,7 @@ // Create the skeleton let skeletonBinary = new spine.SkeletonBinary(atlasLoader); - skeletonBinary.scale = 0.5; + skeletonBinary.scale = 1; let skeletonData = skeletonBinary.readSkeletonData(assetManager.require("mix-and-match-pro.skel")); this.skeleton = new spine.Skeleton(skeletonData); @@ -74,7 +74,7 @@ renderer.resize(spine.ResizeMode.Expand); canvas.clear(0.2, 0.2, 0.2, 1); renderer.begin(); - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); renderer.end(); } } diff --git a/spine-ts/spine-webgl/example/physics.html b/spine-ts/spine-webgl/example/physics.html index 0116f77f9..35c5bf4a7 100644 --- a/spine-ts/spine-webgl/example/physics.html +++ b/spine-ts/spine-webgl/example/physics.html @@ -41,7 +41,7 @@ var skeletonBinary = new spine.SkeletonBinary(atlasLoader); // Set the scale to apply during parsing, parse the file, and create a new skeleton. - skeletonBinary.scale = 0.2; + skeletonBinary.scale = 0.4; var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/sack-pro.skel")); this.skeleton = new spine.Skeleton(skeletonData); @@ -72,7 +72,7 @@ // Begin rendering. renderer.begin(); // Draw the skeleton - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); // Complete rendering. renderer.end(); } diff --git a/spine-ts/spine-webgl/example/physics2.html b/spine-ts/spine-webgl/example/physics2.html index 38bb7692c..20e0c47a6 100644 --- a/spine-ts/spine-webgl/example/physics2.html +++ b/spine-ts/spine-webgl/example/physics2.html @@ -124,7 +124,7 @@ // Begin rendering. renderer.begin(); // Draw the skeleton - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); // Complete rendering. renderer.end(); } diff --git a/spine-ts/spine-webgl/example/physics3.html b/spine-ts/spine-webgl/example/physics3.html index a892c20da..25995a6d2 100644 --- a/spine-ts/spine-webgl/example/physics3.html +++ b/spine-ts/spine-webgl/example/physics3.html @@ -42,7 +42,7 @@ var skeletonBinary = new spine.SkeletonBinary(atlasLoader); // Set the scale to apply during parsing, parse the file, and create a new skeleton. - skeletonBinary.scale = 0.5; + skeletonBinary.scale = 0.4; var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/snowglobe-pro.skel")); this.skeleton = new spine.Skeleton(skeletonData); @@ -74,7 +74,7 @@ // Begin rendering. renderer.begin(); // Draw the skeleton - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); // Complete rendering. renderer.end(); } diff --git a/spine-ts/spine-webgl/example/physics4.html b/spine-ts/spine-webgl/example/physics4.html index 5ddee02a2..f58656c21 100644 --- a/spine-ts/spine-webgl/example/physics4.html +++ b/spine-ts/spine-webgl/example/physics4.html @@ -73,7 +73,7 @@ // Begin rendering. renderer.begin(); // Draw the skeleton - renderer.drawSkeleton(this.skeleton, true); + renderer.drawSkeleton(this.skeleton); // Complete rendering. renderer.end(); } diff --git a/spine-ts/spine-webgl/src/AssetManager.ts b/spine-ts/spine-webgl/src/AssetManager.ts index 62e323b49..9b57296f4 100644 --- a/spine-ts/spine-webgl/src/AssetManager.ts +++ b/spine-ts/spine-webgl/src/AssetManager.ts @@ -33,8 +33,10 @@ import type { ManagedWebGLRenderingContext } from "./WebGL.js"; export class AssetManager extends AssetManagerBase { constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) { - super((image: HTMLImageElement | ImageBitmap) => { - return new GLTexture(context, image); - }, pathPrefix, downloader); + super( + (image: HTMLImageElement | ImageBitmap, pma = false) => new GLTexture(context, image, pma), + pathPrefix, + downloader, + ); } } diff --git a/spine-ts/spine-webgl/src/GLTexture.ts b/spine-ts/spine-webgl/src/GLTexture.ts index 755422d9e..a8a764f55 100644 --- a/spine-ts/spine-webgl/src/GLTexture.ts +++ b/spine-ts/spine-webgl/src/GLTexture.ts @@ -34,13 +34,13 @@ export class GLTexture extends Texture implements Disposable, Restorable { context: ManagedWebGLRenderingContext; private texture: WebGLTexture | null = null; private boundUnit = 0; - private useMipMaps = false; + private pma: boolean; + private useMipMaps: boolean; - public static DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL = false; - - constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, useMipMaps: boolean = false) { + constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, pma: boolean, useMipMaps: boolean = false) { super(image); this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context); + this.pma = pma; this.useMipMaps = useMipMaps; this.restore(); this.context.addRestorable(this); @@ -90,13 +90,15 @@ export class GLTexture extends Texture implements Disposable, Restorable { const gl = this.context.gl; if (!this.texture) this.texture = this.context.gl.createTexture(); this.bind(); - if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + const previousUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !this.pma); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, previousUnpackPmaValue); } restore () { diff --git a/spine-ts/spine-webgl/src/LoadingScreen.ts b/spine-ts/spine-webgl/src/LoadingScreen.ts index 54d5467c2..f2835d45b 100644 --- a/spine-ts/spine-webgl/src/LoadingScreen.ts +++ b/spine-ts/spine-webgl/src/LoadingScreen.ts @@ -88,7 +88,7 @@ export class LoadingScreen implements Disposable { renderer.resize(ResizeMode.Expand); renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0); - renderer.batcher.setBlendMode(BlendMode.Normal, true); + renderer.batcher.setBlendMode(BlendMode.Normal); if (complete) { this.fadeOut += this.timeKeeper.delta * (this.timeKeeper.totalTime < 1 ? 2 : 1); @@ -116,8 +116,8 @@ export class LoadingScreen implements Disposable { tempColor.set(a, a, a, a); if (!this.logo) { - this.logo = new GLTexture(renderer.context, logoImage); - this.spinner = new GLTexture(renderer.context, spinnerImage); + this.logo = new GLTexture(renderer.context, logoImage, true); + this.spinner = new GLTexture(renderer.context, spinnerImage, true); } renderer.camera.zoom = Math.max(1, spinnerSize / canvas.height); renderer.begin(); @@ -131,11 +131,11 @@ export class LoadingScreen implements Disposable { this.timeKeeper.update(); const renderer = this.renderer; - renderer.batcher.setBlendMode(BlendMode.Normal, true); + renderer.batcher.setBlendMode(BlendMode.Normal); if (!this.logo) { - this.logo = new GLTexture(renderer.context, logoImage); - this.spinner = new GLTexture(renderer.context, spinnerImage); + this.logo = new GLTexture(renderer.context, logoImage, true); + this.spinner = new GLTexture(renderer.context, spinnerImage, true); } const shiftedX = x - logoWidth / 2; diff --git a/spine-ts/spine-webgl/src/PolygonBatcher.ts b/spine-ts/spine-webgl/src/PolygonBatcher.ts index 5cc418f3b..17a3f1998 100644 --- a/spine-ts/spine-webgl/src/PolygonBatcher.ts +++ b/spine-ts/spine-webgl/src/PolygonBatcher.ts @@ -35,10 +35,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL.js"; const GL_ONE = 1; const GL_ONE_MINUS_SRC_COLOR = 0x0301; -const GL_SRC_ALPHA = 0x0302; const GL_ONE_MINUS_SRC_ALPHA = 0x0303; -// biome-ignore lint/correctness/noUnusedVariables: intentional -const GL_ONE_MINUS_DST_ALPHA = 0x0305; const GL_DST_COLOR = 0x0306; export class PolygonBatcher implements Disposable { @@ -88,16 +85,16 @@ export class PolygonBatcher implements Disposable { } } - private static blendModesGL: { srcRgb: number, srcRgbPma: number, dstRgb: number, srcAlpha: number }[] = [ - { srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE }, - { srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE, srcAlpha: GL_ONE }, - { srcRgb: GL_DST_COLOR, srcRgbPma: GL_DST_COLOR, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE }, - { srcRgb: GL_ONE, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_COLOR, srcAlpha: GL_ONE } + private static blendModesGL: { srcRgbPma: number, dstRgb: number, srcAlpha: number }[] = [ + { srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE }, + { srcRgbPma: GL_ONE, dstRgb: GL_ONE, srcAlpha: GL_ONE }, + { srcRgbPma: GL_DST_COLOR, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE }, + { srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_COLOR, srcAlpha: GL_ONE } ] - setBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean) { + setBlendMode (blendMode: BlendMode) { const blendModeGL = PolygonBatcher.blendModesGL[blendMode]; - const srcColorBlend = premultipliedAlpha ? blendModeGL.srcRgbPma : blendModeGL.srcRgb; + const srcColorBlend = blendModeGL.srcRgbPma; const srcAlphaBlend = blendModeGL.srcAlpha; const dstBlend = blendModeGL.dstRgb; diff --git a/spine-ts/spine-webgl/src/SceneRenderer.ts b/spine-ts/spine-webgl/src/SceneRenderer.ts index 6013663ca..174447757 100644 --- a/spine-ts/spine-webgl/src/SceneRenderer.ts +++ b/spine-ts/spine-webgl/src/SceneRenderer.ts @@ -87,15 +87,13 @@ export class SceneRenderer implements Disposable { this.enableRenderer(this.batcher); } - drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) { + drawSkeleton (skeleton: Skeleton, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) { this.enableRenderer(this.batcher); - this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha; this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd, transform); } - drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array) { + drawSkeletonDebug (skeleton: Skeleton, ignoredBones?: Array) { this.enableRenderer(this.shapes); - this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha; this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones); } diff --git a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts index 2cee1f3ab..e92d3c1b4 100644 --- a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts @@ -47,7 +47,6 @@ export class SkeletonDebugRenderer implements Disposable { drawPaths = true; drawSkeletonXY = false; drawClipping = true; - premultipliedAlpha = false; scale = 1; boneWidth = 2; @@ -66,8 +65,7 @@ export class SkeletonDebugRenderer implements Disposable { const skeletonX = skeleton.x; const skeletonY = skeleton.y; const gl = this.context.gl; - const srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA; - shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + shapes.setBlendMode(gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); const bones = skeleton.bones; if (this.drawBones) { diff --git a/spine-ts/spine-webgl/src/SkeletonRenderer.ts b/spine-ts/spine-webgl/src/SkeletonRenderer.ts index 6e75560ea..7b7e68ef0 100644 --- a/spine-ts/spine-webgl/src/SkeletonRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonRenderer.ts @@ -42,7 +42,6 @@ export type VertexTransformer = (vertices: NumberArrayLike, numVertices: number, export class SkeletonRenderer { static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; - premultipliedAlpha = false; private tempColor = new Color(); private tempColor2 = new Color(); private vertices: NumberArrayLike; @@ -64,7 +63,6 @@ export class SkeletonRenderer { draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) { const clipper = this.clipper; - const premultipliedAlpha = this.premultipliedAlpha; const twoColorTint = this.twoColorTint; let blendMode: BlendMode | null = null; @@ -134,33 +132,25 @@ export class SkeletonRenderer { if (texture) { const slotColor = pose.color; const finalColor = this.tempColor; - finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r; - finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g; - finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b; - finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a; - if (premultipliedAlpha) { - finalColor.r *= finalColor.a; - finalColor.g *= finalColor.a; - finalColor.b *= finalColor.a; - } + const alpha = skeletonColor.a * slotColor.a * attachmentColor.a; + finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r * alpha; + finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g * alpha; + finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b * alpha; + finalColor.a = alpha; const darkColor = this.tempColor2; if (!pose.darkColor) darkColor.set(0, 0, 0, 1.0); else { - if (premultipliedAlpha) { - darkColor.r = pose.darkColor.r * finalColor.a; - darkColor.g = pose.darkColor.g * finalColor.a; - darkColor.b = pose.darkColor.b * finalColor.a; - } else { - darkColor.setFromColor(pose.darkColor); - } - darkColor.a = premultipliedAlpha ? 1.0 : 0.0; + darkColor.r = pose.darkColor.r * alpha; + darkColor.g = pose.darkColor.g * alpha; + darkColor.b = pose.darkColor.b * alpha; + darkColor.a = 1; } const slotBlendMode = slot.data.blendMode; if (slotBlendMode !== blendMode) { blendMode = slotBlendMode; - batcher.setBlendMode(blendMode, premultipliedAlpha); + batcher.setBlendMode(blendMode); } if (clipper.isClipping() && clipper.clipTriangles(renderable.vertices, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint, vertexSize)) { @@ -175,7 +165,7 @@ export class SkeletonRenderer { verts[v] = finalColor.r; verts[v + 1] = finalColor.g; verts[v + 2] = finalColor.b; - verts[v + 3] = finalColor.a; + verts[v + 3] = alpha; verts[v + 4] = uvs[u]; verts[v + 5] = uvs[u + 1]; } @@ -184,7 +174,7 @@ export class SkeletonRenderer { verts[v] = finalColor.r; verts[v + 1] = finalColor.g; verts[v + 2] = finalColor.b; - verts[v + 3] = finalColor.a; + verts[v + 3] = alpha; verts[v + 4] = uvs[u]; verts[v + 5] = uvs[u + 1]; verts[v + 6] = darkColor.r;