@@ -7139,6 +7139,14 @@ class Texture extends EventDispatcher {
7139
7139
*/
7140
7140
this.userData = {};
7141
7141
7142
+ /**
7143
+ * This can be used to only update a subregion or specific rows of the texture (for example, just the
7144
+ * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array.
7145
+ *
7146
+ * @type {Array<Object>}
7147
+ */
7148
+ this.updateRanges = [];
7149
+
7142
7150
/**
7143
7151
* This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`.
7144
7152
*
@@ -7249,6 +7257,27 @@ class Texture extends EventDispatcher {
7249
7257
7250
7258
}
7251
7259
7260
+ /**
7261
+ * Adds a range of data in the data texture to be updated on the GPU.
7262
+ *
7263
+ * @param {number} start - Position at which to start update.
7264
+ * @param {number} count - The number of components to update.
7265
+ */
7266
+ addUpdateRange( start, count ) {
7267
+
7268
+ this.updateRanges.push( { start, count } );
7269
+
7270
+ }
7271
+
7272
+ /**
7273
+ * Clears the update ranges.
7274
+ */
7275
+ clearUpdateRanges() {
7276
+
7277
+ this.updateRanges.length = 0;
7278
+
7279
+ }
7280
+
7252
7281
/**
7253
7282
* Returns a new texture with copied values from this instance.
7254
7283
*
@@ -20120,6 +20149,15 @@ class Mesh extends Object3D {
20120
20149
*/
20121
20150
this.morphTargetInfluences = undefined;
20122
20151
20152
+ /**
20153
+ * The number of instances of this mesh.
20154
+ * Can only be used with {@link WebGPURenderer}.
20155
+ *
20156
+ * @type {number}
20157
+ * @default 1
20158
+ */
20159
+ this.count = 1;
20160
+
20123
20161
this.updateMorphTargets();
20124
20162
20125
20163
}
@@ -24069,6 +24107,15 @@ class Sprite extends Object3D {
24069
24107
*/
24070
24108
this.center = new Vector2( 0.5, 0.5 );
24071
24109
24110
+ /**
24111
+ * The number of instances of this sprite.
24112
+ * Can only be used with {@link WebGPURenderer}.
24113
+ *
24114
+ * @type {number}
24115
+ * @default 1
24116
+ */
24117
+ this.count = 1;
24118
+
24072
24119
}
24073
24120
24074
24121
/**
@@ -48399,6 +48446,8 @@ const TEXTURE_FILTER = {
48399
48446
LinearMipmapLinearFilter: LinearMipmapLinearFilter
48400
48447
};
48401
48448
48449
+ const _errorMap = new WeakMap();
48450
+
48402
48451
/**
48403
48452
* A loader for loading images as an [ImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap}.
48404
48453
* An `ImageBitmap` provides an asynchronous and resource efficient pathway to prepare
@@ -48409,7 +48458,7 @@ const TEXTURE_FILTER = {
48409
48458
*
48410
48459
* You need to set the equivalent options via {@link ImageBitmapLoader#setOptions} instead.
48411
48460
*
48412
- * Also note that unlike {@link FileLoader}, this loader does not avoid multiple concurrent requests to the same URL.
48461
+ * Also note that unlike {@link FileLoader}, this loader avoids multiple concurrent requests to the same URL only if `Cache` is enabled .
48413
48462
*
48414
48463
* ```js
48415
48464
* const loader = new THREE.ImageBitmapLoader();
@@ -48509,15 +48558,27 @@ class ImageBitmapLoader extends Loader {
48509
48558
48510
48559
cached.then( imageBitmap => {
48511
48560
48512
- if ( onLoad ) onLoad( imageBitmap );
48561
+ // check if there is an error for the cached promise
48562
+
48563
+ if ( _errorMap.has( cached ) === true ) {
48564
+
48565
+ if ( onError ) onError( _errorMap.get( cached ) );
48513
48566
48514
- scope.manager.itemEnd( url );
48567
+ scope.manager.itemError( url );
48568
+ scope.manager.itemEnd( url );
48515
48569
48516
- } ).catch( e => {
48570
+ } else {
48571
+
48572
+ if ( onLoad ) onLoad( imageBitmap );
48573
+
48574
+ scope.manager.itemEnd( url );
48575
+
48576
+ return imageBitmap;
48517
48577
48518
- if ( onError ) onError( e );
48578
+ }
48519
48579
48520
48580
} );
48581
+
48521
48582
return;
48522
48583
48523
48584
}
@@ -48561,6 +48622,8 @@ class ImageBitmapLoader extends Loader {
48561
48622
48562
48623
if ( onError ) onError( e );
48563
48624
48625
+ _errorMap.set( promise, e );
48626
+
48564
48627
Cache.remove( url );
48565
48628
48566
48629
scope.manager.itemError( url );
@@ -68784,6 +68847,115 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
68784
68847
68785
68848
}
68786
68849
68850
+ function getRow( index, rowLength, componentStride ) {
68851
+
68852
+ return Math.floor( Math.floor( index / componentStride ) / rowLength );
68853
+
68854
+ }
68855
+
68856
+ function updateTexture( texture, image, glFormat, glType ) {
68857
+
68858
+ const componentStride = 4; // only RGBA supported
68859
+
68860
+ const updateRanges = texture.updateRanges;
68861
+
68862
+ if ( updateRanges.length === 0 ) {
68863
+
68864
+ state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
68865
+
68866
+ } else {
68867
+
68868
+ // Before applying update ranges, we merge any adjacent / overlapping
68869
+ // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led
68870
+ // to performance improvements for applications which make heavy use of
68871
+ // update ranges. Likely due to GPU command overhead.
68872
+ //
68873
+ // Note that to reduce garbage collection between frames, we merge the
68874
+ // update ranges in-place. This is safe because this method will clear the
68875
+ // update ranges once updated.
68876
+
68877
+ updateRanges.sort( ( a, b ) => a.start - b.start );
68878
+
68879
+ // To merge the update ranges in-place, we work from left to right in the
68880
+ // existing updateRanges array, merging ranges. This may result in a final
68881
+ // array which is smaller than the original. This index tracks the last
68882
+ // index representing a merged range, any data after this index can be
68883
+ // trimmed once the merge algorithm is completed.
68884
+ let mergeIndex = 0;
68885
+
68886
+ for ( let i = 1; i < updateRanges.length; i ++ ) {
68887
+
68888
+ const previousRange = updateRanges[ mergeIndex ];
68889
+ const range = updateRanges[ i ];
68890
+
68891
+ // Only merge if in the same row and overlapping/adjacent
68892
+ const previousEnd = previousRange.start + previousRange.count;
68893
+ const currentRow = getRow( range.start, image.width, componentStride );
68894
+ const previousRow = getRow( previousRange.start, image.width, componentStride );
68895
+
68896
+ // We add one here to merge adjacent ranges. This is safe because ranges
68897
+ // operate over positive integers.
68898
+ if (
68899
+ range.start <= previousEnd + 1 &&
68900
+ currentRow === previousRow &&
68901
+ getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill
68902
+ ) {
68903
+
68904
+ previousRange.count = Math.max(
68905
+ previousRange.count,
68906
+ range.start + range.count - previousRange.start
68907
+ );
68908
+
68909
+ } else {
68910
+
68911
+ ++ mergeIndex;
68912
+ updateRanges[ mergeIndex ] = range;
68913
+
68914
+ }
68915
+
68916
+
68917
+ }
68918
+
68919
+ // Trim the array to only contain the merged ranges.
68920
+ updateRanges.length = mergeIndex + 1;
68921
+
68922
+ const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH );
68923
+ const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS );
68924
+ const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS );
68925
+
68926
+ _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width );
68927
+
68928
+ for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
68929
+
68930
+ const range = updateRanges[ i ];
68931
+
68932
+ const pixelStart = Math.floor( range.start / componentStride );
68933
+ const pixelCount = Math.ceil( range.count / componentStride );
68934
+
68935
+ const x = pixelStart % image.width;
68936
+ const y = Math.floor( pixelStart / image.width );
68937
+
68938
+ // Assumes update ranges refer to contiguous memory
68939
+ const width = pixelCount;
68940
+ const height = 1;
68941
+
68942
+ _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x );
68943
+ _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y );
68944
+
68945
+ state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data );
68946
+
68947
+ }
68948
+
68949
+ texture.clearUpdateRanges();
68950
+
68951
+ _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen );
68952
+ _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels );
68953
+ _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows );
68954
+
68955
+ }
68956
+
68957
+ }
68958
+
68787
68959
function uploadTexture( textureProperties, texture, slot ) {
68788
68960
68789
68961
let textureType = _gl.TEXTURE_2D;
@@ -68897,7 +69069,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
68897
69069
68898
69070
if ( dataReady ) {
68899
69071
68900
- state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data );
69072
+ updateTexture( texture, image, glFormat, glType );
68901
69073
68902
69074
}
68903
69075
0 commit comments