-
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
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
7f27631
Decode draco meshes, include sample models
ggetz 253f397
Merge branch 'master' into draco
ggetz c983d8c
Update CHANGES.md
ggetz 9d039a9
Added draco loading spec
ggetz dce83b7
Use uint16 for indices
ggetz f46a6c1
Draco cleanup
ggetz b1593ee
More draco tests
ggetz 0ef63a8
Modification for updated models
ggetz 147ea11
Move draco decoding to web worker, decode in paralell
ggetz f516ed7
Merge branch 'master' into draco
ggetz c997b4e
Updated Specs
ggetz f2f38b1
Remove fit spec
ggetz a727dc6
Properly exlcude draco decodder module from main
ggetz 395f0c7
Cleanup draco model loading
ggetz bdf8078
Model load cleanup
ggetz 166a669
Exclude other worker third party modules from source
ggetz f860422
Clenaup specs
ggetz b37b146
Merge remote-tracking branch 'origin/master' into draco
ggetz a25fb07
Add comment, cleanup
ggetz d37b7c7
Fix race condition
ggetz feeb6dc
Use decoded indecies count instead of accessor count
ggetz 4bc5336
Fix formatting, update comments
ggetz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
define([ | ||
'../Core/arraySlice', | ||
'../Core/ComponentDatatype', | ||
'../Core/defined', | ||
'../Core/FeatureDetection', | ||
'../Core/TaskProcessor', | ||
'../Renderer/Buffer', | ||
'../Renderer/BufferUsage', | ||
'../ThirdParty/GltfPipeline/ForEach', | ||
'../ThirdParty/when' | ||
], function( | ||
arraySlice, | ||
ComponentDatatype, | ||
defined, | ||
FeatureDetection, | ||
TaskProcessor, | ||
Buffer, | ||
BufferUsage, | ||
ForEach, | ||
when) { | ||
'use strict'; | ||
|
||
/** | ||
* @private | ||
*/ | ||
function DracoLoader() {} | ||
|
||
// Maximum concurrency to use when deocding draco models | ||
DracoLoader._maxDecodingConcurrency = Math.max(FeatureDetection.hardwareConcurrency - 1, 1); | ||
|
||
// Exposed for testing purposes | ||
DracoLoader._decoderTaskProcessor = undefined; | ||
DracoLoader._getDecoderTaskProcessor = function () { | ||
if (!defined(DracoLoader._decoderTaskProcessor)) { | ||
DracoLoader._decoderTaskProcessor = new TaskProcessor('decodeDraco', DracoLoader._maxDecodingConcurrency); | ||
} | ||
|
||
return DracoLoader._decoderTaskProcessor; | ||
}; | ||
|
||
function hasExtension(model) { | ||
return (defined(model.extensionsRequired.KHR_draco_mesh_compression) | ||
|| defined(model.extensionsUsed.KHR_draco_mesh_compression)); | ||
} | ||
|
||
function addBufferToModelResources(model, buffer) { | ||
var resourceBuffers = model._rendererResources.buffers; | ||
var bufferViewId = Object.keys(resourceBuffers).length; | ||
resourceBuffers[bufferViewId] = buffer; | ||
model._geometryByteLength += buffer.sizeInBytes; | ||
|
||
return bufferViewId; | ||
} | ||
|
||
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; | ||
|
||
var bufferViewId = addBufferToModelResources(model, indexBuffer); | ||
return { | ||
bufferViewId: bufferViewId, | ||
numberOfIndices : indexBuffer.numberOfIndices | ||
}; | ||
} | ||
|
||
function addDecodededBuffers(primitive, model, context) { | ||
return function (result) { | ||
var decodedIndexBuffer = 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 : decodedIndexBuffer.bufferViewId, | ||
numberOfIndices : decodedIndexBuffer.numberOfIndices, | ||
attributes : attributes | ||
}; | ||
}; | ||
} | ||
|
||
function scheduleDecodingTask(decoderTaskProcessor, model, loadResources, context) { | ||
var taskData = loadResources.primitivesToDecode.peek(); | ||
if (!defined(taskData)) { | ||
// All primitives are processing | ||
return; | ||
} | ||
|
||
var promise = decoderTaskProcessor.scheduleTask(taskData, [taskData.array.buffer]); | ||
if (!defined(promise)) { | ||
// Cannot schedule another task this frame | ||
return; | ||
} | ||
|
||
loadResources.primitivesToDecode.dequeue(); | ||
return promise.then(addDecodededBuffers(taskData, model, context)); | ||
} | ||
|
||
/** | ||
* Parses draco extension on model primitives and | ||
* adds the decoding data to the model's load resources. | ||
* | ||
* @private | ||
*/ | ||
DracoLoader.parse = function(model) { | ||
if (!hasExtension(model)) { | ||
return; | ||
} | ||
|
||
var loadResources = model._loadResources; | ||
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 typedArray = arraySlice(gltf.buffers[bufferView.buffer].extras._pipeline.source, bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength); | ||
|
||
loadResources.primitivesToDecode.enqueue({ | ||
mesh : meshId, | ||
primitive : primitiveId, | ||
array : typedArray, | ||
bufferView : bufferView, | ||
compressedAttributes : compressionData.attributes | ||
}); | ||
}); | ||
}); | ||
}; | ||
|
||
/** | ||
* Schedules decoding tasks available this frame. | ||
* @private | ||
*/ | ||
DracoLoader.decode = function(model, context) { | ||
if (!hasExtension(model)) { | ||
return when.resolve(); | ||
} | ||
|
||
var loadResources = model._loadResources; | ||
if (loadResources.primitivesToDecode.length === 0) { | ||
// No more tasks to schedule | ||
return when.resolve(); | ||
} | ||
|
||
var decoderTaskProcessor = DracoLoader._getDecoderTaskProcessor(); | ||
var decodingPromises = []; | ||
|
||
var promise = scheduleDecodingTask(decoderTaskProcessor, model, loadResources, context); | ||
while (defined(promise)) { | ||
decodingPromises.push(promise); | ||
promise = scheduleDecodingTask(decoderTaskProcessor, model, loadResources, context); | ||
} | ||
|
||
return when.all(decodingPromises).then(function () { | ||
// Done decoding when there are no more active tasks | ||
loadResources.decoding = (decoderTaskProcessor._activeTasks !== 0); | ||
}); | ||
}; | ||
|
||
return DracoLoader; | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Generally Cesium only calls GL functions in the render loop. Is that the case here and below? If not, is the worth the rework to change?
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.
It looks like this is the case.
@ggetz for this you could try integrating the draco loading with
createBuffers
which manages buffer creation from typed arrays.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.
I've made changes in #6341 that ensure the buffers are being created during
Model.update
, does that cover this? Or would it be better to integrate it intocreateBuffers
?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.
I think it would be better, only for the sake of utilizing the
JobScheduler
. If it's easy to integrate I would try going for it.However I did look at the approach in #6341 and it seems solid too.