Skip to content
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
merged 22 commits into from
Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Change Log

### 1.44 - 2018-04-02

##### Additions :tada:
* Added support for glTF models with [Draco geometry compression](https://github.com/fanzhanggoogle/glTF/blob/KHR_mesh_compression/extensions/Khronos/KHR_draco_mesh_compression/README.md).

##### Fixes :wrench:
* Fixed support of glTF-supplied tangent vectors. [#6302](https://github.com/AnalyticalGraphicsInc/cesium/pull/6302)

Expand Down
16 changes: 16 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,22 @@ https://github.com/KhronosGroup/glTF-WebGL-PBR
>CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
>OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

### Draco

https://github.com/google/draco

>Licensed under the Apache License, Version 2.0 (the "License"); you may not
>use this file except in compliance with the License. You may obtain a copy of
>the License at
>
><http://www.apache.org/licenses/LICENSE-2.0>
>
>Unless required by applicable law or agreed to in writing, software
>distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
>WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
>License for the specific language governing permissions and limitations under
>the License.

Tests
=====

Expand Down
197 changes: 197 additions & 0 deletions Source/Scene/DracoLoader.js
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({
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

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;
});
Loading