Skip to content

Commit

Permalink
Merge pull request #10731 from CesiumGS/free-instanced-typed-arrays
Browse files Browse the repository at this point in the history
Free typed arrays for instanced models
  • Loading branch information
ptrgags authored Aug 30, 2022
2 parents 12a593b + a18057c commit 4237eef
Show file tree
Hide file tree
Showing 14 changed files with 932 additions and 978 deletions.
50 changes: 26 additions & 24 deletions Source/Scene/GltfLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,16 +870,20 @@ function finalizeAttribute(
}

if (loadTypedArray) {
// The accessor's byteOffset and byteStride should be ignored since values
// are tightly packed in a typed array
const bufferViewTypedArray = vertexBufferLoader.typedArray;
attribute.typedArray = getPackedTypedArray(
gltf,
accessor,
bufferViewTypedArray
);
attribute.byteOffset = 0;
attribute.byteStride = undefined;

if (!loadBuffer) {
// If the buffer isn't loaded, then the accessor's byteOffset and
// byteStride should be ignored, since values are only available in a
// tightly packed typed array
attribute.byteOffset = 0;
attribute.byteStride = undefined;
}
}
}

Expand Down Expand Up @@ -1043,39 +1047,37 @@ function loadInstancedAttribute(
InstanceAttributeSemantic,
gltfSemantic
);

const modelSemantic = semanticInfo.modelSemantic;

const isTransformAttribute =
modelSemantic === InstanceAttributeSemantic.TRANSLATION ||
modelSemantic === InstanceAttributeSemantic.ROTATION ||
modelSemantic === InstanceAttributeSemantic.SCALE;

const isTranslationAttribute =
modelSemantic === InstanceAttributeSemantic.TRANSLATION;

const loadFor2D =
isTranslationAttribute &&
loader._loadAttributesFor2D &&
!frameState.scene3DOnly;

// In addition to the loader options, load the attributes as typed arrays if:
// - the instances have rotations, so that instance matrices are computed on the CPU.
// This avoids the expensive quaternion -> rotation matrix conversion in the shader.
// - the translation accessor does not have a min and max, so the values can be used
// for computing an accurate bounding volume.
// - the attributes contain feature IDs, in order to add the instance's feature ID
// to the pick object.
// - translations are required for 2D
// Load the attributes as typed arrays only if:
// - loadAttributesAsTypedArray is true
// - the instances have rotations. This only applies to the transform attributes,
// since The instance matrices are computed on the CPU. This avoids the
// expensive quaternion -> rotation matrix conversion in the shader.
// - GPU instancing is not supported.
let loadTypedArray =
const loadAsTypedArrayOnly =
loader._loadAttributesAsTypedArray ||
((hasRotation || !hasTranslationMinMax) && isTransformAttribute) ||
modelSemantic === InstanceAttributeSemantic.FEATURE_ID ||
(hasRotation && isTransformAttribute) ||
!frameState.context.instancedArrays;

const loadBuffer = !loadTypedArray;
loadTypedArray = loadTypedArray || loadFor2D;
const loadBuffer = !loadAsTypedArrayOnly;

// Load the translations as a typed array in addition to the buffer if
// - the accessor does not have a min and max. The values will be used
// for computing an accurate bounding volume.
// - the model will be projected to 2D.
const loadFor2D = loader._loadAttributesFor2D && !frameState.scene3DOnly;
const loadTranslationAsTypedArray =
isTranslationAttribute && (!hasTranslationMinMax || loadFor2D);

const loadTypedArray = loadAsTypedArrayOnly || loadTranslationAsTypedArray;

// Don't pass in draco object since instanced attributes can't be draco compressed
return loadAttribute(
Expand Down
4 changes: 2 additions & 2 deletions Source/Scene/Model/GeometryPipelineStage.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,8 @@ function addAttributeToRenderResources(
count: attribute.count,
componentsPerAttribute: componentsPerAttribute,
componentDatatype: ComponentDatatype.FLOAT, // Projected positions will always be floats.
offsetInBytes: attribute.byteOffset,
strideInBytes: attribute.byteStride,
offsetInBytes: 0,
strideInBytes: undefined,
normalize: attribute.normalized,
};

Expand Down
158 changes: 126 additions & 32 deletions Source/Scene/Model/I3dmLoader.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
import AttributeCompression from "../../Core/AttributeCompression.js";
import Axis from "../Axis.js";
import BoundingSphere from "../../Core/BoundingSphere.js";
import Cartesian3 from "../../Core/Cartesian3.js";
import Cesium3DTileFeatureTable from "../Cesium3DTileFeatureTable.js";
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 Ellipsoid from "../../Core/Ellipsoid.js";
import StructuralMetadata from "../StructuralMetadata.js";
import getStringFromTypedArray from "../../Core/getStringFromTypedArray.js";
import GltfLoader from "../GltfLoader.js";
import I3dmParser from "../I3dmParser.js";
import Matrix3 from "../../Core/Matrix3.js";
import Matrix4 from "../../Core/Matrix4.js";
import Quaternion from "../../Core/Quaternion.js";
import RuntimeError from "../../Core/RuntimeError.js";
import Transforms from "../../Core/Transforms.js";
import Buffer from "../../Renderer/Buffer.js";
import BufferUsage from "../../Renderer/BufferUsage.js";
import AttributeType from "../AttributeType.js";
import Axis from "../Axis.js";
import Cesium3DTileFeatureTable from "../Cesium3DTileFeatureTable.js";
import GltfLoader from "../GltfLoader.js";
import InstanceAttributeSemantic from "../InstanceAttributeSemantic.js";
import I3dmParser from "../I3dmParser.js";
import MetadataClass from "../MetadataClass.js";
import ModelComponents from "../ModelComponents.js";
import parseBatchTable from "../parseBatchTable.js";
import PropertyTable from "../PropertyTable.js";
import Quaternion from "../../Core/Quaternion.js";
import ResourceLoader from "../ResourceLoader.js";
import RuntimeError from "../../Core/RuntimeError.js";
import Transforms from "../../Core/Transforms.js";
import InstanceAttributeSemantic from "../InstanceAttributeSemantic.js";
import AttributeType from "../AttributeType.js";
import BoundingSphere from "../../Core/BoundingSphere.js";
import StructuralMetadata from "../StructuralMetadata.js";

const I3dmLoaderState = {
UNLOADED: 0,
NOT_LOADED: 0,
LOADING: 1,
PROCESSING: 2,
READY: 3,
FAILED: 4,
POST_PROCESSING: 3,
READY: 4,
FAILED: 5,
UNLOADED: 6,
};

const Attribute = ModelComponents.Attribute;
Expand Down Expand Up @@ -107,10 +111,20 @@ function I3dmLoader(options) {
this._loadIndicesForWireframe = loadIndicesForWireframe;
this._loadPrimitiveOutline = loadPrimitiveOutline;

this._state = I3dmLoaderState.UNLOADED;
this._state = I3dmLoaderState.NOT_LOADED;
this._promise = undefined;

this._gltfLoader = undefined;
this._gltfLoaderPromise = undefined;
this._process = function (loader, frameState) {};
this._postProcess = function (loader, frameState) {};

// Instanced attributes are initially parsed as typed arrays, but if they
// do not need to be further processed (e.g. turned into transform matrices),
// it is more efficient to turn them into buffers. The I3dmLoader will own the
// resources and store them here.
this._buffers = [];
this._components = undefined;

this._transform = Matrix4.IDENTITY;
this._batchTable = undefined;
Expand Down Expand Up @@ -267,34 +281,47 @@ I3dmLoader.prototype.load = function () {
this._gltfLoader = gltfLoader;
this._state = I3dmLoaderState.LOADING;

const that = this;
gltfLoader.load();
this._promise = gltfLoader.promise
.then(function () {
if (that.isDestroyed()) {
return;
}

const that = this;
const processPromise = new Promise(function (resolve) {
that._process = function (loader, frameState) {
loader._gltfLoader.process(frameState);
};

that._postProcess = function (loader, frameState) {
const gltfLoader = loader._gltfLoader;
const components = gltfLoader.components;

// Combine the RTC_CENTER transform from the i3dm and the CESIUM_RTC
// transform from the glTF. In practice CESIUM_RTC is not set for
// instanced models but multiply the transforms just in case.
components.transform = Matrix4.multiplyTransformation(
that._transform,
loader._transform,
components.transform,
components.transform
);

createInstances(that, components);
createStructuralMetadata(that, components);
that._components = components;
createInstances(loader, components, frameState);
createStructuralMetadata(loader, components);
loader._components = components;

// Now that we have the parsed components, we can release the array buffer
that._arrayBuffer = undefined;
loader._arrayBuffer = undefined;

that._state = I3dmLoaderState.READY;
return that;
loader._state = I3dmLoaderState.READY;
resolve(loader);
};
});

this._promise = gltfLoader.promise
.then(function () {
if (that.isDestroyed()) {
return;
}
that._state = I3dmLoaderState.POST_PROCESSING;

return processPromise;
})
.catch(function (error) {
if (that.isDestroyed()) {
Expand Down Expand Up @@ -324,7 +351,11 @@ I3dmLoader.prototype.process = function (frameState) {
}

if (this._state === I3dmLoaderState.PROCESSING) {
this._gltfLoader.process(frameState);
this._process(this, frameState);
}

if (this._state === I3dmLoaderState.POST_PROCESSING) {
this._postProcess(this, frameState);
}
};

Expand Down Expand Up @@ -363,7 +394,7 @@ const positionScratch = new Cartesian3();
const propertyScratch1 = new Array(4);
const transformScratch = new Matrix4();

function createInstances(loader, components) {
function createInstances(loader, components, frameState) {
let i;
const featureTable = loader._featureTable;
const instancesLength = loader._instancesLength;
Expand Down Expand Up @@ -507,6 +538,7 @@ function createInstances(loader, components) {
// Create instances.
const instances = new Instances();
instances.transformInWorldSpace = true;
const buffers = loader._buffers;

// Create translation vertex attribute.
const translationAttribute = new Attribute();
Expand All @@ -515,7 +547,24 @@ function createInstances(loader, components) {
translationAttribute.componentDatatype = ComponentDatatype.FLOAT;
translationAttribute.type = AttributeType.VEC3;
translationAttribute.count = instancesLength;
// The min / max values of the translation attribute need to be computed
// by the model pipeline, so so a pointer to the typed array is stored.
translationAttribute.typedArray = translationTypedArray;
// If there is no rotation attribute, however, the translations can also be
// loaded as a buffer to prevent additional resource creation in the pipeline.
if (!hasRotation) {
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: translationTypedArray,
usage: BufferUsage.STATIC_DRAW,
});
// Destruction of resources is handled by I3dmLoader.unload().
buffer.vertexArrayDestroyable = false;
buffers.push(buffer);

translationAttribute.buffer = buffer;
}

instances.attributes.push(translationAttribute);

// Create rotation vertex attribute.
Expand All @@ -538,7 +587,23 @@ function createInstances(loader, components) {
scaleAttribute.componentDatatype = ComponentDatatype.FLOAT;
scaleAttribute.type = AttributeType.VEC3;
scaleAttribute.count = instancesLength;
scaleAttribute.typedArray = scaleTypedArray;
if (hasRotation) {
// If rotations are present, all transform attributes are loaded
// as typed arrays to compute transform matrices for the model.
scaleAttribute.typedArray = scaleTypedArray;
} else {
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: translationTypedArray,
usage: BufferUsage.STATIC_DRAW,
});
// Destruction of resources is handled by I3dmLoader.unload().
buffer.vertexArrayDestroyable = false;
buffers.push(buffer);

scaleAttribute.buffer = buffer;
}

instances.attributes.push(scaleAttribute);
}

Expand All @@ -550,7 +615,16 @@ function createInstances(loader, components) {
featureIdAttribute.componentDatatype = ComponentDatatype.FLOAT;
featureIdAttribute.type = AttributeType.SCALAR;
featureIdAttribute.count = instancesLength;
featureIdAttribute.typedArray = featureIdArray;
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: featureIdArray,
usage: BufferUsage.STATIC_DRAW,
});
// Destruction of resources is handled by I3dmLoader.unload().
buffer.vertexArrayDestroyable = false;
buffers.push(buffer);
featureIdAttribute.buffer = buffer;

instances.attributes.push(featureIdAttribute);

// Create feature ID attribute.
Expand Down Expand Up @@ -765,12 +839,32 @@ function processScale(featureTable, i, instanceScale) {
}
}

function unloadBuffers(loader) {
const buffers = loader._buffers;
const length = buffers.length;
for (let i = 0; i < length; i++) {
const buffer = buffers[i];
if (!buffer.isDestroyed()) {
buffer.destroy();
}
}
buffers.length = 0;
}

I3dmLoader.prototype.isUnloaded = function () {
return this._state === I3dmLoaderState.UNLOADED;
};

I3dmLoader.prototype.unload = function () {
if (defined(this._gltfLoader)) {
this._gltfLoader.unload();
}

unloadBuffers(this);

this._components = undefined;
this._arrayBuffer = undefined;
this._state = I3dmLoaderState.UNLOADED;
};

export default I3dmLoader;
Loading

0 comments on commit 4237eef

Please sign in to comment.