Skip to content

Texture: Add updateRanges. #30998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 110 additions & 1 deletion src/renderers/webgl/WebGLTextures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );

}

Expand Down
29 changes: 29 additions & 0 deletions src/textures/Texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,14 @@ class Texture extends EventDispatcher {
*/
this.userData = {};

/**
* This can be used to only update a subregion or specific rows of the texture (for example, just the
* first 3 rows). Use the `addUpdateRange()` function to add ranges to this array.
*
* @type {Array<Object>}
*/
this.updateRanges = [];

/**
* This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`.
*
Expand Down Expand Up @@ -385,6 +393,27 @@ class Texture extends EventDispatcher {

}

/**
* Adds a range of data in the data texture to be updated on the GPU.
*
* @param {number} start - Position at which to start update.
* @param {number} count - The number of components to update.
*/
addUpdateRange( start, count ) {

this.updateRanges.push( { start, count } );

}

/**
* Clears the update ranges.
*/
clearUpdateRanges() {

this.updateRanges.length = 0;

}

/**
* Returns a new texture with copied values from this instance.
*
Expand Down