[Perf] RLottie: Add GPU-acceleration with ImageBitmap
This commit is contained in:
parent
a5cda0f209
commit
572fb78daa
@ -16,8 +16,8 @@ interface Params {
|
|||||||
coords?: { x: number; y: number };
|
coords?: { x: number; y: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frames = ArrayBuffer[];
|
type Frame = ImageBitmap;
|
||||||
type Chunks = (Frames | undefined)[];
|
type Chunks = (Frame[] | undefined)[];
|
||||||
|
|
||||||
// TODO Consider removing chunks
|
// TODO Consider removing chunks
|
||||||
const CHUNK_SIZE = 1;
|
const CHUNK_SIZE = 1;
|
||||||
@ -49,6 +49,8 @@ class RLottie {
|
|||||||
|
|
||||||
private imgSize!: number;
|
private imgSize!: number;
|
||||||
|
|
||||||
|
private imageData!: ImageData;
|
||||||
|
|
||||||
private msPerFrame = 1000 / 60;
|
private msPerFrame = 1000 / 60;
|
||||||
|
|
||||||
private reduceFactor = 1;
|
private reduceFactor = 1;
|
||||||
@ -263,6 +265,7 @@ class RLottie {
|
|||||||
|
|
||||||
if (!this.imgSize) {
|
if (!this.imgSize) {
|
||||||
this.imgSize = imgSize;
|
this.imgSize = imgSize;
|
||||||
|
this.imageData = new ImageData(imgSize, imgSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isRendererInited) {
|
if (this.isRendererInited) {
|
||||||
@ -299,6 +302,7 @@ class RLottie {
|
|||||||
this.tgsUrl,
|
this.tgsUrl,
|
||||||
this.imgSize,
|
this.imgSize,
|
||||||
this.params.isLowPriority,
|
this.params.isLowPriority,
|
||||||
|
this.customColor,
|
||||||
this.onRendererInit.bind(this),
|
this.onRendererInit.bind(this),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -407,26 +411,14 @@ class RLottie {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const arr = new Uint8ClampedArray(frame);
|
|
||||||
if (this.customColor) {
|
|
||||||
for (let i = 0; i < arr.length; i += 4) {
|
|
||||||
/* eslint-disable prefer-destructuring */
|
|
||||||
arr[i] = this.customColor[0];
|
|
||||||
arr[i + 1] = this.customColor[1];
|
|
||||||
arr[i + 2] = this.customColor[2];
|
|
||||||
/* eslint-enable prefer-destructuring */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageData = new ImageData(arr, this.imgSize, this.imgSize);
|
|
||||||
|
|
||||||
this.containers.forEach((containerData) => {
|
this.containers.forEach((containerData) => {
|
||||||
const {
|
const {
|
||||||
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad,
|
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad,
|
||||||
} = containerData;
|
} = containerData;
|
||||||
|
|
||||||
if (!isLoaded || !isPaused) {
|
if (!isLoaded || !isPaused) {
|
||||||
ctx.putImageData(imageData, x || 0, y || 0);
|
ctx.clearRect(x || 0, y || 0, this.imgSize, this.imgSize);
|
||||||
|
ctx.drawImage(frame, x || 0, y || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
@ -511,6 +503,17 @@ class RLottie {
|
|||||||
return chunk[indexInChunk];
|
return chunk[indexInChunk];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setFrame(frameIndex: number, frame: Frame) {
|
||||||
|
const chunkIndex = this.getChunkIndex(frameIndex);
|
||||||
|
const indexInChunk = this.getFrameIndexInChunk(frameIndex);
|
||||||
|
const chunk = this.chunks[chunkIndex];
|
||||||
|
if (!chunk) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk[indexInChunk] = frame;
|
||||||
|
}
|
||||||
|
|
||||||
private getFrameIndexInChunk(frameIndex: number) {
|
private getFrameIndexInChunk(frameIndex: number) {
|
||||||
const chunkIndex = this.getChunkIndex(frameIndex);
|
const chunkIndex = this.getChunkIndex(frameIndex);
|
||||||
return frameIndex - chunkIndex * this.chunkSize;
|
return frameIndex - chunkIndex * this.chunkSize;
|
||||||
@ -557,7 +560,7 @@ class RLottie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFrameLoad(frameIndex: number, arrayBuffer: ArrayBuffer) {
|
private onFrameLoad(frameIndex: number, imageBitmap: ImageBitmap) {
|
||||||
const chunkIndex = this.getChunkIndex(frameIndex);
|
const chunkIndex = this.getChunkIndex(frameIndex);
|
||||||
const chunk = this.chunks[chunkIndex];
|
const chunk = this.chunks[chunkIndex];
|
||||||
// Frame can be skipped and chunk can be already cleaned up
|
// Frame can be skipped and chunk can be already cleaned up
|
||||||
@ -565,7 +568,7 @@ class RLottie {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk[this.getFrameIndexInChunk(frameIndex)] = arrayBuffer;
|
chunk[this.getFrameIndexInChunk(frameIndex)] = imageBitmap;
|
||||||
|
|
||||||
if (this.isWaiting) {
|
if (this.isWaiting) {
|
||||||
this.doPlay();
|
this.doPlay();
|
||||||
|
|||||||
@ -33,6 +33,8 @@ const renderers = new Map<string, {
|
|||||||
imgSize: number;
|
imgSize: number;
|
||||||
reduceFactor: number;
|
reduceFactor: number;
|
||||||
handle: any;
|
handle: any;
|
||||||
|
imageData: ImageData;
|
||||||
|
customColor?: [number, number, number];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function init(
|
async function init(
|
||||||
@ -40,6 +42,7 @@ async function init(
|
|||||||
tgsUrl: string,
|
tgsUrl: string,
|
||||||
imgSize: number,
|
imgSize: number,
|
||||||
isLowPriority: boolean,
|
isLowPriority: boolean,
|
||||||
|
customColor: [number, number, number] | undefined,
|
||||||
onInit: CancellableCallback,
|
onInit: CancellableCallback,
|
||||||
) {
|
) {
|
||||||
if (!rLottieApi) {
|
if (!rLottieApi) {
|
||||||
@ -52,9 +55,14 @@ async function init(
|
|||||||
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
||||||
rLottieApi.resize(handle, imgSize, imgSize);
|
rLottieApi.resize(handle, imgSize, imgSize);
|
||||||
|
|
||||||
|
const imageData = new ImageData(imgSize, imgSize);
|
||||||
|
|
||||||
const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount);
|
const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount);
|
||||||
|
|
||||||
renderers.set(key, { imgSize, reduceFactor, handle });
|
renderers.set(key, {
|
||||||
|
imgSize, reduceFactor, handle, imageData, customColor,
|
||||||
|
});
|
||||||
|
|
||||||
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +82,7 @@ async function changeData(
|
|||||||
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
|
||||||
|
|
||||||
const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount);
|
const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount);
|
||||||
|
|
||||||
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
onInit(reduceFactor, msPerFrame, reducedFramesCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +119,9 @@ async function renderFrames(
|
|||||||
await rLottieApiPromise;
|
await rLottieApiPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { imgSize, reduceFactor, handle } = renderers.get(key)!;
|
const {
|
||||||
|
imgSize, reduceFactor, handle, imageData, customColor,
|
||||||
|
} = renderers.get(key)!;
|
||||||
|
|
||||||
for (let i = fromIndex; i <= toIndex; i++) {
|
for (let i = fromIndex; i <= toIndex; i++) {
|
||||||
const realIndex = i * reduceFactor;
|
const realIndex = i * reduceFactor;
|
||||||
@ -118,8 +129,26 @@ async function renderFrames(
|
|||||||
rLottieApi.render(handle, realIndex);
|
rLottieApi.render(handle, realIndex);
|
||||||
const bufferPointer = rLottieApi.buffer(handle);
|
const bufferPointer = rLottieApi.buffer(handle);
|
||||||
const data = Module.HEAPU8.subarray(bufferPointer, bufferPointer + (imgSize * imgSize * 4));
|
const data = Module.HEAPU8.subarray(bufferPointer, bufferPointer + (imgSize * imgSize * 4));
|
||||||
const arrayBuffer = new Uint8ClampedArray(data).buffer;
|
|
||||||
onProgress(i, arrayBuffer);
|
if (customColor) {
|
||||||
|
const arr = new Uint8ClampedArray(data);
|
||||||
|
applyColor(arr, customColor);
|
||||||
|
imageData.data.set(arr);
|
||||||
|
} else {
|
||||||
|
imageData.data.set(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageBitmap = await createImageBitmap(imageData);
|
||||||
|
|
||||||
|
onProgress(i, imageBitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyColor(arr: Uint8ClampedArray, color: [number, number, number]) {
|
||||||
|
for (let i = 0; i < arr.length; i += 4) {
|
||||||
|
arr[i] = color[0];
|
||||||
|
arr[i + 1] = color[1];
|
||||||
|
arr[i + 2] = color[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export default function createInterface(api: Record<string, Function>) {
|
|||||||
type: 'methodCallback',
|
type: 'methodCallback',
|
||||||
messageId,
|
messageId,
|
||||||
callbackArgs,
|
callbackArgs,
|
||||||
}, lastArg instanceof ArrayBuffer ? [lastArg] : undefined);
|
}, isTransferable(lastArg) ? [lastArg] : undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
callbackState.set(messageId, callback);
|
callbackState.set(messageId, callback);
|
||||||
@ -78,6 +78,10 @@ export default function createInterface(api: Record<string, Function>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTransferable(obj: any) {
|
||||||
|
return obj instanceof ArrayBuffer || obj instanceof ImageBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
function handleErrors() {
|
function handleErrors() {
|
||||||
self.onerror = (e) => {
|
self.onerror = (e) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -92,9 +96,9 @@ function handleErrors() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendToOrigin(data: WorkerMessageData, arrayBuffers?: ArrayBuffer[]) {
|
function sendToOrigin(data: WorkerMessageData, transferables?: Transferable[]) {
|
||||||
if (arrayBuffers) {
|
if (transferables) {
|
||||||
postMessage(data, arrayBuffers);
|
postMessage(data, transferables);
|
||||||
} else {
|
} else {
|
||||||
postMessage(data);
|
postMessage(data);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user