function WebGLAttributes( gl ) {
const buffers = new WeakMap();
function createBuffer( attribute, bufferType ) {
const array = attribute.array;
const usage = attribute.usage;
const size = array.byteLength;
const buffer = gl.createBuffer();
gl.bindBuffer( bufferType, buffer );
gl.bufferData( bufferType, array, usage );
attribute.onUploadCallback();
let type;
if ( array instanceof Float32Array ) {
type = gl.FLOAT;
} else if ( array instanceof Uint16Array ) {
if ( attribute.isFloat16BufferAttribute ) {
type = gl.HALF_FLOAT;
} else {
type = gl.UNSIGNED_SHORT;
}
} else if ( array instanceof Int16Array ) {
type = gl.SHORT;
} else if ( array instanceof Uint32Array ) {
type = gl.UNSIGNED_INT;
} else if ( array instanceof Int32Array ) {
type = gl.INT;
} else if ( array instanceof Int8Array ) {
type = gl.BYTE;
} else if ( array instanceof Uint8Array ) {
type = gl.UNSIGNED_BYTE;
} else if ( array instanceof Uint8ClampedArray ) {
type = gl.UNSIGNED_BYTE;
} else {
throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array );
}
return {
buffer: buffer,
type: type,
bytesPerElement: array.BYTES_PER_ELEMENT,
version: attribute.version,
size: size
};
}
function updateBuffer( buffer, attribute, bufferType ) {
const array = attribute.array;
const updateRanges = attribute.updateRanges;
gl.bindBuffer( bufferType, buffer );
if ( updateRanges.length === 0 ) {
// Not using update ranges
gl.bufferSubData( bufferType, 0, array );
} else {
// Before applying update ranges, we merge any adjacent / overlapping
// ranges to reduce load on `gl.bufferSubData`. Empirically, this has led
// to performance improvements for applications which make heavy use of
// update ranges. Likely due to GPU command overhead.
//
// Note that to reduce garbage collection between frames, we merge the
// update ranges in-place. This is safe because this method will clear the
// update ranges once updated.
updateRanges.sort( ( a, b ) => a.start - b.start );
// To merge the update ranges in-place, we work from left to right in the
// existing updateRanges array, merging ranges. This may result in a final
// array which is smaller than the original. This index tracks the last
// index representing a merged range, any data after this index can be
// trimmed once the merge algorithm is completed.
let mergeIndex = 0;
for ( let i = 1; i < updateRanges.length; i ++ ) {
const previousRange = updateRanges[ mergeIndex ];
const range = updateRanges[ i ];
// We add one here to merge adjacent ranges. This is safe because ranges
// operate over positive integers.
if ( range.start <= previousRange.start + previousRange.count + 1 ) {
previousRange.count = Math.max(
previousRange.count,
range.start + range.count - previousRange.start
);
} else {
++ mergeIndex;
updateRanges[ mergeIndex ] = range;
}
}
// Trim the array to only contain the merged ranges.
updateRanges.length = mergeIndex + 1;
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
const range = updateRanges[ i ];
gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT,
array, range.start, range.count );
}
attribute.clearUpdateRanges();
}
attribute.onUploadCallback();
}
//
function get( attribute ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
return buffers.get( attribute );
}
function remove( attribute ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
const data = buffers.get( attribute );
if ( data ) {
gl.deleteBuffer( data.buffer );
buffers.delete( attribute );
}
}
function update( attribute, bufferType ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
if ( attribute.isGLBufferAttribute ) {
const cached = buffers.get( attribute );
if ( ! cached || cached.version < attribute.version ) {
buffers.set( attribute, {
buffer: attribute.buffer,
type: attribute.type,
bytesPerElement: attribute.elementSize,
version: attribute.version
} );
}
return;
}
const data = buffers.get( attribute );
if ( data === undefined ) {
buffers.set( attribute, createBuffer( attribute, bufferType ) );
} else if ( data.version < attribute.version ) {
if ( data.size !== attribute.array.byteLength ) {
throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' );
}
updateBuffer( data.buffer, attribute, bufferType );
data.version = attribute.version;
}
}
return {
get: get,
remove: remove,
update: update
};
}
export { WebGLAttributes };