[ts][canvaskit] Use Uint32Array for colors to avoid canvaskit to allocate one each vertices creation.

This commit is contained in:
Davide Tantillo 2025-07-22 15:42:50 +02:00
parent 532fdb87eb
commit c7cf509071
2 changed files with 59 additions and 75 deletions

View File

@ -1,9 +1,9 @@
import * as fs from "fs"
import { fileURLToPath } from 'url';
import path from 'path';
import CanvasKitInit from "canvaskit-wasm"; import CanvasKitInit from "canvaskit-wasm";
import UPNG from "@pdf-lib/upng" import UPNG from "@pdf-lib/upng"
import {loadTextureAtlas, SkeletonRenderer, Skeleton, SkeletonBinary, AnimationState, AnimationStateData, AtlasAttachmentLoader, Physics, loadSkeletonData, SkeletonDrawable} from "../dist/index.js" import path from "node:path";
import { fileURLToPath } from "node:url";
import { readFileSync, writeFileSync } from "node:fs"
import { loadTextureAtlas, SkeletonRenderer, loadSkeletonData, SkeletonDrawable } from "../dist/index.js"
// Get the current directory // Get the current directory
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@ -19,10 +19,10 @@ async function main() {
if (!surface) throw new Error(); if (!surface) throw new Error();
// Load atlas // Load atlas
const atlas = await loadTextureAtlas(ck, __dirname + "/../../assets/spineboy.atlas", async (path) => fs.readFileSync(path)); const atlas = await loadTextureAtlas(ck, `${__dirname}/../../assets/spineboy.atlas`, async (path) => readFileSync(path));
// Load the skeleton data // Load the skeleton data
const skeletonData = await loadSkeletonData(__dirname + "/../../assets/spineboy-pro.skel", atlas, async (path) => fs.readFileSync(path)); const skeletonData = await loadSkeletonData(`${__dirname}/../../assets/spineboy-pro.skel`, atlas, async (path) => readFileSync(path));
// Create a SkeletonDrawable // Create a SkeletonDrawable
const drawable = new SkeletonDrawable(skeletonData); const drawable = new SkeletonDrawable(skeletonData);
@ -68,7 +68,7 @@ async function main() {
} }
const apng = UPNG.default.encode(frames, 600, 400, 0, frames.map(() => FRAME_TIME * 1000)); const apng = UPNG.default.encode(frames, 600, 400, 0, frames.map(() => FRAME_TIME * 1000));
fs.writeFileSync('output.png', Buffer.from(apng)); writeFileSync('output.png', Buffer.from(apng));
} }
main(); main();

View File

@ -35,7 +35,8 @@ import {
AtlasAttachmentLoader, AtlasAttachmentLoader,
BlendMode, BlendMode,
ClippingAttachment, ClippingAttachment,
Color, type Color,
MathUtils,
MeshAttachment, MeshAttachment,
type NumberArrayLike, type NumberArrayLike,
Physics, Physics,
@ -224,12 +225,10 @@ export class SkeletonDrawable {
*/ */
export class SkeletonRenderer { export class SkeletonRenderer {
private clipper = new SkeletonClipping(); private clipper = new SkeletonClipping();
private tempColor = new Color();
private tempColor2 = new Color();
private static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; private static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
private scratchPositions = Utils.newFloatArray(100); private scratchPositions = Utils.newFloatArray(100);
private scratchColors = Utils.newFloatArray(100);
private scratchUVs = Utils.newFloatArray(100); private scratchUVs = Utils.newFloatArray(100);
private scratchColors = new Uint32Array(100 / 4);
/** /**
* Creates a new skeleton renderer. * Creates a new skeleton renderer.
@ -257,50 +256,40 @@ export class SkeletonRenderer {
const attachment = slot.getAttachment(); const attachment = slot.getAttachment();
let positions = this.scratchPositions; let positions = this.scratchPositions;
let colors = this.scratchColors;
let uvs: NumberArrayLike;
let texture: CanvasKitTexture;
let triangles: Array<number>; let triangles: Array<number>;
let attachmentColor: Color; let numVertices = 4;
let numVertices = 0;
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
const region = attachment; attachment.computeWorldVertices(slot, positions, 0, 2);
numVertices = 4;
region.computeWorldVertices(slot, positions, 0, 2);
triangles = SkeletonRenderer.QUAD_TRIANGLES; triangles = SkeletonRenderer.QUAD_TRIANGLES;
uvs = region.uvs as Float32Array;
texture = region.region ?.texture as CanvasKitTexture;
attachmentColor = region.color;
} else if (attachment instanceof MeshAttachment) { } else if (attachment instanceof MeshAttachment) {
const mesh = attachment as MeshAttachment; if (positions.length < attachment.worldVerticesLength) {
if (positions.length < mesh.worldVerticesLength) { this.scratchPositions = Utils.newFloatArray(attachment.worldVerticesLength);
this.scratchPositions = Utils.newFloatArray(mesh.worldVerticesLength);
positions = this.scratchPositions; positions = this.scratchPositions;
} }
numVertices = mesh.worldVerticesLength >> 1; numVertices = attachment.worldVerticesLength >> 1;
mesh.computeWorldVertices( attachment.computeWorldVertices(
slot, slot,
0, 0,
mesh.worldVerticesLength, attachment.worldVerticesLength,
positions, positions,
0, 0,
2 2
); );
triangles = mesh.triangles; triangles = attachment.triangles;
texture = mesh.region ?.texture as CanvasKitTexture;
uvs = mesh.uvs as Float32Array;
attachmentColor = mesh.color;
} else if (attachment instanceof ClippingAttachment) { } else if (attachment instanceof ClippingAttachment) {
const clip = attachment as ClippingAttachment; clipper.clipStart(slot, attachment);
clipper.clipStart(slot, clip);
continue; continue;
} else { } else {
clipper.clipEndWithSlot(slot); clipper.clipEndWithSlot(slot);
continue; continue;
} }
const texture = attachment.region?.texture as CanvasKitTexture;
if (texture) { if (texture) {
let uvs = attachment.uvs;
let scaledUvs: NumberArrayLike; let scaledUvs: NumberArrayLike;
let colors = this.scratchColors;
if (clipper.isClipping()) { if (clipper.isClipping()) {
clipper.clipTrianglesUnpacked(positions, triangles, triangles.length, uvs); clipper.clipTrianglesUnpacked(positions, triangles, triangles.length, uvs);
if (clipper.clippedVertices.length <= 0) { if (clipper.clippedVertices.length <= 0) {
@ -308,21 +297,16 @@ export class SkeletonRenderer {
continue; continue;
} }
positions = clipper.clippedVertices; positions = clipper.clippedVertices;
uvs = clipper.clippedUVs; uvs = scaledUvs = clipper.clippedUVs;
scaledUvs = clipper.clippedUVs;
triangles = clipper.clippedTriangles; triangles = clipper.clippedTriangles;
numVertices = clipper.clippedVertices.length / 2; numVertices = clipper.clippedVertices.length / 2;
colors = Utils.newFloatArray(numVertices * 4); colors = new Uint32Array(numVertices);
} else { } else {
scaledUvs = this.scratchUVs; scaledUvs = this.scratchUVs;
if (this.scratchUVs.length < uvs.length) { if (this.scratchUVs.length < uvs.length)
this.scratchUVs = Utils.newFloatArray(uvs.length); scaledUvs = this.scratchUVs = Utils.newFloatArray(uvs.length);
scaledUvs = this.scratchUVs; if (colors.length < numVertices)
} colors = this.scratchColors = new Uint32Array(numVertices);
if (colors.length / 4 < numVertices) {
this.scratchColors = Utils.newFloatArray(numVertices * 4);
colors = this.scratchColors;
}
} }
const ckImage = texture.getImage(); const ckImage = texture.getImage();
@ -334,19 +318,19 @@ export class SkeletonRenderer {
scaledUvs[i + 1] = uvs[i + 1] * height; scaledUvs[i + 1] = uvs[i + 1] * height;
} }
const attachmentColor = attachment.color;
const slotColor = slot.color; const slotColor = slot.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;
for (let i = 0, n = numVertices * 4; i < n; i += 4) { // using Uint32Array for colors allows to avoid canvaskit to allocate one each time
colors[i] = finalColor.r; // but colors need to be in canvaskit format.
colors[i + 1] = finalColor.g; // See: https://github.com/google/skia/blob/bb8c36fdf7b915a8c096e35e2f08109e477fe1b8/modules/canvaskit/color.js#L163
colors[i + 2] = finalColor.b; const finalColor = (
colors[i + 3] = finalColor.a; MathUtils.clamp(skeletonColor.a * slotColor.a * attachmentColor.a * 255, 0, 255) << 24 |
} MathUtils.clamp(skeletonColor.r * slotColor.r * attachmentColor.r * 255, 0, 255) << 16 |
MathUtils.clamp(skeletonColor.g * slotColor.g * attachmentColor.g * 255, 0, 255) << 8 |
MathUtils.clamp(skeletonColor.b * slotColor.b * attachmentColor.b * 255, 0, 255) << 0
) >>> 0;
for (let i = 0, n = numVertices; i < n; i++) colors[i] = finalColor;
const vertices = this.ck.MakeVertices( const vertices = this.ck.MakeVertices(
this.ck.VertexMode.Triangles, this.ck.VertexMode.Triangles,