From 5611795920cc42f9256a388a436db758658f010a Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Wed, 18 Dec 2024 21:03:38 +0100 Subject: [PATCH] First draft for sparse accessor support --- .../Source/Scene/GltfIndexBufferLoader.js | 69 +++++++---- packages/engine/Source/Scene/GltfLoader.js | 27 +++- .../Source/Scene/GltfVertexBufferLoader.js | 115 ++++++++++++++++++ .../engine/Source/Scene/getComponentWriter.js | 59 +++++++++ 4 files changed, 239 insertions(+), 31 deletions(-) create mode 100644 packages/engine/Source/Scene/getComponentWriter.js diff --git a/packages/engine/Source/Scene/GltfIndexBufferLoader.js b/packages/engine/Source/Scene/GltfIndexBufferLoader.js index 5a46f8e43535..a9a02bef3f61 100644 --- a/packages/engine/Source/Scene/GltfIndexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfIndexBufferLoader.js @@ -240,35 +240,50 @@ function createIndicesTypedArray(indexBufferLoader, bufferViewTypedArray) { const accessorId = indexBufferLoader._accessorId; const accessor = gltf.accessors[accessorId]; const count = accessor.count; - const indexDatatype = accessor.componentType; - const indexSize = IndexDatatype.getSizeInBytes(indexDatatype); - - let arrayBuffer = bufferViewTypedArray.buffer; - let byteOffset = bufferViewTypedArray.byteOffset + accessor.byteOffset; - - if (byteOffset % indexSize !== 0) { - const byteLength = count * indexSize; - const view = new Uint8Array(arrayBuffer, byteOffset, byteLength); - const copy = new Uint8Array(view); - arrayBuffer = copy.buffer; - byteOffset = 0; - deprecationWarning( - "index-buffer-unaligned", - `The index array is not aligned to a ${indexSize}-byte boundary.`, - ); - } + const byteOffset = accessor.byteOffset; + const componentType = accessor.componentType; + return GltfIndexBufferLoader.createIndicesTypedArrayFromBufferViewTypedArray( + bufferViewTypedArray, + byteOffset, + componentType, + count, + ); +} - let typedArray; - if (indexDatatype === IndexDatatype.UNSIGNED_BYTE) { - typedArray = new Uint8Array(arrayBuffer, byteOffset, count); - } else if (indexDatatype === IndexDatatype.UNSIGNED_SHORT) { - typedArray = new Uint16Array(arrayBuffer, byteOffset, count); - } else if (indexDatatype === IndexDatatype.UNSIGNED_INT) { - typedArray = new Uint32Array(arrayBuffer, byteOffset, count); - } +GltfIndexBufferLoader.createIndicesTypedArrayFromBufferViewTypedArray = + function (bufferViewTypedArray, byteOffset, componentType, count) { + const indexSize = IndexDatatype.getSizeInBytes(componentType); + + let arrayBuffer = bufferViewTypedArray.buffer; + let arrayBufferByteOffset = bufferViewTypedArray.byteOffset + byteOffset; + + if (arrayBufferByteOffset % indexSize !== 0) { + const byteLength = count * indexSize; + const view = new Uint8Array( + arrayBuffer, + arrayBufferByteOffset, + byteLength, + ); + const copy = new Uint8Array(view); + arrayBuffer = copy.buffer; + arrayBufferByteOffset = 0; + deprecationWarning( + "index-buffer-unaligned", + `The index array is not aligned to a ${indexSize}-byte boundary.`, + ); + } - return typedArray; -} + let typedArray; + if (componentType === IndexDatatype.UNSIGNED_BYTE) { + typedArray = new Uint8Array(arrayBuffer, arrayBufferByteOffset, count); + } else if (componentType === IndexDatatype.UNSIGNED_SHORT) { + typedArray = new Uint16Array(arrayBuffer, arrayBufferByteOffset, count); + } else if (componentType === IndexDatatype.UNSIGNED_INT) { + typedArray = new Uint32Array(arrayBuffer, arrayBufferByteOffset, count); + } + + return typedArray; + }; function handleError(indexBufferLoader, error) { indexBufferLoader.unload(); diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index d853d90d73dc..4a761f6b0016 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -748,11 +748,30 @@ function getBufferViewLoader(loader, bufferViewId) { } function getPackedTypedArray(gltf, accessor, bufferViewTypedArray) { - let byteOffset = accessor.byteOffset; + const byteOffset = accessor.byteOffset; const byteStride = getAccessorByteStride(gltf, accessor); const count = accessor.count; - const componentCount = numberOfComponentsForType(accessor.type); const componentType = accessor.componentType; + const type = accessor.type; + return GltfLoader.getPackedTypedArrayFromBufferViewTypedArray( + bufferViewTypedArray, + byteOffset, + type, + componentType, + byteStride, + count, + ); +} + +GltfLoader.getPackedTypedArrayFromBufferViewTypedArray = function ( + bufferViewTypedArray, + byteOffset, + type, + componentType, + byteStride, + count, +) { + const componentCount = numberOfComponentsForType(type); const componentByteLength = ComponentDatatype.getSizeInBytes(componentType); const defaultByteStride = componentByteLength * componentCount; const componentsLength = count * componentCount; @@ -775,7 +794,7 @@ function getPackedTypedArray(gltf, accessor, bufferViewTypedArray) { const dataView = new DataView(bufferViewTypedArray.buffer); const components = new Array(componentCount); - const componentReader = getComponentReader(accessor.componentType); + const componentReader = getComponentReader(componentType); byteOffset = bufferViewTypedArray.byteOffset + byteOffset; for (let i = 0; i < count; ++i) { @@ -793,7 +812,7 @@ function getPackedTypedArray(gltf, accessor, bufferViewTypedArray) { } return accessorTypedArray; -} +}; function loadDefaultAccessorValues(accessor, values) { const accessorType = accessor.type; diff --git a/packages/engine/Source/Scene/GltfVertexBufferLoader.js b/packages/engine/Source/Scene/GltfVertexBufferLoader.js index 52f77e4505e1..dabdb2016ca4 100644 --- a/packages/engine/Source/Scene/GltfVertexBufferLoader.js +++ b/packages/engine/Source/Scene/GltfVertexBufferLoader.js @@ -1,10 +1,15 @@ import Check from "../Core/Check.js"; +import ComponentDatatype from "../Core/ComponentDatatype.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import Buffer from "../Renderer/Buffer.js"; import BufferUsage from "../Renderer/BufferUsage.js"; import AttributeType from "./AttributeType.js"; +import getComponentWriter from "./getComponentWriter.js"; +import GltfIndexBufferLoader from "./GltfIndexBufferLoader.js"; +import GltfLoader from "./GltfLoader.js"; +import numberOfComponentsForType from "./GltfPipeline/numberOfComponentsForType.js"; import JobType from "./JobType.js"; import ModelComponents from "./ModelComponents.js"; import ResourceLoader from "./ResourceLoader.js"; @@ -335,6 +340,15 @@ async function loadFromBufferView(vertexBufferLoader) { } vertexBufferLoader._typedArray = bufferViewLoader.typedArray; + + if (defined(vertexBufferLoader._accessorId)) { + const accessor = + vertexBufferLoader._gltf.accessors[vertexBufferLoader._accessorId]; + if (defined(accessor.sparse)) { + await handleSparseAccessor(vertexBufferLoader, accessor); + } + } + vertexBufferLoader._state = ResourceLoaderState.PROCESSING; return vertexBufferLoader; } catch (error) { @@ -346,6 +360,78 @@ async function loadFromBufferView(vertexBufferLoader) { } } +async function handleSparseAccessor(vertexBufferLoader, accessor) { + const sparse = accessor.sparse; + + console.log("There, there, sparse!", sparse); + const count = sparse.count; + + const indices = sparse.indices; + const indicesBufferViewId = indices.bufferView; + const indicesBufferViewTypedArray = await loadDependencyBufferViewTypedArray( + vertexBufferLoader, + indicesBufferViewId, + ); + + const indicesByteOffset = indices.byteOffset; + const indicesComponentType = indices.componentType; + const indicesTypedArray = + GltfIndexBufferLoader.createIndicesTypedArrayFromBufferViewTypedArray( + indicesBufferViewTypedArray, + indicesByteOffset, + indicesComponentType, + count, + ); + console.log("indices typed array ", indicesTypedArray); + + const values = sparse.values; + const valuesBufferViewId = values.bufferView; + const valuesBufferViewTypedArray = await loadDependencyBufferViewTypedArray( + vertexBufferLoader, + valuesBufferViewId, + ); + + const valuesByteOffset = values.byteOffset; + const valuesComponentType = accessor.componentType; + const componentByteLength = + ComponentDatatype.getSizeInBytes(valuesComponentType); + const type = accessor.type; + const componentCount = numberOfComponentsForType(type); + const byteStride = componentByteLength * componentCount; + const valuesTypedArray = + GltfLoader.getPackedTypedArrayFromBufferViewTypedArray( + valuesBufferViewTypedArray, + valuesByteOffset, + type, + valuesComponentType, + byteStride, + count, + ); + console.log("values typed array ", valuesTypedArray); + + const targetTypedArray = vertexBufferLoader._typedArray; + const targetDataView = new DataView( + targetTypedArray.buffer, + targetTypedArray.byteOffset, + targetTypedArray.byteLength, + ); + const componentWriter = getComponentWriter(valuesComponentType); + + console.log("target typed array ", targetTypedArray); + const n = indicesTypedArray.length; + for (let i = 0; i < n; i++) { + const index = indicesTypedArray[i]; + for (let c = 0; c < componentCount; c++) { + const value = valuesTypedArray[i * componentCount + c]; + console.log( + `setting ${value} at component ${c} of index ${index} of ${targetTypedArray}`, + ); + componentWriter(targetDataView, index * componentCount + c, value); + } + } + console.log("target typed array now ", targetTypedArray); +} + function handleError(vertexBufferLoader, error) { vertexBufferLoader.unload(); vertexBufferLoader._state = ResourceLoaderState.FAILED; @@ -353,6 +439,35 @@ function handleError(vertexBufferLoader, error) { throw vertexBufferLoader.getError(errorMessage, error); } +async function loadDependencyBufferViewTypedArray( + vertexBufferLoader, + bufferViewId, +) { + vertexBufferLoader._state = ResourceLoaderState.LOADING; + const resourceCache = vertexBufferLoader._resourceCache; + try { + const bufferViewLoader = resourceCache.getBufferViewLoader({ + gltf: vertexBufferLoader._gltf, + bufferViewId: bufferViewId, + gltfResource: vertexBufferLoader._gltfResource, + baseResource: vertexBufferLoader._baseResource, + }); + vertexBufferLoader._bufferViewLoader = bufferViewLoader; + await bufferViewLoader.load(); + + if (vertexBufferLoader.isDestroyed()) { + return; + } + return bufferViewLoader.typedArray; + } catch (error) { + if (vertexBufferLoader.isDestroyed()) { + return; + } + + handleError(vertexBufferLoader, error); + } +} + function CreateVertexBufferJob() { this.typedArray = undefined; this.context = undefined; diff --git a/packages/engine/Source/Scene/getComponentWriter.js b/packages/engine/Source/Scene/getComponentWriter.js new file mode 100644 index 000000000000..4ef5a3c1daa7 --- /dev/null +++ b/packages/engine/Source/Scene/getComponentWriter.js @@ -0,0 +1,59 @@ +import ComponentDatatype from "../Core/ComponentDatatype.js"; + +/** + * Returns a function to write data into a DataView + * + * @param {number} componentType Type to convert the data to. + * @returns {ComponentWriter} Function that writes. + * + * @private + */ +function getComponentWriter(componentType) { + switch (componentType) { + case ComponentDatatype.BYTE: + return function (dataView, index, input) { + dataView.setInt8(index, input); + }; + case ComponentDatatype.UNSIGNED_BYTE: + return function (dataView, index, input) { + dataView.setUint8(index, input); + }; + case ComponentDatatype.SHORT: + return function (dataView, index, input) { + dataView.setInt16(index * 2, input, true); + }; + case ComponentDatatype.UNSIGNED_SHORT: + return function (dataView, index, input) { + dataView.setUint16(index * 2, input, true); + }; + case ComponentDatatype.INT: + return function (dataView, index, input) { + dataView.setInt32(index * 4, input, true); + }; + case ComponentDatatype.UNSIGNED_INT: + return function (dataView, index, input) { + dataView.setUint32(index * 4, input, true); + }; + case ComponentDatatype.FLOAT: + return function (dataView, index, input) { + dataView.setFloat32(index * 4, input, true); + }; + case ComponentDatatype.DOUBLE: + return function (dataView, index, input) { + dataView.setFloat64(index * 8, input, true); + }; + } +} + +/** + * A function to write components into a data view + * @callback ComponentWriter + * + * @param {DataView} dataView The data view to write to. + * @param {number} index The index of the component. + * @param {number} input The value to write at the given index. + * + * @private + */ + +export default getComponentWriter;