Skip to content

Commit 7b664f5

Browse files
authored
WebGPURenderer: Add update ranges for bindings (#32248)
1 parent 0757803 commit 7b664f5

File tree

8 files changed

+242
-44
lines changed

8 files changed

+242
-44
lines changed

src/animation/AnimationUtils.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Quaternion } from '../math/Quaternion.js';
22
import { AdditiveAnimationBlendMode } from '../constants.js';
3+
import { isTypedArray } from '../utils.js';
34

45
/**
56
* Converts an array to a specific type.
@@ -22,18 +23,6 @@ function convertArray( array, type ) {
2223

2324
}
2425

25-
/**
26-
* Returns `true` if the given object is a typed array.
27-
*
28-
* @param {any} object - The object to check.
29-
* @return {boolean} Whether the given object is a typed array.
30-
*/
31-
function isTypedArray( object ) {
32-
33-
return ArrayBuffer.isView( object ) && ! ( object instanceof DataView );
34-
35-
}
36-
3726
/**
3827
* Returns an array by which times and values can be sorted.
3928
*

src/nodes/accessors/BufferNode.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,34 @@ class BufferNode extends UniformNode {
5858
*/
5959
this.bufferCount = bufferCount;
6060

61+
/**
62+
* An array of update ranges.
63+
*
64+
* @type {Array<{start: number, count: number}>}
65+
*/
66+
this.updateRanges = [];
67+
68+
}
69+
70+
/**
71+
* Adds a range of data in the data array to be updated on the GPU.
72+
*
73+
* @param {number} start - Position at which to start update.
74+
* @param {number} count - The number of components to update.
75+
*/
76+
addUpdateRange( start, count ) {
77+
78+
this.updateRanges.push( { start, count } );
79+
80+
}
81+
82+
/**
83+
* Clears the update ranges.
84+
*/
85+
clearUpdateRanges() {
86+
87+
this.updateRanges.length = 0;
88+
6189
}
6290

6391
/**

src/renderers/common/Buffer.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,46 @@ class Buffer extends Binding {
4444
*/
4545
this._buffer = buffer;
4646

47+
/**
48+
* An array of update ranges.
49+
*
50+
* @private
51+
* @type {Array<{start: number, count: number}>}
52+
*/
53+
this._updateRanges = [];
54+
55+
}
56+
57+
/**
58+
* The array of update ranges.
59+
*
60+
* @type {Array<{start: number, count: number}>}
61+
*/
62+
get updateRanges() {
63+
64+
return this._updateRanges;
65+
66+
}
67+
68+
/**
69+
* Adds an update range.
70+
*
71+
* @param {number} start - The start index.
72+
* @param {number} count - The number of elements.
73+
*/
74+
addUpdateRange( start, count ) {
75+
76+
this.updateRanges.push( { start, count } );
77+
78+
}
79+
80+
/**
81+
* Clears all update ranges.
82+
*/
83+
clearUpdateRanges() {
84+
85+
this.updateRanges.length = 0;
86+
4787
}
4888

4989
/**

src/renderers/common/nodes/NodeUniformBuffer.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,58 @@ class NodeUniformBuffer extends UniformBuffer {
3535
*/
3636
this.groupNode = groupNode;
3737

38+
/**
39+
* This flag can be used for type testing.
40+
*
41+
* @type {boolean}
42+
* @readonly
43+
* @default true
44+
*/
45+
this.isNodeUniformBuffer = true;
46+
47+
}
48+
49+
/**
50+
* The array of update ranges.
51+
*
52+
* @param {Array<{start: number, count: number}>} value - The update ranges.
53+
*/
54+
set updateRanges( value ) {
55+
56+
this.nodeUniform.updateRanges = value;
57+
58+
}
59+
60+
/**
61+
* The array of update ranges.
62+
*
63+
* @type {Array<{start: number, count: number}>}
64+
*/
65+
get updateRanges() {
66+
67+
return this.nodeUniform.updateRanges;
68+
69+
}
70+
71+
/**
72+
* Adds a range of data in the data array to be updated on the GPU.
73+
*
74+
* @param {number} start - Position at which to start update.
75+
* @param {number} count - The number of components to update.
76+
*/
77+
addUpdateRange( start, count ) {
78+
79+
this.nodeUniform.addUpdateRange( start, count );
80+
81+
}
82+
83+
/**
84+
* Clears all update ranges.
85+
*/
86+
clearUpdateRanges() {
87+
88+
this.nodeUniform.clearUpdateRanges();
89+
3890
}
3991

4092
/**

src/renderers/webgl-fallback/WebGLBackend.js

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import WebGLCapabilities from './utils/WebGLCapabilities.js';
1111
import { GLFeatureName } from './utils/WebGLConstants.js';
1212
import { WebGLBufferRenderer } from './WebGLBufferRenderer.js';
1313

14-
import { warnOnce, warn, error } from '../../utils.js';
14+
import { isTypedArray, warnOnce, warn, error } from '../../utils.js';
1515
import { WebGLCoordinateSystem, TimestampQuery } from '../../constants.js';
1616
import WebGLTimestampQueryPool from './utils/WebGLTimestampQueryPool.js';
1717

@@ -1743,25 +1743,53 @@ class WebGLBackend extends Backend {
17431743

17441744
if ( binding.isUniformsGroup || binding.isUniformBuffer ) {
17451745

1746-
const data = binding.buffer;
1747-
let { bufferGPU } = this.get( data );
1746+
const array = binding.buffer;
1747+
let { bufferGPU } = this.get( array );
17481748

17491749
if ( bufferGPU === undefined ) {
17501750

17511751
// create
17521752

17531753
bufferGPU = gl.createBuffer();
1754+
17541755
gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
1755-
gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW );
1756+
gl.bufferData( gl.UNIFORM_BUFFER, array.byteLength, gl.DYNAMIC_DRAW );
17561757

1757-
this.set( data, { bufferGPU } );
1758+
this.set( array, { bufferGPU } );
17581759

17591760
} else {
17601761

1761-
// update
1762-
17631762
gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
1764-
gl.bufferSubData( gl.UNIFORM_BUFFER, 0, data );
1763+
1764+
}
1765+
1766+
// update
1767+
1768+
const updateRanges = binding.updateRanges;
1769+
1770+
gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
1771+
1772+
if ( updateRanges.length === 0 ) {
1773+
1774+
gl.bufferData( gl.UNIFORM_BUFFER, array, gl.DYNAMIC_DRAW );
1775+
1776+
} else {
1777+
1778+
const isTyped = isTypedArray( array );
1779+
const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
1780+
1781+
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
1782+
1783+
const range = updateRanges[ i ];
1784+
1785+
const dataOffset = range.start * byteOffsetFactor;
1786+
const size = range.count * byteOffsetFactor;
1787+
1788+
const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
1789+
1790+
gl.bufferSubData( gl.UNIFORM_BUFFER, bufferOffset, array, dataOffset, size );
1791+
1792+
}
17651793

17661794
}
17671795

@@ -1799,10 +1827,35 @@ class WebGLBackend extends Backend {
17991827

18001828
const bindingData = this.get( binding );
18011829
const bufferGPU = bindingData.bufferGPU;
1802-
const data = binding.buffer;
1830+
const array = binding.buffer;
1831+
1832+
const updateRanges = binding.updateRanges;
18031833

18041834
gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU );
1805-
gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW );
1835+
1836+
if ( updateRanges.length === 0 ) {
1837+
1838+
gl.bufferData( gl.UNIFORM_BUFFER, array, gl.DYNAMIC_DRAW );
1839+
1840+
} else {
1841+
1842+
const isTyped = isTypedArray( array );
1843+
const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
1844+
1845+
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
1846+
1847+
const range = updateRanges[ i ];
1848+
1849+
const dataOffset = range.start * byteOffsetFactor;
1850+
const size = range.count * byteOffsetFactor;
1851+
1852+
const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
1853+
1854+
gl.bufferSubData( gl.UNIFORM_BUFFER, bufferOffset, array, dataOffset, size );
1855+
1856+
}
1857+
1858+
}
18061859

18071860
}
18081861

src/renderers/webgpu/utils/WebGPUAttributeUtils.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GPUInputStepMode } from './WebGPUConstants.js';
22

33
import { Float16BufferAttribute } from '../../../core/BufferAttribute.js';
4-
import { error } from '../../../utils.js';
4+
import { isTypedArray, error } from '../../../utils.js';
55

66
const typedArraysToVertexFormatPrefix = new Map( [
77
[ Int8Array, [ 'sint8', 'snorm8' ]],
@@ -174,7 +174,6 @@ class WebGPUAttributeUtils {
174174
}
175175

176176

177-
const isTypedArray = this._isTypedArray( array );
178177
const updateRanges = bufferAttribute.updateRanges;
179178

180179
if ( updateRanges.length === 0 ) {
@@ -190,7 +189,8 @@ class WebGPUAttributeUtils {
190189

191190
} else {
192191

193-
const byteOffsetFactor = isTypedArray ? 1 : array.BYTES_PER_ELEMENT;
192+
const isTyped = isTypedArray( array );
193+
const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
194194

195195
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
196196

@@ -211,7 +211,7 @@ class WebGPUAttributeUtils {
211211

212212
}
213213

214-
const bufferOffset = dataOffset * ( isTypedArray ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
214+
const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
215215

216216
device.queue.writeBuffer(
217217
buffer,
@@ -415,19 +415,6 @@ class WebGPUAttributeUtils {
415415

416416
}
417417

418-
/**
419-
* Returns `true` if the given array is a typed array.
420-
*
421-
* @private
422-
* @param {any} array - The array.
423-
* @return {boolean} Whether the given array is a typed array or not.
424-
*/
425-
_isTypedArray( array ) {
426-
427-
return ArrayBuffer.isView( array ) && ! ( array instanceof DataView );
428-
429-
}
430-
431418
/**
432419
* Utility method for handling interleaved buffer attributes correctly.
433420
* To process them, their `InterleavedBuffer` is returned.

src/renderers/webgpu/utils/WebGPUBindingUtils.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55

66
import { FloatType, IntType, UnsignedIntType } from '../../../constants.js';
77
import { NodeAccess } from '../../../nodes/core/constants.js';
8-
import { error } from '../../../utils.js';
8+
import { isTypedArray, error } from '../../../utils.js';
99

1010
/**
1111
* A WebGPU backend utility module for managing bindings.
@@ -306,10 +306,47 @@ class WebGPUBindingUtils {
306306
const backend = this.backend;
307307
const device = backend.device;
308308

309-
const buffer = binding.buffer;
310-
const bufferGPU = backend.get( binding ).buffer;
309+
const array = binding.buffer; // cpu
310+
const buffer = backend.get( binding ).buffer; // gpu
311311

312-
device.queue.writeBuffer( bufferGPU, 0, buffer, 0 );
312+
const updateRanges = binding.updateRanges;
313+
314+
if ( updateRanges.length === 0 ) {
315+
316+
device.queue.writeBuffer(
317+
buffer,
318+
0,
319+
array,
320+
0
321+
);
322+
323+
} else {
324+
325+
const isTyped = isTypedArray( array );
326+
const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
327+
328+
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
329+
330+
const range = updateRanges[ i ];
331+
332+
const dataOffset = range.start * byteOffsetFactor;
333+
const size = range.count * byteOffsetFactor;
334+
335+
const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
336+
337+
device.queue.writeBuffer(
338+
buffer,
339+
bufferOffset,
340+
array,
341+
dataOffset,
342+
size
343+
);
344+
345+
}
346+
347+
binding.clearUpdateRanges();
348+
349+
}
313350

314351
}
315352

0 commit comments

Comments
 (0)