|
|
@@ -739,6 +739,115 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function getRow( index, rowLength, componentStride ) {
|
|
|
+
|
|
|
+ return Math.floor( Math.floor( index / componentStride ) / rowLength );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateTexture( texture, image, glFormat, glType ) {
|
|
|
+
|
|
|
+ const componentStride = 4; // only RGBA supported
|
|
|
+
|
|
|
+ const updateRanges = texture.updateRanges;
|
|
|
+
|
|
|
+ if ( updateRanges.length === 0 ) {
|
|
|
+
|
|
|
+ state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Before applying update ranges, we merge any adjacent / overlapping
|
|
|
+ // ranges to reduce load on `gl.texSubImage2D`. 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 ];
|
|
|
+
|
|
|
+ // Only merge if in the same row and overlapping/adjacent
|
|
|
+ const previousEnd = previousRange.start + previousRange.count;
|
|
|
+ const currentRow = getRow( range.start, image.width, componentStride );
|
|
|
+ const previousRow = getRow( previousRange.start, image.width, componentStride );
|
|
|
+
|
|
|
+ // We add one here to merge adjacent ranges. This is safe because ranges
|
|
|
+ // operate over positive integers.
|
|
|
+ if (
|
|
|
+ range.start <= previousEnd + 1 &&
|
|
|
+ currentRow === previousRow &&
|
|
|
+ getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill
|
|
|
+ ) {
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH );
|
|
|
+ const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS );
|
|
|
+ const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS );
|
|
|
+
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width );
|
|
|
+
|
|
|
+ for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
|
|
|
+
|
|
|
+ const range = updateRanges[ i ];
|
|
|
+
|
|
|
+ const pixelStart = Math.floor( range.start / componentStride );
|
|
|
+ const pixelCount = Math.ceil( range.count / componentStride );
|
|
|
+
|
|
|
+ const x = pixelStart % image.width;
|
|
|
+ const y = Math.floor( pixelStart / image.width );
|
|
|
+
|
|
|
+ // Assumes update ranges refer to contiguous memory
|
|
|
+ const width = pixelCount;
|
|
|
+ const height = 1;
|
|
|
+
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x );
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y );
|
|
|
+
|
|
|
+ state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ texture.clearUpdateRanges();
|
|
|
+
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen );
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels );
|
|
|
+ _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
function uploadTexture( textureProperties, texture, slot ) {
|
|
|
|
|
|
let textureType = _gl.TEXTURE_2D;
|
|
|
@@ -852,7 +961,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
|
|
|
|
|
|
if ( dataReady ) {
|
|
|
|
|
|
- state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
|
|
|
+ updateTexture( texture, image, glFormat, glType );
|
|
|
|
|
|
}
|
|
|
|