updateTexture method

void updateTexture(
  1. Texture texture,
  2. ImageElement image,
  3. int glFormat,
  4. int glType,
)

Implementation

void updateTexture(Texture texture, ImageElement image, int glFormat, int glType ) {
	int componentStride = 4; // only RGBA supported
	final updateRanges = texture.updateRanges;

	if ( updateRanges.length == 0 ) {
		state.texSubImage2D( WebGL.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.
		int mergeIndex = 0;

		for (int i = 1; i < updateRanges.length; i ++ ) {
			final previousRange = updateRanges[ mergeIndex ];
			final range = updateRanges[ i ];

			// Only merge if in the same row and overlapping/adjacent
			final previousEnd = previousRange.start + previousRange.count;
			final currentRow = getRow( range.start, image.width, componentStride );
			final 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;

		final currentUnpackRowLen = _gl.getParameter( WebGL.UNPACK_ROW_LENGTH );
		final currentUnpackSkipPixels = _gl.getParameter( WebGL.UNPACK_SKIP_PIXELS );
		final currentUnpackSkipRows = _gl.getParameter( WebGL.UNPACK_SKIP_ROWS );

		_gl.pixelStorei( WebGL.UNPACK_ROW_LENGTH, image.width.toInt() );

		for (int i = 0, l = updateRanges.length; i < l; i ++ ) {
			final range = updateRanges[ i ];

			final pixelStart = ( range.start / componentStride ).floor();
			final pixelCount = ( range.count / componentStride ).ceil();

			final x = (pixelStart % image.width).toInt();
			final y = ( pixelStart / image.width ).floor();

			// Assumes update ranges refer to contiguous memory
			final width = pixelCount;
			final height = 1;

			_gl.pixelStorei( WebGL.UNPACK_SKIP_PIXELS, x );
			_gl.pixelStorei( WebGL.UNPACK_SKIP_ROWS, y );

			state.texSubImage2D( WebGL.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data );
		}

		texture.clearUpdateRanges();

		_gl.pixelStorei( WebGL.UNPACK_ROW_LENGTH, currentUnpackRowLen );
		_gl.pixelStorei( WebGL.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels );
		_gl.pixelStorei( WebGL.UNPACK_SKIP_ROWS, currentUnpackSkipRows );
	}
}