-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Draco glTF models #6191
Changes from 11 commits
7f27631
253f397
c983d8c
9d039a9
dce83b7
f46a6c1
b1593ee
0ef63a8
147ea11
f516ed7
c997b4e
f2f38b1
a727dc6
395f0c7
bdf8078
166a669
f860422
b37b146
a25fb07
d37b7c7
feeb6dc
4bc5336
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ define([ | |
'../Core/clone', | ||
'../Core/Color', | ||
'../Core/combine', | ||
'../Core/ComponentDatatype', | ||
'../Core/defaultValue', | ||
'../Core/defined', | ||
'../Core/defineProperties', | ||
|
@@ -34,6 +35,7 @@ define([ | |
'../Core/Quaternion', | ||
'../Core/Queue', | ||
'../Core/RuntimeError', | ||
'../Core/TaskProcessor', | ||
'../Core/Transforms', | ||
'../Core/WebGLConstants', | ||
'../Renderer/Buffer', | ||
|
@@ -84,6 +86,7 @@ define([ | |
clone, | ||
Color, | ||
combine, | ||
ComponentDatatype, | ||
defaultValue, | ||
defined, | ||
defineProperties, | ||
|
@@ -110,6 +113,7 @@ define([ | |
Quaternion, | ||
Queue, | ||
RuntimeError, | ||
TaskProcessor, | ||
Transforms, | ||
WebGLConstants, | ||
Buffer, | ||
|
@@ -616,6 +620,7 @@ define([ | |
this._cachedRendererResources = undefined; | ||
this._loadRendererResourcesFromCache = false; | ||
this._updatedGltfVersion = false; | ||
this._decodedData = {}; | ||
|
||
this._cachedGeometryByteLength = 0; | ||
this._cachedTexturesByteLength = 0; | ||
|
@@ -1369,8 +1374,11 @@ define([ | |
// through glTF accessors to create the bufferview's index buffer. | ||
ForEach.accessor(model.gltf, function(accessor) { | ||
var bufferViewId = accessor.bufferView; | ||
var bufferView = bufferViews[bufferViewId]; | ||
if (!defined(bufferViewId)) { | ||
return; | ||
} | ||
|
||
var bufferView = bufferViews[bufferViewId]; | ||
if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) { | ||
indexBufferIds[bufferViewId] = true; | ||
indexBuffersToCreate.enqueue({ | ||
|
@@ -2387,6 +2395,7 @@ define([ | |
|
||
for (var i = 0; i < primitivesLength; ++i) { | ||
var primitive = primitives[i]; | ||
var decodedData = model._decodedData[meshId + '.primitive.' + i]; | ||
|
||
// GLTF_SPEC: This does not take into account attribute arrays, | ||
// indicated by when an attribute points to a parameter with a | ||
|
@@ -2407,6 +2416,26 @@ define([ | |
// with an attribute that wasn't used and the asset wasn't optimized. | ||
if (defined(attributeLocation)) { | ||
var a = accessors[primitiveAttributes[attributeName]]; | ||
|
||
// Use decoded draco attributes if available | ||
if (defined(decodedData)) { | ||
var decodedAttributes = decodedData.attributes; | ||
if (decodedAttributes.hasOwnProperty(attributeName)) { | ||
var decodedAttribute = decodedAttributes[attributeName]; | ||
attributes.push({ | ||
index : attributeLocation, | ||
vertexBuffer : rendererBuffers[decodedAttribute.bufferView], | ||
componentsPerAttribute : decodedAttribute.componentsPerAttribute, | ||
componentDatatype : decodedAttribute.componentDatatype, | ||
normalize: decodedAttribute.normalized, | ||
offsetInBytes : decodedAttribute.byteOffset, | ||
strideInBytes : decodedAttribute.byteStride | ||
}); | ||
|
||
continue; | ||
} | ||
} | ||
|
||
var normalize = false; | ||
if (defined(a.normalized) && a.normalized) { | ||
normalize = true; | ||
|
@@ -2443,7 +2472,14 @@ define([ | |
var indexBuffer; | ||
if (defined(primitive.indices)) { | ||
var accessor = accessors[primitive.indices]; | ||
indexBuffer = rendererBuffers[accessor.bufferView]; | ||
var bufferView = accessor.bufferView; | ||
|
||
// Used decoded draco buffer if available | ||
if (defined(decodedData)) { | ||
bufferView = decodedData.bufferView; | ||
} | ||
|
||
indexBuffer = rendererBuffers[bufferView]; | ||
} | ||
rendererVertexArrays[meshId + '.primitive.' + i] = new VertexArray({ | ||
context : context, | ||
|
@@ -4009,6 +4045,151 @@ define([ | |
return (distance2 >= nearSquared) && (distance2 <= farSquared); | ||
} | ||
|
||
/////////////////////////////////////////////////////////////////////////// | ||
|
||
function addBufferToModelResources(model, buffer) { | ||
var resourceBuffers = model._rendererResources.buffers; | ||
var id = Object.keys(resourceBuffers).length; | ||
resourceBuffers[id] = buffer; | ||
model._geometryByteLength += buffer.sizeInBytes; | ||
|
||
return id; | ||
} | ||
|
||
function addNewVertexBuffer(typedArray, model, context) { | ||
var vertexBuffer = Buffer.createVertexBuffer({ | ||
context : context, | ||
typedArray : typedArray, | ||
usage : BufferUsage.STATIC_DRAW | ||
}); | ||
vertexBuffer.vertexArrayDestroyable = false; | ||
|
||
return addBufferToModelResources(model, vertexBuffer); | ||
} | ||
|
||
function addNewIndexBuffer(typedArray, model, context) { | ||
var indexBuffer = Buffer.createIndexBuffer({ | ||
context : context, | ||
typedArray : typedArray, | ||
usage : BufferUsage.STATIC_DRAW, | ||
indexDatatype : ComponentDatatype.fromTypedArray(typedArray) | ||
}); | ||
indexBuffer.vertexArrayDestroyable = false; | ||
|
||
return addBufferToModelResources(model, indexBuffer); | ||
} | ||
|
||
function addDecodededBuffers(primitive, model, context) { | ||
return function (result) { | ||
var decodedBufferView = addNewIndexBuffer(result.indexArray, model, context); | ||
|
||
var attributes = {}; | ||
var decodedAttributeData = result.attributeData; | ||
for (var attributeName in decodedAttributeData) { | ||
if (decodedAttributeData.hasOwnProperty(attributeName)) { | ||
var attribute = decodedAttributeData[attributeName]; | ||
var vertexArray = attribute.array; | ||
var vertexBufferView = addNewVertexBuffer(vertexArray, model, context); | ||
|
||
var data = attribute.data; | ||
data.bufferView = vertexBufferView; | ||
|
||
attributes[attributeName] = data; | ||
} | ||
} | ||
|
||
model._decodedData[primitive.mesh + '.primitive.' + primitive.primitive] = { | ||
bufferView : decodedBufferView, | ||
attributes : attributes | ||
}; | ||
}; | ||
} | ||
|
||
function parseDraco(model, context) { | ||
if (!defined(model.extensionsRequired['KHR_draco_mesh_compression']) | ||
|| !defined(model.extensionsUsed['KHR_draco_mesh_compression'])) { | ||
return; | ||
} | ||
|
||
var loadResources = model._loadResources; | ||
if (loadResources.primitivesToDecode.length === 0) { | ||
if (loadResources.decoding) { | ||
// Done decoding | ||
return; | ||
} | ||
|
||
loadResources.decoding = true; | ||
|
||
var gltf = model.gltf; | ||
ForEach.mesh(gltf, function(mesh, meshId) { | ||
ForEach.meshPrimitive(mesh, function(primitive, primitiveId) { | ||
if (!defined(primitive.extensions)) { | ||
return; | ||
} | ||
|
||
var compressionData = primitive.extensions['KHR_draco_mesh_compression']; | ||
if (!defined(compressionData)) { | ||
return; | ||
} | ||
|
||
var bufferView = gltf.bufferViews[compressionData.bufferView]; | ||
var rawBuffer = gltf.buffers[bufferView.buffer]; | ||
var data = rawBuffer.extras._pipeline.source; | ||
data = data.slice(0, data.length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to slice only the section of the buffer that Draco uses? |
||
|
||
loadResources.primitivesToDecode.enqueue({ | ||
mesh : meshId, | ||
primitive : primitiveId, | ||
array : data, | ||
bufferView : bufferView, | ||
compressedAttributes : compressionData.attributes | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
var decoderTaskProcessor = Model._getDecoderTaskProcessor(); | ||
var taskData = loadResources.primitivesToDecode.peek(); | ||
var decodingPromises = []; | ||
var promise; | ||
|
||
if (defined(taskData)) { | ||
promise = decoderTaskProcessor.scheduleTask(taskData, [taskData.array.buffer]); | ||
} | ||
|
||
while (defined(promise)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it simplify the code to loop over With that change the code below may only need to be called in one place.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated this to be less redundant, but I'm hesitant to iterate through all of the primitives, as we don't necessarily iterate through all the primitives in the queue each frame. It depends on how many task can be scheduled as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, good point. |
||
loadResources.finishedDecoding = false; | ||
loadResources.primitivesToDecode.dequeue(); | ||
var decodedPromise = promise.then(addDecodededBuffers(taskData, model, context)) | ||
.otherwise(function (error) { | ||
model._state = ModelState.FAILED; | ||
model._readyPromise.reject(new RuntimeError('Failed to load model: ' + model.basePath + '\n' + error.message)); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|
||
decodingPromises.push(decodedPromise); | ||
|
||
promise = undefined; | ||
taskData = loadResources.primitivesToDecode.peek(); | ||
if (defined(taskData)) { | ||
promise = decoderTaskProcessor.scheduleTask(taskData, [taskData.array.buffer]); | ||
} | ||
} | ||
|
||
when.all(decodingPromises).then(function () { | ||
loadResources.finishedDecoding = true; | ||
}); | ||
} | ||
|
||
Model._maxDecodingConcurrency = Math.max(FeatureDetection.hardwareConcurrency - 1, 1); // Maximum concurrency to use wehn deocding draco models | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typos in comment. |
||
Model._decoderTaskProcessor = undefined; | ||
Model._getDecoderTaskProcessor = function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might want to add a comment that this is exposed for testing purposes. |
||
if (!defined(Model._decoderTaskProcessor)) { | ||
Model._decoderTaskProcessor = new TaskProcessor('decodeDraco', Model._maxDecodingConcurrency); | ||
} | ||
|
||
return Model._decoderTaskProcessor; | ||
}; | ||
|
||
/** | ||
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to | ||
* get the draw commands needed to render this primitive. | ||
|
@@ -4094,38 +4275,50 @@ define([ | |
// Textures may continue to stream in while in the LOADED state. | ||
if (loadResources.pendingBufferLoads === 0) { | ||
if (!this._updatedGltfVersion) { | ||
var options = { | ||
optimizeForCesium: true, | ||
addBatchIdToGeneratedShaders : this._addBatchIdToGeneratedShaders | ||
}; | ||
frameState.brdfLutGenerator.update(frameState); | ||
updateVersion(this.gltf); | ||
ModelUtility.checkSupportedExtensions(this.extensionsRequired); | ||
addPipelineExtras(this.gltf); | ||
addDefaults(this.gltf); | ||
processModelMaterialsCommon(this.gltf, options); | ||
processPbrMetallicRoughness(this.gltf, options); | ||
// We do this after to make sure that the ids don't change | ||
addBuffersToLoadResources(this); | ||
|
||
if (!this._loadRendererResourcesFromCache) { | ||
parseBufferViews(this); | ||
parseShaders(this); | ||
parsePrograms(this); | ||
parseTextures(this, context); | ||
if (loadResources.decoding) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interaction of gltf-pipeline updgrading, resource parsing, and draco loading is getting a bit confusing. They should be separated in a cleaner way. Also I realize some of the confusion was already here from before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restructured, let me know if that's similar to what you had in mind. |
||
parseDraco(this, context); | ||
} else { | ||
var options = { | ||
optimizeForCesium: true, | ||
addBatchIdToGeneratedShaders : this._addBatchIdToGeneratedShaders | ||
}; | ||
frameState.brdfLutGenerator.update(frameState); | ||
updateVersion(this.gltf); | ||
ModelUtility.checkSupportedExtensions(this.extensionsRequired); | ||
addPipelineExtras(this.gltf); | ||
addDefaults(this.gltf); | ||
processModelMaterialsCommon(this.gltf, options); | ||
processPbrMetallicRoughness(this.gltf, options); | ||
|
||
parseDraco(this, context); | ||
} | ||
parseMaterials(this); | ||
parseMeshes(this); | ||
parseNodes(this); | ||
|
||
this._boundingSphere = computeBoundingSphere(this); | ||
this._initialRadius = this._boundingSphere.radius; | ||
this._updatedGltfVersion = true; | ||
// We must wait until the geometry is decoded | ||
if (loadResources.decodingComplete()) { | ||
// We do this after to make sure that the ids don't change | ||
addBuffersToLoadResources(this); | ||
|
||
if (!this._loadRendererResourcesFromCache) { | ||
parseBufferViews(this); | ||
parseShaders(this); | ||
parsePrograms(this); | ||
parseTextures(this, context); | ||
} | ||
parseMaterials(this); | ||
parseMeshes(this); | ||
parseNodes(this); | ||
|
||
this._boundingSphere = computeBoundingSphere(this); | ||
this._initialRadius = this._boundingSphere.radius; | ||
this._updatedGltfVersion = true; | ||
} | ||
} | ||
|
||
if (this._updatedGltfVersion && loadResources.pendingShaderLoads === 0) { | ||
createResources(this, frameState); | ||
} | ||
} | ||
|
||
if (loadResources.finished() || | ||
(incrementallyLoadTextures && loadResources.finishedEverythingButTextureCreation())) { | ||
this._state = ModelState.LOADED; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
arraySlice
instead.