src/renderers/webgl/utils/SortWorker.ts (127 lines of code) (raw):
import loadWasm from "../../../wasm/sort";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let wasmModule: any;
async function initWasm() {
wasmModule = await loadWasm();
}
let sortData: {
positions: Float32Array;
transforms: Float32Array;
transformIndices: Uint32Array;
vertexCount: number;
};
let viewProjPtr: number;
let transformsPtr: number;
let transformIndicesPtr: number;
let positionsPtr: number;
let depthBufferPtr: number;
let depthIndexPtr: number;
let startsPtr: number;
let countsPtr: number;
let allocatedVertexCount: number = 0;
let allocatedTransformCount: number = 0;
let viewProj: number[] = [];
let dirty = true;
let lock = false;
let allocationPending = false;
let sorting = false;
const allocateBuffers = async () => {
if (lock) {
allocationPending = true;
return;
}
lock = true;
allocationPending = false;
if (!wasmModule) await initWasm();
const targetAllocatedVertexCount = Math.pow(2, Math.ceil(Math.log2(sortData.vertexCount)));
if (allocatedVertexCount < targetAllocatedVertexCount) {
if (allocatedVertexCount > 0) {
wasmModule._free(viewProjPtr);
wasmModule._free(transformIndicesPtr);
wasmModule._free(positionsPtr);
wasmModule._free(depthBufferPtr);
wasmModule._free(depthIndexPtr);
wasmModule._free(startsPtr);
wasmModule._free(countsPtr);
}
allocatedVertexCount = targetAllocatedVertexCount;
viewProjPtr = wasmModule._malloc(16 * 4);
transformIndicesPtr = wasmModule._malloc(allocatedVertexCount * 4);
positionsPtr = wasmModule._malloc(3 * allocatedVertexCount * 4);
depthBufferPtr = wasmModule._malloc(allocatedVertexCount * 4);
depthIndexPtr = wasmModule._malloc(allocatedVertexCount * 4);
startsPtr = wasmModule._malloc(allocatedVertexCount * 4);
countsPtr = wasmModule._malloc(allocatedVertexCount * 4);
}
if (allocatedTransformCount < sortData.transforms.length) {
if (allocatedTransformCount > 0) {
wasmModule._free(transformsPtr);
}
allocatedTransformCount = sortData.transforms.length;
transformsPtr = wasmModule._malloc(allocatedTransformCount * 4);
}
lock = false;
if (allocationPending) {
allocationPending = false;
await allocateBuffers();
}
};
const runSort = () => {
if (lock || allocationPending || !wasmModule) return;
lock = true;
wasmModule.HEAPF32.set(sortData.positions, positionsPtr / 4);
wasmModule.HEAPF32.set(sortData.transforms, transformsPtr / 4);
wasmModule.HEAPU32.set(sortData.transformIndices, transformIndicesPtr / 4);
wasmModule.HEAPF32.set(new Float32Array(viewProj), viewProjPtr / 4);
wasmModule._sort(
viewProjPtr,
transformsPtr,
transformIndicesPtr,
sortData.vertexCount,
positionsPtr,
depthBufferPtr,
depthIndexPtr,
startsPtr,
countsPtr,
);
const depthIndex = new Uint32Array(wasmModule.HEAPU32.buffer, depthIndexPtr, sortData.vertexCount);
const detachedDepthIndex = new Uint32Array(depthIndex.slice().buffer);
self.postMessage({ depthIndex: detachedDepthIndex }, [detachedDepthIndex.buffer]);
lock = false;
dirty = false;
};
const throttledSort = () => {
if (!sorting) {
sorting = true;
if (dirty) runSort();
setTimeout(() => {
sorting = false;
throttledSort();
});
}
};
self.onmessage = (e) => {
if (e.data.sortData) {
//Recreating the typed arrays every time, will cause firefox to leak memory
if (!sortData) {
sortData = {
positions: new Float32Array(e.data.sortData.positions),
transforms: new Float32Array(e.data.sortData.transforms),
transformIndices: new Uint32Array(e.data.sortData.transformIndices),
vertexCount: e.data.sortData.vertexCount,
};
} else {
sortData.positions.set(e.data.sortData.positions);
sortData.transforms.set(e.data.sortData.transforms);
sortData.transformIndices.set(e.data.sortData.transformIndices);
sortData.vertexCount = e.data.sortData.vertexCount;
}
dirty = true;
allocateBuffers();
}
if (e.data.viewProj) {
if ((e.data.viewProj as number[]).every((item) => viewProj.includes(item)) === false) {
viewProj = e.data.viewProj;
dirty = true;
}
throttledSort();
}
};