Skip to content

Commit

Permalink
Preserve vertex buffer types in DracoEncoder (#16153)
Browse files Browse the repository at this point in the history
* Allow other TypedArray data for draco

* Use GetTypedArrayData where appropriate

* Force copy vertex buffers ourselves, not in encoder
  • Loading branch information
alexchuber authored Feb 6, 2025
1 parent 00abfc4 commit dbc3d4f
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 25 deletions.
6 changes: 6 additions & 0 deletions packages/dev/core/src/Buffers/bufferUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import { Constants } from "../Engines/constants";
import { Logger } from "../Misc/logger";
import type { DataArray, FloatArray, IndicesArray, Nullable, TypedArray } from "../types";

/**
* Union of TypedArrays that can be used for vertex data.
*/
export type VertexDataTypedArray = Exclude<TypedArray, Float64Array | BigInt64Array | BigUint64Array>;

/**
* Interface for a constructor of a TypedArray.
*/
export interface TypedArrayConstructor<T extends TypedArray = TypedArray> {
new (length: number): T;
new (elements: Iterable<number>): T;
Expand Down
23 changes: 20 additions & 3 deletions packages/dev/core/src/Meshes/Compression/dracoCompressionWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { EncoderMessage, IDracoAttributeData, IDracoEncodedMeshData, IDraco
import type { DecoderMessage } from "./dracoDecoder.types";
import type { DecoderBuffer, Decoder, Mesh, PointCloud, Status, DecoderModule, EncoderModule, MeshBuilder, Encoder, DracoInt8Array } from "draco3dgltf";
import { DracoDecoderModule } from "draco3dgltf";
import type { TypedArrayConstructor } from "core/Buffers";
import type { TypedArrayConstructor, VertexDataTypedArray } from "core/Buffers";

// eslint-disable-next-line @typescript-eslint/naming-convention
declare let DracoDecoderModule: DracoDecoderModule;
Expand Down Expand Up @@ -59,10 +59,27 @@ export function EncodeMesh(
// Add the faces
meshBuilder.AddFacesToMesh(mesh, indices.length / 3, indices);

const addAttributeMap = new Map<
Function,
(builder: MeshBuilder, mesh: Mesh, attr: any, count: number, size: number, data: Exclude<VertexDataTypedArray, Uint8ClampedArray>) => number
>([
[Float32Array, (mb, m, a, c, s, d) => mb.AddFloatAttribute(m, a, c, s, d)],
[Uint32Array, (mb, m, a, c, s, d) => mb.AddUInt32Attribute(m, a, c, s, d)],
[Uint16Array, (mb, m, a, c, s, d) => mb.AddUInt16Attribute(m, a, c, s, d)],
[Uint8Array, (mb, m, a, c, s, d) => mb.AddUInt8Attribute(m, a, c, s, d)],
[Int32Array, (mb, m, a, c, s, d) => mb.AddInt32Attribute(m, a, c, s, d)],
[Int16Array, (mb, m, a, c, s, d) => mb.AddInt16Attribute(m, a, c, s, d)],
[Int8Array, (mb, m, a, c, s, d) => mb.AddInt8Attribute(m, a, c, s, d)],
]);

// Add the attributes
for (const attribute of attributes) {
if (attribute.data instanceof Uint8ClampedArray) {
attribute.data = new Uint8Array(attribute.data); // Draco does not support Uint8ClampedArray
}
const addAttribute = addAttributeMap.get(attribute.data.constructor)!;
const verticesCount = attribute.data.length / attribute.size;
attributeIDs[attribute.kind] = meshBuilder.AddFloatAttribute(mesh, encoderModule[attribute.dracoName], verticesCount, attribute.size, attribute.data);
attributeIDs[attribute.kind] = addAttribute(meshBuilder, mesh, encoderModule[attribute.dracoName], verticesCount, attribute.size, attribute.data);
if (options.quantizationBits && options.quantizationBits[attribute.dracoName]) {
encoder.SetAttributeQuantization(encoderModule[attribute.dracoName], options.quantizationBits[attribute.dracoName]);
}
Expand All @@ -77,7 +94,7 @@ export function EncodeMesh(
}

// Encode to native buffer
encodedNativeBuffer = new encoderModule.DracoInt8Array() as DracoInt8Array;
encodedNativeBuffer = new encoderModule.DracoInt8Array();
const encodedLength = encoder.EncodeMeshToDracoBuffer(mesh, encodedNativeBuffer);
if (encodedLength <= 0) {
throw new Error("Draco encoding failed.");
Expand Down
28 changes: 17 additions & 11 deletions packages/dev/core/src/Meshes/Compression/dracoEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { Geometry } from "../geometry";
import { Logger } from "../../Misc/logger";
import { deepMerge } from "../../Misc/deepMerger";
import type { EncoderModule } from "draco3d";
import { AreIndices32Bits } from "core/Buffers/bufferUtils";
import { AreIndices32Bits, GetTypedArrayData } from "core/Buffers/bufferUtils";

// Missing type from types/draco3d. Do not use in public scope; UMD tests will fail because of EncoderModule.
type DracoEncoderModule = (props: { wasmBinary?: ArrayBuffer }) => Promise<EncoderModule>;
Expand Down Expand Up @@ -41,7 +41,7 @@ function GetDracoAttributeName(kind: string): DracoAttributeName {
* @internal
*/
function PrepareIndicesForDraco(input: Mesh | Geometry): Nullable<Uint32Array | Uint16Array> {
let indices = input.getIndices();
let indices = input.getIndices(undefined, true);

// Convert number[] and Int32Array types, if needed
if (indices && !(indices instanceof Uint32Array) && !(indices instanceof Uint16Array)) {
Expand All @@ -68,12 +68,20 @@ function PrepareAttributesForDraco(input: Mesh | Geometry, excludedAttributes?:
continue;
}

// Convert number[] to typed array, if needed
let data = input.getVerticesData(kind)!;
if (!(data instanceof Float32Array)) {
data = Float32Array.from(data!);
}
attributes.push({ kind: kind, dracoName: GetDracoAttributeName(kind), size: input.getVertexBuffer(kind)!.getSize(), data: data });
// Convert number[] to typed array, if needed.
const vertexBuffer = input.getVertexBuffer(kind)!;
const size = vertexBuffer.getSize();
const data = GetTypedArrayData(
vertexBuffer.getData()!,
size,
vertexBuffer.type,
vertexBuffer.byteOffset,
vertexBuffer.byteStride,
vertexBuffer.normalized,
input.getTotalVertices(),
true
);
attributes.push({ kind: kind, dracoName: GetDracoAttributeName(kind), size: size, data: data });
}

return attributes;
Expand Down Expand Up @@ -234,14 +242,12 @@ export class DracoEncoder extends DracoCodec {
worker.addEventListener("error", onError);
worker.addEventListener("message", onMessage);

// Manually create copies of our attribute data and add them to the transfer list to ensure we only copy the ArrayBuffer data we need.
// Build the transfer list. No need to copy, as the data was copied in previous steps.
const transferList = [];
attributes.forEach((attribute) => {
attribute.data = attribute.data.slice();
transferList.push(attribute.data.buffer);
});
if (indices) {
indices = indices.slice();
transferList.push(indices.buffer);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { VertexDataTypedArray } from "core/Buffers/bufferUtils";
import type { Nullable } from "core/types";

/**
Expand Down Expand Up @@ -56,7 +57,7 @@ export interface IDracoAttributeData {
/**
* The buffer view of the attribute.
*/
data: Float32Array;
data: VertexDataTypedArray;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MeshPrimitiveMode } from "babylonjs-gltf2interface";
import type { IAccessor, IBufferView, IKHRDracoMeshCompression, IMeshPrimitive } from "babylonjs-gltf2interface";
import type { BufferManager } from "../bufferManager";
import { DracoEncoder } from "core/Meshes/Compression/dracoEncoder";
import { GetFloatData, GetTypeByteLength } from "core/Buffers/bufferUtils";
import { GetTypedArrayData, GetTypeByteLength } from "core/Buffers/bufferUtils";
import { GetAccessorElementCount } from "../glTFUtilities";
import type { DracoAttributeName, IDracoAttributeData, IDracoEncoderOptions } from "core/Meshes/Compression/dracoEncoder.types";
import { Logger } from "core/Misc/logger";
Expand Down Expand Up @@ -84,7 +84,7 @@ export class KHR_draco_mesh_compression implements IGLTFExporterExtensionV2 {
const accessor = accessors[primitive.indices];
const bufferView = bufferManager.getBufferView(accessor);
// Per exportIndices, indices must be either Uint16Array or Uint32Array
indices = bufferManager.getData(bufferView) as Uint32Array | Uint16Array;
indices = bufferManager.getData(bufferView).slice() as Uint32Array | Uint16Array;

primitiveBufferViews.push(bufferView);
primitiveAccessors.push(accessor);
Expand All @@ -95,22 +95,20 @@ export class KHR_draco_mesh_compression implements IGLTFExporterExtensionV2 {
for (const [name, accessorIndex] of Object.entries(primitive.attributes)) {
const accessor = accessors[accessorIndex];
const bufferView = bufferManager.getBufferView(accessor);
const data = bufferManager.getData(bufferView);

const size = GetAccessorElementCount(accessor.type);
// TODO: Implement a way to preserve original data type, as Draco can handle more than just floats
// TODO: Add flag in DracoEncoder API to prevent copying data (a second time) to transferable buffer
const floatData = GetFloatData(
data,
const data = GetTypedArrayData(
bufferManager.getData(bufferView),
size,
accessor.componentType,
accessor.byteOffset || 0,
bufferView.byteStride || GetTypeByteLength(accessor.componentType) * size,
accessor.normalized || false,
accessor.count
) as Float32Array; // Because data is a TypedArray, GetFloatData will return a Float32Array
accessor.count,
true
);

attributes.push({ kind: name, dracoName: getDracoAttributeName(name), size: GetAccessorElementCount(accessor.type), data: floatData });
attributes.push({ kind: name, dracoName: getDracoAttributeName(name), size: GetAccessorElementCount(accessor.type), data: data });

primitiveBufferViews.push(bufferView);
primitiveAccessors.push(accessor);
Expand Down

0 comments on commit dbc3d4f

Please sign in to comment.