Skip to content

Commit

Permalink
Batchmesh: Fix cases where calling optimize can result in inconsisten…
Browse files Browse the repository at this point in the history
…t state (#29624)

* Fix skipping over active rather than inactive geometry

* Fix optimize

* Correct the shifting of geometry data

* Account for a case where the index bit size can be increased.

* typo fix

* Remove comments

* Add comments, removed unnecessary changes

* Dispose of textures on instance resize
  • Loading branch information
gkjohnson authored Oct 16, 2024
1 parent b9144b9 commit fdd528d
Showing 1 changed file with 67 additions and 53 deletions.
120 changes: 67 additions & 53 deletions src/objects/BatchedMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ const _batchIntersects = [];
// @TODO: geometry.drawRange support?
// @TODO: geometry.morphAttributes support?
// @TODO: Support uniform parameter per geometry
// @TODO: Add an "optimize" function to pack geometry and remove data gaps

// copies data from attribute "src" into "target" starting at "targetOffset"
function copyAttributeData( src, target, targetOffset = 0 ) {
Expand Down Expand Up @@ -132,8 +131,23 @@ function copyAttributeData( src, target, targetOffset = 0 ) {
// safely copies array contents to a potentially smaller array
function copyArrayContents( src, target ) {

const len = Math.min( src.length, target.length );
target.set( new src.constructor( src.buffer, 0, len ) );
if ( src.constructor !== target.constructor ) {

// if arrays are of a different type (eg due to index size increasing) then data must be per-element copied
const len = Math.min( src.length, target.length );
for ( let i = 0; i < len; i ++ ) {

target[ i ] = src[ i ];

}

} else {

// if the arrays use the same data layout we can use a fast block copy
const len = Math.min( src.length, target.length );
target.set( new src.constructor( src.buffer, 0, len ) );

}

}

Expand Down Expand Up @@ -180,6 +194,10 @@ class BatchedMesh extends Mesh {
this._multiDrawInstances = null;
this._visibilityChanged = true;

// used to track where the next point is that geometry should be inserted
this._nextIndexStart = 0;
this._nextVertexStart = 0;

// Local matrix per geometry by using data texture
this._matricesTexture = null;
this._indirectTexture = null;
Expand Down Expand Up @@ -425,16 +443,11 @@ class BatchedMesh extends Mesh {
indexCount: - 1,
};

let lastRange = null;
const reservedRanges = this._reservedRanges;
const drawRanges = this._drawRanges;
const bounds = this._bounds;
if ( this._geometryCount !== 0 ) {

lastRange = reservedRanges[ reservedRanges.length - 1 ];

}

reservedRange.vertexStart = this._nextVertexStart;
if ( vertexCount === - 1 ) {

reservedRange.vertexCount = geometry.getAttribute( 'position' ).count;
Expand All @@ -445,20 +458,11 @@ class BatchedMesh extends Mesh {

}

if ( lastRange === null ) {

reservedRange.vertexStart = 0;

} else {

reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount;

}

const index = geometry.getIndex();
const hasIndex = index !== null;
if ( hasIndex ) {

reservedRange.indexStart = this._nextIndexStart;
if ( indexCount === - 1 ) {

reservedRange.indexCount = index.count;
Expand All @@ -469,16 +473,6 @@ class BatchedMesh extends Mesh {

}

if ( lastRange === null ) {

reservedRange.indexStart = 0;

} else {

reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount;

}

}

if (
Expand Down Expand Up @@ -531,6 +525,10 @@ class BatchedMesh extends Mesh {
// update the geometry
this.setGeometryAt( geometryId, geometry );

// increment the next geometry position
this._nextIndexStart = reservedRange.indexStart + reservedRange.indexCount;
this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount;

return geometryId;

}
Expand Down Expand Up @@ -698,41 +696,56 @@ class BatchedMesh extends Mesh {
let nextVertexStart = 0;
let nextIndexStart = 0;

// iterate over all geometry ranges
// Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest
// in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order.
const drawRanges = this._drawRanges;
const reservedRanges = this._reservedRanges;
const indices = drawRanges
.map( ( e, i ) => i )
.sort( ( a, b ) => {

return reservedRanges[ a ].vertexStart - reservedRanges[ b ].vertexStart;

} );

const geometry = this.geometry;
for ( let i = 0, l = drawRanges.length; i < l; i ++ ) {

// if a geometry range is inactive then don't copy anything
const drawRange = drawRanges[ i ];
const reservedRange = reservedRanges[ i ];
const index = indices[ i ];
const drawRange = drawRanges[ index ];
const reservedRange = reservedRanges[ index ];
if ( drawRange.active === false ) {

continue;

}

// if a geometry contains an index buffer then shift it, as well
if ( geometry.index !== null && reservedRange.indexStart !== nextIndexStart ) {
if ( geometry.index !== null ) {

const { indexStart, indexCount } = reservedRange;
const index = geometry.index;
const array = index.array;
if ( reservedRange.indexStart !== nextIndexStart ) {

// shift the index pointers based on how the vertex data will shift
// adjusting the index must happen first so the original vertex start value is available
const elementDelta = nextVertexStart - reservedRange.vertexStart;
for ( let j = indexStart; j < indexStart + indexCount; j ++ ) {
const { indexStart, indexCount } = reservedRange;
const index = geometry.index;
const array = index.array;

array[ j ] = array[ j ] + elementDelta;
// shift the index pointers based on how the vertex data will shift
// adjusting the index must happen first so the original vertex start value is available
const elementDelta = nextVertexStart - reservedRange.vertexStart;
for ( let j = indexStart; j < indexStart + indexCount; j ++ ) {

}
array[ j ] = array[ j ] + elementDelta;

}

index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount );
index.addUpdateRange( nextIndexStart, indexCount );

index.array.copyWithin( nextIndexStart, indexStart, indexStart + indexCount );
index.addUpdateRange( nextIndexStart, indexCount );
reservedRange.indexStart = nextIndexStart;

}

reservedRange.indexStart = nextIndexStart;
nextIndexStart += reservedRange.indexCount;

}
Expand All @@ -752,12 +765,16 @@ class BatchedMesh extends Mesh {
}

reservedRange.vertexStart = nextVertexStart;
nextVertexStart += reservedRange.vertexCount;

}

nextVertexStart += reservedRange.vertexCount;
drawRange.start = geometry.index ? reservedRange.indexStart : reservedRange.vertexStart;

// step the next geometry points to the shifted position
this._nextIndexStart = geometry.index ? reservedRange.indexStart + reservedRange.indexCount : 0;
this._nextVertexStart = reservedRange.vertexStart + reservedRange.vertexCount;

}

return this;
Expand Down Expand Up @@ -857,9 +874,6 @@ class BatchedMesh extends Mesh {

setMatrixAt( instanceId, matrix ) {

// @TODO: Map geometryId to index of the arrays because
// optimize() can make geometryId mismatch the index

const drawInfo = this._drawInfo;
const matricesTexture = this._matricesTexture;
const matricesArray = this._matricesTexture.image.data;
Expand Down Expand Up @@ -898,9 +912,6 @@ class BatchedMesh extends Mesh {

}

// @TODO: Map id to index of the arrays because
// optimize() can make id mismatch the index

const colorsTexture = this._colorsTexture;
const colorsArray = this._colorsTexture.image.data;
const drawInfo = this._drawInfo;
Expand Down Expand Up @@ -1055,14 +1066,17 @@ class BatchedMesh extends Mesh {
const matricesTexture = this._matricesTexture;
const colorsTexture = this._colorsTexture;

indirectTexture.dispose();
this._initIndirectTexture();
copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data );

matricesTexture.dispose();
this._initMatricesTexture();
copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data );

if ( colorsTexture ) {

colorsTexture.dispose();
this._initColorsTexture();
copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data );

Expand Down Expand Up @@ -1158,7 +1172,7 @@ class BatchedMesh extends Mesh {
const drawRange = drawRanges[ geometryId ];
_mesh.geometry.setDrawRange( drawRange.start, drawRange.count );

// ge the intersects
// get the intersects
this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld );
this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox );
this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere );
Expand Down

0 comments on commit fdd528d

Please sign in to comment.