Skip to content

Commit

Permalink
Merge pull request #14773 from RaananW/preparingForExternalDependencies
Browse files Browse the repository at this point in the history
Preparation for the external dependencies package
  • Loading branch information
RaananW committed Feb 28, 2024
2 parents 3c470d5 + b9f46fc commit 7e708fa
Show file tree
Hide file tree
Showing 18 changed files with 988 additions and 771 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
);
} else if (KhronosTextureContainer2.IsValid(data)) {
const ktx2 = new KhronosTextureContainer2(texture.getEngine());
ktx2.uploadAsync(data, texture, options).then(
ktx2._uploadAsync(data, texture, options).then(
() => {
callback(texture.width, texture.height, texture.generateMipMaps, true, () => {}, false);
},
Expand Down
503 changes: 145 additions & 358 deletions packages/dev/core/src/Meshes/Compression/dracoCompression.ts

Large diffs are not rendered by default.

261 changes: 261 additions & 0 deletions packages/dev/core/src/Meshes/Compression/dracoCompressionWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import type { Nullable } from "core/types";
import type { DecoderModule, DecoderBuffer, Decoder, Mesh, PointCloud, Status } from "draco3dgltf";
import { DracoDecoderModule } from "draco3dgltf";

// eslint-disable-next-line @typescript-eslint/naming-convention
declare let DracoDecoderModule: DracoDecoderModule;

export interface AttributeData {
kind: string;
data: ArrayBufferView;
size: number;
byteOffset: number;
byteStride: number;
normalized: boolean;
}

interface InitDoneMessage {
id: "initDone";
}

interface DecodeMeshDoneMessage {
id: "decodeMeshDone";
totalVertices: number;
}

interface IndicesMessage {
id: "indices";
data: Uint16Array | Uint32Array;
}

interface AttributeMessage extends AttributeData {
id: "attribute";
}
export type Message = InitDoneMessage | DecodeMeshDoneMessage | IndicesMessage | AttributeMessage;
// WorkerGlobalScope
declare function importScripts(...urls: string[]): void;
declare function postMessage(message: Message, transfer?: any[]): void;

/**
* @internal
*/
export function decodeMesh(
decoderModule: DecoderModule,
data: Int8Array,
attributes: { [kind: string]: number } | undefined,
onIndicesData: (indices: Uint16Array | Uint32Array) => void,
onAttributeData: (kind: string, data: ArrayBufferView, size: number, offset: number, stride: number, normalized: boolean) => void
): number {
let decoder: Nullable<Decoder> = null;
let buffer: Nullable<DecoderBuffer> = null;
let geometry: Nullable<Mesh | PointCloud> = null;

try {
decoder = new decoderModule.Decoder();

buffer = new decoderModule.DecoderBuffer();
buffer.Init(data, data.byteLength);

let status: Status;
const type = decoder.GetEncodedGeometryType(buffer);
switch (type) {
case decoderModule.TRIANGULAR_MESH: {
const mesh = new decoderModule.Mesh();
status = decoder.DecodeBufferToMesh(buffer, mesh);
if (!status.ok() || mesh.ptr === 0) {
throw new Error(status.error_msg());
}

const numFaces = mesh.num_faces();
const numIndices = numFaces * 3;
const byteLength = numIndices * 4;

const ptr = decoderModule._malloc(byteLength);
try {
decoder.GetTrianglesUInt32Array(mesh, byteLength, ptr);
const indices = new Uint32Array(numIndices);
indices.set(new Uint32Array(decoderModule.HEAPF32.buffer, ptr, numIndices));
onIndicesData(indices);
} finally {
decoderModule._free(ptr);
}

geometry = mesh;
break;
}
case decoderModule.POINT_CLOUD: {
const pointCloud = new decoderModule.PointCloud();
status = decoder.DecodeBufferToPointCloud(buffer, pointCloud);
if (!status.ok() || !pointCloud.ptr) {
throw new Error(status.error_msg());
}

geometry = pointCloud;
break;
}
default: {
throw new Error(`Invalid geometry type ${type}`);
}
}

const numPoints = geometry.num_points();

const processAttribute = (decoder: Decoder, geometry: Mesh | PointCloud, kind: string, attribute: any) => {
const dataType = attribute.data_type();
const numComponents = attribute.num_components();
const normalized = attribute.normalized();
const byteStride = attribute.byte_stride();
const byteOffset = attribute.byte_offset();

const dataTypeInfo = {
[decoderModule.DT_FLOAT32]: { typedArrayConstructor: Float32Array, heap: decoderModule.HEAPF32 },
[decoderModule.DT_INT8]: { typedArrayConstructor: Int8Array, heap: decoderModule.HEAP8 },
[decoderModule.DT_INT16]: { typedArrayConstructor: Int16Array, heap: decoderModule.HEAP16 },
[decoderModule.DT_INT32]: { typedArrayConstructor: Int32Array, heap: decoderModule.HEAP32 },
[decoderModule.DT_UINT8]: { typedArrayConstructor: Uint8Array, heap: decoderModule.HEAPU8 },
[decoderModule.DT_UINT16]: { typedArrayConstructor: Uint16Array, heap: decoderModule.HEAPU16 },
[decoderModule.DT_UINT32]: { typedArrayConstructor: Uint32Array, heap: decoderModule.HEAPU32 },
};

const info = dataTypeInfo[dataType];
if (!info) {
throw new Error(`Invalid data type ${dataType}`);
}

const numValues = numPoints * numComponents;
const byteLength = numValues * info.typedArrayConstructor.BYTES_PER_ELEMENT;

const ptr = decoderModule._malloc(byteLength);
try {
decoder.GetAttributeDataArrayForAllPoints(geometry, attribute, dataType, byteLength, ptr);
const data = new info.typedArrayConstructor(info.heap.buffer, ptr, numValues);
onAttributeData(kind, data.slice(), numComponents, byteOffset, byteStride, normalized);
} finally {
decoderModule._free(ptr);
}
};

if (attributes) {
for (const kind in attributes) {
const id = attributes[kind];
const attribute = decoder.GetAttributeByUniqueId(geometry, id);
processAttribute(decoder, geometry, kind, attribute);
}
} else {
const dracoAttributeTypes: { [kind: string]: number } = {
position: decoderModule.POSITION,
normal: decoderModule.NORMAL,
color: decoderModule.COLOR,
uv: decoderModule.TEX_COORD,
};

for (const kind in dracoAttributeTypes) {
const id = decoder.GetAttributeId(geometry, dracoAttributeTypes[kind]);
if (id !== -1) {
const attribute = decoder.GetAttribute(geometry, id);
processAttribute(decoder, geometry, kind, attribute);
}
}
}

return numPoints;
} finally {
if (geometry) {
decoderModule.destroy(geometry);
}

if (buffer) {
decoderModule.destroy(buffer);
}

if (decoder) {
decoderModule.destroy(decoder);
}
}
}

/**
* The worker function that gets converted to a blob url to pass into a worker.
* To be used if a developer wants to create their own worker instance and inject it instead of using the default worker.
*/
export function workerFunction(): void {
let decoderPromise: PromiseLike<any> | undefined;

onmessage = (event) => {
const message = event.data;
switch (message.id) {
case "init": {
const decoder = message.decoder;
// if URL is provided then load the script. Otherwise expect the script to be loaded already
if (decoder.url) {
importScripts(decoder.url);
}
decoderPromise = DracoDecoderModule({ wasmBinary: decoder.wasmBinary });
postMessage({ id: "initDone" });
break;
}
case "decodeMesh": {
if (!decoderPromise) {
throw new Error("Draco decoder module is not available");
}
decoderPromise.then((decoder) => {
const numPoints = decodeMesh(
decoder,
message.dataView,
message.attributes,
(indices) => {
postMessage({ id: "indices", data: indices }, [indices.buffer]);
},
(kind, data, size, offset, stride, normalized) => {
postMessage({ id: "attribute", kind, data, size, byteOffset: offset, byteStride: stride, normalized }, [data.buffer]);
}
);
postMessage({ id: "decodeMeshDone", totalVertices: numPoints });
});
break;
}
}
};
}

/**
* Initializes a worker that was created for the draco agent pool
* @param worker The worker to initialize
* @param decoderWasmBinary The wasm binary to load into the worker
* @param moduleUrl The url to the draco decoder module (optional)
* @returns A promise that resolves when the worker is initialized
*/
export function initializeWebWorker(worker: Worker, decoderWasmBinary: ArrayBuffer, moduleUrl?: string): Promise<Worker> {
return new Promise<Worker>((resolve, reject) => {
const onError = (error: ErrorEvent) => {
worker.removeEventListener("error", onError);
worker.removeEventListener("message", onMessage);
reject(error);
};

const onMessage = (event: MessageEvent<Message>) => {
if (event.data.id === "initDone") {
worker.removeEventListener("error", onError);
worker.removeEventListener("message", onMessage);
resolve(worker);
}
};

worker.addEventListener("error", onError);
worker.addEventListener("message", onMessage);

// clone the array buffer to make it transferable
const clone = decoderWasmBinary.slice(0);
worker.postMessage(
{
id: "init",
decoder: {
url: moduleUrl,
wasmBinary: clone,
},
},
[clone]
);
// note: no transfer list as the ArrayBuffer is shared across main thread and pool workers
});
}

0 comments on commit 7e708fa

Please sign in to comment.