Skip to content

Commit e7e83ce

Browse files
CodyJasonBennettRuthySheffi
authored andcommitted
Texture: Add updateRanges. (mrdoob#30998)
* Texture: Add updateRanges. * BatchedMesh: Use updateRanges API for data textures. * WebGLTextures: Set row length. * WebGLTextures: Clear update ranges. * WebGLTextures: Merge contiguous update ranges. * WebGLTextures: Cleanup. * WebGLTextures: Cleanup. * BatchedMesh: Revert changes.
1 parent 43eec9f commit e7e83ce

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

src/renderers/webgl/WebGLTextures.js

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,115 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
739739

740740
}
741741

742+
function getRow( index, rowLength, componentStride ) {
743+
744+
return Math.floor( Math.floor( index / componentStride ) / rowLength );
745+
746+
}
747+
748+
function updateTexture( texture, image, glFormat, glType ) {
749+
750+
const componentStride = 4; // only RGBA supported
751+
752+
const updateRanges = texture.updateRanges;
753+
754+
if ( updateRanges.length === 0 ) {
755+
756+
state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
757+
758+
} else {
759+
760+
// Before applying update ranges, we merge any adjacent / overlapping
761+
// ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led
762+
// to performance improvements for applications which make heavy use of
763+
// update ranges. Likely due to GPU command overhead.
764+
//
765+
// Note that to reduce garbage collection between frames, we merge the
766+
// update ranges in-place. This is safe because this method will clear the
767+
// update ranges once updated.
768+
769+
updateRanges.sort( ( a, b ) => a.start - b.start );
770+
771+
// To merge the update ranges in-place, we work from left to right in the
772+
// existing updateRanges array, merging ranges. This may result in a final
773+
// array which is smaller than the original. This index tracks the last
774+
// index representing a merged range, any data after this index can be
775+
// trimmed once the merge algorithm is completed.
776+
let mergeIndex = 0;
777+
778+
for ( let i = 1; i < updateRanges.length; i ++ ) {
779+
780+
const previousRange = updateRanges[ mergeIndex ];
781+
const range = updateRanges[ i ];
782+
783+
// Only merge if in the same row and overlapping/adjacent
784+
const previousEnd = previousRange.start + previousRange.count;
785+
const currentRow = getRow( range.start, image.width, componentStride );
786+
const previousRow = getRow( previousRange.start, image.width, componentStride );
787+
788+
// We add one here to merge adjacent ranges. This is safe because ranges
789+
// operate over positive integers.
790+
if (
791+
range.start <= previousEnd + 1 &&
792+
currentRow === previousRow &&
793+
getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill
794+
) {
795+
796+
previousRange.count = Math.max(
797+
previousRange.count,
798+
range.start + range.count - previousRange.start
799+
);
800+
801+
} else {
802+
803+
++ mergeIndex;
804+
updateRanges[ mergeIndex ] = range;
805+
806+
}
807+
808+
809+
}
810+
811+
// Trim the array to only contain the merged ranges.
812+
updateRanges.length = mergeIndex + 1;
813+
814+
const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH );
815+
const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS );
816+
const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS );
817+
818+
_gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width );
819+
820+
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
821+
822+
const range = updateRanges[ i ];
823+
824+
const pixelStart = Math.floor( range.start / componentStride );
825+
const pixelCount = Math.ceil( range.count / componentStride );
826+
827+
const x = pixelStart % image.width;
828+
const y = Math.floor( pixelStart / image.width );
829+
830+
// Assumes update ranges refer to contiguous memory
831+
const width = pixelCount;
832+
const height = 1;
833+
834+
_gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x );
835+
_gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y );
836+
837+
state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data );
838+
839+
}
840+
841+
texture.clearUpdateRanges();
842+
843+
_gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen );
844+
_gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels );
845+
_gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows );
846+
847+
}
848+
849+
}
850+
742851
function uploadTexture( textureProperties, texture, slot ) {
743852

744853
let textureType = _gl.TEXTURE_2D;
@@ -852,7 +961,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
852961

853962
if ( dataReady ) {
854963

855-
state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
964+
updateTexture( texture, image, glFormat, glType );
856965

857966
}
858967

src/textures/Texture.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ class Texture extends EventDispatcher {
305305
*/
306306
this.userData = {};
307307

308+
/**
309+
* This can be used to only update a subregion or specific rows of the texture (for example, just the
310+
* first 3 rows). Use the `addUpdateRange()` function to add ranges to this array.
311+
*
312+
* @type {Array<Object>}
313+
*/
314+
this.updateRanges = [];
315+
308316
/**
309317
* This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`.
310318
*
@@ -415,6 +423,27 @@ class Texture extends EventDispatcher {
415423

416424
}
417425

426+
/**
427+
* Adds a range of data in the data texture to be updated on the GPU.
428+
*
429+
* @param {number} start - Position at which to start update.
430+
* @param {number} count - The number of components to update.
431+
*/
432+
addUpdateRange( start, count ) {
433+
434+
this.updateRanges.push( { start, count } );
435+
436+
}
437+
438+
/**
439+
* Clears the update ranges.
440+
*/
441+
clearUpdateRanges() {
442+
443+
this.updateRanges.length = 0;
444+
445+
}
446+
418447
/**
419448
* Returns a new texture with copied values from this instance.
420449
*

0 commit comments

Comments
 (0)