-
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
Conversation
@ggetz, thanks for the pull request! Maintainers, we have a signed CLA from @ggetz, so you can review this at any time.
I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome. 🌍 🌎 🌏 |
Test is failing on Travis because of the following:
I use an an unsigned int for the index array, should I be using something else? |
Exciting! @ggetz please update this tasklist: #5120 (comment) @lilleyse bump to me for a quick once over when you think this is ready. |
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.
The implementation looks really clean and self-contained - nice job!
Besides just loading the models to see if they worked I tested creating two of the same models to see how the new code interacted with the resource cache. Everything seems to be working fine.
Source/Scene/Model.js
Outdated
@@ -158,6 +164,8 @@ define([ | |||
return {}; | |||
} | |||
|
|||
var dracoDecoder = new draco.Decoder(); |
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 wonder if this should be initialized on demand in case this is a heavy object to add to the global state.
Source/Scene/Model.js
Outdated
var typedArray = arraySlice(rawBuffer.extras._pipeline.source, bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength); | ||
|
||
buffer = new draco.DecoderBuffer(); | ||
buffer.Init(new Int8Array(typedArray), bufferView.byteLength); |
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.
Does Draco require an Int8Array
instead of a Uint8Array
? We should be careful because constructing a typed array from another typed array will copy the data, which we should try to avoid.
Source/Scene/Model.js
Outdated
throw new RuntimeError('Unsupported draco mesh geometry type.'); | ||
} | ||
|
||
dracoGeometry = new draco.Mesh(); |
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.
This one is not getting destroyed later. It looks like the rest of the draco objects are good though.
Source/Scene/Model.js
Outdated
@@ -4076,7 +4076,7 @@ define([ | |||
context : context, | |||
typedArray : typedArray, | |||
usage : BufferUsage.STATIC_DRAW, | |||
indexDatatype : IndexDatatype.UNSIGNED_INT | |||
indexDatatype : IndexDatatype.UNSIGNED_SHORT |
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.
We should test a model that has more than 65536 vertices. It will likely fail if UNSIGNED_SHORT
is hardcoded. Could the index datatype be determined when reading then number of vertices in the compressed data?
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.
Fixed this so it uses UNSIGNED_INT
for more vertices. Do you know where I could find a model to test?
@lilleyse Updated. See #6191 (comment) |
The new changes look good and the large model test worked. @pjcozzi this is ready for you to look at. |
|
Coverage is good other than the error handling, I'll add test cases for those.
Yes, tested a subsection, I can test them all if needed?
I'll profile, but it shouldn't be too hard to move to a web worker since it's typed array in, typed array out.
ThreeJS allows you to set the path of the module and load it at runtime, see here |
Absolutely. They don't need to be submitted to the repo, just tested manually is OK unless they are needed for coverage. |
👍 for this approach. As @ggetz already mention, this should be a trivial change so it ca happen before this goes into master. Should also be faster too because we'll be decompressing multiple meshes in parallel. EDIT: I was too curious not to check, final gzippped files size of Cesium.js went up 18% compared to master (722 KB to 865 KB). So that alone is reason enough to move it to a work and avoid loading it unless needed. |
CC @cx20 - this is the PR to watch for Draco support in Cesium. |
@lilleyse Updated! The decoder module is now only included when a web worker is launched to decode a model. The decoding is concurrent, based on hardware limits, and parallel per primitive. All the example models have been tested. Is there a way to test the web workers directly? I'm testing the draco decoding one through |
Specs/Scene/ModelSpec.js
Outdated
}); | ||
}); | ||
|
||
fit('error decoding a glTF causes model loading to fail', function() { |
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.
Accidentally left fit
in here
Source/Scene/Model.js
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Use arraySlice
instead.
Source/Scene/Model.js
Outdated
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 comment
The 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?
Source/Scene/Model.js
Outdated
parseShaders(this); | ||
parsePrograms(this); | ||
parseTextures(this, context); | ||
if (loadResources.decoding) { |
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.
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 comment
The 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.
Source/Workers/decodeDraco.js
Outdated
return decodedAttributeData; | ||
} | ||
|
||
function decodeDracoPrimitive(parameters, transferableObjects) { |
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.
transferableObjects
isn't used.
Source/Scene/Model.js
Outdated
}); | ||
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Typos in comment.
Source/Scene/Model.js
Outdated
promise = decoderTaskProcessor.scheduleTask(taskData, [taskData.array.buffer]); | ||
} | ||
|
||
while (defined(promise)) { |
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.
Would it simplify the code to loop over loadResources.primitivesToDecode.length
?
With that change the code below may only need to be called in one place.
promise = undefined;
taskData = loadResources.primitivesToDecode.peek();
if (defined(taskData)) {
promise = decoderTaskProcessor.scheduleTask(taskData, [taskData.array.buffer]);
}
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 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point.
Source/Scene/Model.js
Outdated
|
||
Model._maxDecodingConcurrency = Math.max(FeatureDetection.hardwareConcurrency - 1, 1); // Maximum concurrency to use wehn deocding draco models | ||
Model._decoderTaskProcessor = undefined; | ||
Model._getDecoderTaskProcessor = function () { |
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.
You might want to add a comment that this is exposed for testing purposes.
Source/Workers/decodeDraco.js
Outdated
|
||
if (attribute.data_type() === 4) { | ||
attributeData = new draco.DracoInt32Array(); | ||
vertexArray = new Uint16Array(numPoints * numComponents); |
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.
Do we have information about the max value in the DracoInt32Array
? Ideally vertexArray
should be either a Uint16Array
or Uint32Array
depending on the max value.
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.
This value isn't available through the draco API. We can only get it if we start iterating through and getting the values. Is it worth stopping and remaking the array if it should be Uint32
?
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's probably safe to just use uint16
then. I can't think of an integer attribute that commonly has elements above 65535. Leave a comment though explaining why a Uint16Array
is used and the potential problem.
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.
OK, will do. Int
attributes were rarely used, I don't think the glTF example models use them at all.
Source/Workers/decodeDraco.js
Outdated
dracoDecoder.GetAttributeFloatForAllPoints(dracoGeometry, attribute, attributeData); | ||
} | ||
|
||
for (var i = 0; i < vertexArray.length; ++i) { |
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.
Cesium style note: factor out vertexArray.length
to its own variable.
Specs/Scene/ModelSpec.js
Outdated
@@ -123,6 +123,9 @@ defineSuite([ | |||
var animatedMorphCubeUrl = './Data/Models/PBR/AnimatedMorphCube/AnimatedMorphCube.gltf'; | |||
var twoSidedPlaneUrl = './Data/Models/PBR/TwoSidedPlane/TwoSidedPlane.gltf'; | |||
var vertexColorTestUrl = './Data/Models/PBR/VertexColorTest/VertexColorTest.gltf'; | |||
var dracoCompressedModelUrl = './Data/Models/DracoCompression/CesiumMilkTruck/CesiumMilkTruck.gltf'; | |||
var dracoCompressedModelWithAnimationUrl = './Data/Models/DracoCompression/CesiumMan/CesiumMan.gltf'; | |||
var dracoCompressedModelWithErrorUrl = './Data/Models/DracoCompression/ShouldError/CesiumMilkTruck.gltf'; |
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.
Is ShouldError/CesiumMilkTruck.gltf
different than CesiumMilkTruck/CesiumMilkTruck.gltf
? If not it can be removed.
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.
For #6191 (comment) and #6191 (comment), the new restructuring looks great.
Source/Scene/DracoLoader.js
Outdated
ForEach, | ||
when | ||
) { | ||
'use strict'; |
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 slightly different formatting here:
when) {
'use strict';
Source/Scene/DracoLoader.js
Outdated
}; | ||
} | ||
|
||
function scheduleDecodingTask (decoderTaskProcessor, model, loadResources, context) { |
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.
Remove space: function scheduleDecodingTask(
Source/Scene/DracoLoader.js
Outdated
} | ||
|
||
var bufferView = gltf.bufferViews[compressionData.bufferView]; | ||
//console.log(gltf.buffers[bufferView.buffer].extras._pipeline); |
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.
Remove comment.
Source/Workers/decodeDraco.js
Outdated
|
||
if (attribute.data_type() === 4) { | ||
attributeData = new draco.DracoInt32Array(); | ||
vertexArray = new Uint16Array(numPoints * numComponents); |
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's probably safe to just use uint16
then. I can't think of an integer attribute that commonly has elements above 65535. Leave a comment though explaining why a Uint16Array
is used and the potential problem.
Is |
I have another branch with the support that I will open in another PR - I need to confirm the test models and I don't want to slow down this one. |
Thanks @lilleyse ! Addressed your remaining comments. |
@lilleyse Fixed a race condition that was causing this error, and tested these models and a few others. |
@lilleyse Sorry, missed that since the model had partially loaded. Updated and loading correctly now! |
FYI, do to the build issue @ggetz found (and fixed) regarding including things that were only used on workers (and now that draco is only used on a worker), this PR actually shrinks the size of minified/gzipped Cesium.js by about ~40KB compared to mater! Awesome. |
Source/Scene/Model.js
Outdated
offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices | ||
} | ||
else { | ||
var positions = accessors[primitive.attributes.POSITION]; | ||
count = positions.count; | ||
offset = 0; | ||
} | ||
} |
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.
Fix formatting.
Source/Scene/DracoLoader.js
Outdated
var bufferViewId = addBufferToModelResources(model, indexBuffer); | ||
return { | ||
bufferViewId: bufferViewId, | ||
numberOfIndices : indexBuffer.numberOfIndices |
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.
Since numberOfIndices
can get gotten from bufferViewId
(though indirectly), it may be fine to not store numberOfIndices
anywhere.
Up to you though, it doesn't matter much to me.
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 prefer getting all the needed data in one place, then just accessing it later. I did update comments to closely mirror the wording in the spec:
When loading each accessor, you must ignore the bufferView and go to the previously decoded Draco geometry in the primitive to get the data of indices and attributes.
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.
Sounds good.
Thanks @ggetz, can't wait to start using this with our data. |
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.
@ggetz looks pretty clean at quick glance. I would suggest updating the Sandcastle example with a "Load Draco compressed model" even if the code is exactly the same as the others with a comment pointing to https://github.com/google/draco or whatever content pipeline they need until ion does Draco compression.
The reference doc should also be updated to say that Model
supports the Draco extension (and any other non-draft extensions that might have slipped through the cracks): https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Model.js#L1073
I won't have bandwidth for another review so please iterate with @lilleyse and ping me if you need something specific.
Also looking forward to the blog on this!
} | ||
|
||
function addNewVertexBuffer(typedArray, model, context) { | ||
var vertexBuffer = Buffer.createVertexBuffer({ |
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 into createBuffers
?
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.
for (var i = 0; i < numFaces; ++i) { | ||
dracoDecoder.GetFaceFromMesh(dracoGeometry, i, faceIndices); | ||
|
||
var offset = i * 3; |
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.
Old school optimization that won't matter, but why not offset += 3;
after the assignments to indexArray
to avoid the multiplication?
'../Core/defined', | ||
'../Core/IndexDatatype', | ||
'../Core/RuntimeError', | ||
'../ThirdParty/draco-decoder-gltf', |
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.
@fanzhanggoogle any chance you are able to quick a quick look at Cesium's use of the Draco API in this file and let us know if you have any feedback or tips? Thanks!
@@ -2330,6 +2336,49 @@ defineSuite([ | |||
}); | |||
}); | |||
|
|||
it('loads a glTF with KHR_draco_mesh_compression extension', function() { |
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.
Do these tests have code coverage for all the new code?
Do we test both with and without quantization?
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.
Do we test both with and without quantization?
I believe Draco + quantization was slated for the next PR.
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.
Ah, I did not realize that quantization would be a separate PR. I was under the impression that Draco was "done." Let's please finish Draco with quantization before moving on to the GLSL extension.
Part of #5120
Fixes #6298
Added support for Draco compressed models. Added sample models of the Mi;k Truck and Cesium Man.
TODO: