Skip to content

Commit

Permalink
Merge pull request #562 from CesiumGS/draco-async
Browse files Browse the repository at this point in the history
Load draco asynchronously
  • Loading branch information
lilleyse authored Dec 7, 2020
2 parents 1b74787 + 969fc6e commit 532e5c1
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 49 deletions.
27 changes: 21 additions & 6 deletions lib/compressDracoMeshes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const Cesium = require('cesium');
const draco3d = require('draco3d');
const hashObject = require('object-hash');
const Promise = require('bluebird');
const addBuffer = require('./addBuffer');
const addExtensionsRequired = require('./addExtensionsRequired');
const addExtensionsUsed = require('./addExtensionsUsed');
Expand All @@ -23,8 +24,8 @@ const defined = Cesium.defined;
const RuntimeError = Cesium.RuntimeError;
const WebGLConstants = Cesium.WebGLConstants;

// Prepare encoder for compressing meshes.
const encoderModule = draco3d.createEncoderModule({});
let encoderModulePromise;
let decoderModulePromise;

module.exports = compressDracoMeshes;

Expand All @@ -43,11 +44,25 @@ module.exports = compressDracoMeshes;
* @param {Boolean} [options.dracoOptions.uncompressedFallback=false] If set, add uncompressed fallback versions of the compressed meshes.
* @param {Boolean} [options.dracoOptions.unifiedQuantization=false] Quantize positions, defined by the unified bounding box of all primitives. If not set, quantization is applied separately.
* @param {Object} [options.dracoOptions.quantizationVolume] An AxisAlignedBoundingBox defining the explicit quantization volume.
* @returns {Object} The glTF asset with compressed meshes.
* @returns {Promise} A promise that resolves to the glTF asset with compressed meshes.
*
* @private
*/
function compressDracoMeshes(gltf, options) {
if (!defined(encoderModulePromise)) {
// Prepare encoder for compressing meshes.
encoderModulePromise = Promise.resolve(draco3d.createEncoderModule({}));
decoderModulePromise = Promise.resolve(draco3d.createDecoderModule({}));
}

return encoderModulePromise.then(function(encoderModule) {
return decoderModulePromise.then(function(decoderModule) {
return compress(gltf, options, encoderModule, decoderModule);
});
});
}

function compress(gltf, options, encoderModule, decoderModule) {
options = defaultValue(options, {});
const dracoOptions = defaultValue(options.dracoOptions, {});
const defaults = compressDracoMeshes.defaults;
Expand Down Expand Up @@ -196,7 +211,7 @@ function compressDracoMeshes(gltf, options) {
numberOfPoints: encoder.GetNumberOfEncodedPoints(),
numberOfFaces: encoder.GetNumberOfEncodedFaces()
};
addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues);
addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues, decoderModule);

encoderModule.destroy(encodedDracoDataArray);
encoderModule.destroy(mesh);
Expand Down Expand Up @@ -243,7 +258,7 @@ function addIndices(gltf, primitive) {
primitive.indices = addToArray(gltf.accessors, accessor);
}

function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues) {
function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues, decoderModule) {
if (!uncompressedFallback) {
// Remove properties from accessors.
// Remove indices bufferView.
Expand All @@ -269,7 +284,7 @@ function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, drac
attributes: attributeToId
};

gltf = replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues);
gltf = replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues, decoderModule);
}

function copyCompressedExtensionToPrimitive(primitive, compressedPrimitive) {
Expand Down
10 changes: 4 additions & 6 deletions lib/replaceWithDecompressedPrimitive.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
'use strict';
const Cesium = require('cesium');
const draco3d = require('draco3d');
const addBuffer = require('./addBuffer');
const numberOfComponentsForType = require('./numberOfComponentsForType');

const defined = Cesium.defined;
const RuntimeError = Cesium.RuntimeError;
const WebGLConstants = Cesium.WebGLConstants;

const decoderModule = draco3d.createDecoderModule({});

module.exports = replaceWithDecompressedPrimitive;

/**
Expand All @@ -23,11 +20,12 @@ module.exports = replaceWithDecompressedPrimitive;
* @param {Buffer} dracoEncodedBuffer.buffer A Buffer object containing a Draco compressed mesh.
* @param {Boolean} uncompressedFallback If set, replaces the original with the decompressed data.
* @param {Object} quantizationBitsValues A javascript object containing quantization bits for the different attribute types.
* @param {Object} decoderModule The draco decoder module.
* @returns {Object} The glTF asset with the decompressed primitive.
*
* @private
*/
function replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues) {
function replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, uncompressedFallback, quantizationBitsValues, decoderModule) {
let decoder;
let dracoGeometry;

Expand All @@ -37,7 +35,7 @@ function replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, u

if (uncompressedFallback) {
decoder = new decoderModule.Decoder();
dracoGeometry = decompressDracoBuffer(decoder, dracoEncodedBuffer.buffer);
dracoGeometry = decompressDracoBuffer(decoderModule, decoder, dracoEncodedBuffer.buffer);

const indicesBuffer = getIndicesBuffer(indicesAccessor, decoderModule, decoder, dracoGeometry, dracoEncodedBuffer.numberOfFaces);

Expand Down Expand Up @@ -75,7 +73,7 @@ function replaceWithDecompressedPrimitive(gltf, primitive, dracoEncodedBuffer, u
return gltf;
}

function decompressDracoBuffer(decoder, compressedData) {
function decompressDracoBuffer(decoderModule, decoder, compressedData) {
const source = new Uint8Array(compressedData.buffer);
const dracoBuffer = new decoderModule.DecoderBuffer();
dracoBuffer.Init(source, source.length);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"bluebird": "^3.7.2",
"cesium": "^1.69.0",
"draco3d": "1.3.6",
"draco3d": "^1.4.0",
"fs-extra": "^9.0.0",
"mime": "^2.4.5",
"object-hash": "^2.0.3",
Expand Down
72 changes: 36 additions & 36 deletions specs/lib/compressDracoMeshesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const triangleWithoutIndicesPath = 'specs/data/2.0/triangle-without-indices/tria
let gltf;
let gltfOther;

function expectOutOfRange(gltf, name, value) {
async function expectOutOfRange(gltf, name, value) {
const options = {
dracoOptions: {}
};
options.dracoOptions[name] = value;

let thrownError;
try {
compressDracoMeshes(gltf, options);
await compressDracoMeshes(gltf, options);
} catch (e) {
thrownError = e;
}
Expand All @@ -44,11 +44,11 @@ describe('compressDracoMeshes', () => {
gltfOther = await readGltf(boxPath);
});

it('compresses meshes with default options', () => {
it('compresses meshes with default options', async () => {
expect(gltf.accessors.length).toBe(4); // 3 attributes + indices
expect(gltf.bufferViews.length).toBe(4); // position/normal + texcoord + indices + image

compressDracoMeshes(gltf);
await compressDracoMeshes(gltf);

expect(gltf.accessors.length).toBe(4); // accessors are not removed
expect(gltf.bufferViews.length).toBe(2); // draco + image
Expand All @@ -75,7 +75,7 @@ describe('compressDracoMeshes', () => {
expect(gltf.accessors.length).toBe(1); // positions
expect(gltf.bufferViews.length).toBe(1); // positions

compressDracoMeshes(gltf);
await compressDracoMeshes(gltf);

expect(gltf.accessors.length).toBe(2); // positions + indices
expect(gltf.bufferViews.length).toBe(1); // draco
Expand All @@ -90,30 +90,30 @@ describe('compressDracoMeshes', () => {
expect(positionAccessor.byteLength).toBeUndefined();
});

it('throws if quantize bits is out of range', () => {
expectOutOfRange(gltf, 'compressionLevel', -1);
expectOutOfRange(gltf, 'compressionLevel', 11);
expectOutOfRange(gltf, 'quantizePositionBits', -1);
expectOutOfRange(gltf, 'quantizePositionBits', 31);
expectOutOfRange(gltf, 'quantizeNormalBits', -1);
expectOutOfRange(gltf, 'quantizeNormalBits', 31);
expectOutOfRange(gltf, 'quantizeTexcoordBits', -1);
expectOutOfRange(gltf, 'quantizeTexcoordBits', 31);
expectOutOfRange(gltf, 'quantizeColorBits', -1);
expectOutOfRange(gltf, 'quantizeColorBits', 31);
expectOutOfRange(gltf, 'quantizeGenericBits', -1);
expectOutOfRange(gltf, 'quantizeGenericBits', 31);
it('throws if quantize bits is out of range', async () => {
await expectOutOfRange(gltf, 'compressionLevel', -1);
await expectOutOfRange(gltf, 'compressionLevel', 11);
await expectOutOfRange(gltf, 'quantizePositionBits', -1);
await expectOutOfRange(gltf, 'quantizePositionBits', 31);
await expectOutOfRange(gltf, 'quantizeNormalBits', -1);
await expectOutOfRange(gltf, 'quantizeNormalBits', 31);
await expectOutOfRange(gltf, 'quantizeTexcoordBits', -1);
await expectOutOfRange(gltf, 'quantizeTexcoordBits', 31);
await expectOutOfRange(gltf, 'quantizeColorBits', -1);
await expectOutOfRange(gltf, 'quantizeColorBits', 31);
await expectOutOfRange(gltf, 'quantizeGenericBits', -1);
await expectOutOfRange(gltf, 'quantizeGenericBits', 31);
});

it('applies unified quantization', async () => {
const gltfUnified = await readGltf(multipleBoxesPath);
const gltfNonUnified = await readGltf(multipleBoxesPath);
compressDracoMeshes(gltfUnified, {
await compressDracoMeshes(gltfUnified, {
dracoOptions: {
unifiedQuantization: true
}
});
compressDracoMeshes(gltfNonUnified, {
await compressDracoMeshes(gltfNonUnified, {
dracoOptions: {
unifiedQuantization: false
}
Expand All @@ -128,17 +128,17 @@ describe('compressDracoMeshes', () => {
const gltfUnified = await readGltf(multipleBoxesPath);
const gltfDefault = await readGltf(multipleBoxesPath);
const aabb = new AxisAlignedBoundingBox(new Cartesian3(-10.0, -10.0, -10.0), new Cartesian3(10.0, 10.0, 10.0));
compressDracoMeshes(gltfVolume, {
await compressDracoMeshes(gltfVolume, {
dracoOptions: {
quantizationVolume: aabb
}
});
compressDracoMeshes(gltfUnified, {
await compressDracoMeshes(gltfUnified, {
dracoOptions: {
unifiedQuantization: true
}
});
compressDracoMeshes(gltfDefault, {
await compressDracoMeshes(gltfDefault, {
dracoOptions: {}
});
const dracoBufferVolume = getDracoBuffer(gltfVolume);
Expand All @@ -148,13 +148,13 @@ describe('compressDracoMeshes', () => {
expect(dracoBufferVolume).not.toEqual(dracoBufferUnified);
});

it('applies quantization bits', () => {
compressDracoMeshes(gltf, {
it('applies quantization bits', async () => {
await compressDracoMeshes(gltf, {
dracoOptions: {
quantizePositionBits: 8
}
});
compressDracoMeshes(gltfOther, {
await compressDracoMeshes(gltfOther, {
dracoOptions: {
quantizePositionBits: 25
}
Expand All @@ -165,8 +165,8 @@ describe('compressDracoMeshes', () => {
expect(dracoBuffer8.length).toBeLessThan(dracoBuffer14.length);
});

it('does not quantize when quantize bits is 0', () => {
compressDracoMeshes(gltf, {
it('does not quantize when quantize bits is 0', async () => {
await compressDracoMeshes(gltf, {
dracoOptions: {
quantizePositionBits: 0,
quantizeNormalBits: 0,
Expand All @@ -175,16 +175,16 @@ describe('compressDracoMeshes', () => {
quantizeGenericBits: 0
}
});
compressDracoMeshes(gltfOther);
await compressDracoMeshes(gltfOther);
const dracoBufferUncompressed = getDracoBuffer(gltf);
const dracoBufferCompressed = getDracoBuffer(gltfOther);
expect(dracoBufferCompressed.length).toBeLessThan(dracoBufferUncompressed.length);
});

it('only compresses duplicate primitive once', () => {
it('only compresses duplicate primitive once', async () => {
const primitives = gltf.meshes[0].primitives;
primitives.push(clone(primitives[0], true));
compressDracoMeshes(gltf);
await compressDracoMeshes(gltf);
expect(primitives[0]).toEqual(primitives[1]);
});

Expand All @@ -200,20 +200,20 @@ describe('compressDracoMeshes', () => {
const gltfMorph = await readGltf(boxMorphPath);
const gltfNoMorph = removeMorphTargets(await readGltf(boxMorphPath));

compressDracoMeshes(gltfMorph);
compressDracoMeshes(gltfNoMorph);
await compressDracoMeshes(gltfMorph);
await compressDracoMeshes(gltfNoMorph);
const dracoBufferMorph = getDracoBuffer(gltfMorph);
const dracoBufferNoMorph = getDracoBuffer(gltfNoMorph);
expect(dracoBufferMorph).not.toEqual(dracoBufferNoMorph);
});

it('applies uncompressed fallback', () => {
compressDracoMeshes(gltf, {
it('applies uncompressed fallback', async () => {
await compressDracoMeshes(gltf, {
dracoOptions: {
uncompressedFallback: true
}
});
compressDracoMeshes(gltfOther, {
await compressDracoMeshes(gltfOther, {
dracoOptions: {
uncompressedFallback: false
}
Expand Down

0 comments on commit 532e5c1

Please sign in to comment.