Skip to content

Commit 8ad5f7c

Browse files
authored
WebGPURenderer: Introduce ReadbackBuffer and reuse getArrayBufferAsync() buffers (#33300)
1 parent 3643961 commit 8ad5f7c

12 files changed

Lines changed: 262 additions & 31 deletions

File tree

examples/jsm/inspector/tabs/Memory.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class Memory extends Tab {
5858
this.programs = new Item( 'Programs', createValueSpan(), createValueSpan() );
5959
this.memoryStats.add( this.programs );
6060

61+
this.readbackBuffers = new Item( 'Readback Buffers', createValueSpan(), createValueSpan() );
62+
this.memoryStats.add( this.readbackBuffers );
63+
6164
this.renderTargets = new Item( 'Render Targets', createValueSpan(), 'N/A' );
6265
this.memoryStats.add( this.renderTargets );
6366

@@ -108,6 +111,9 @@ class Memory extends Tab {
108111
setText( this.programs.data[ 1 ], memory.programs.toString() );
109112
setText( this.programs.data[ 2 ], formatBytes( memory.programsSize ) );
110113

114+
setText( this.readbackBuffers.data[ 1 ], memory.readbackBuffers.toString() );
115+
setText( this.readbackBuffers.data[ 2 ], formatBytes( memory.readbackBuffersSize ) );
116+
111117
setText( this.renderTargets.data[ 1 ], memory.renderTargets.toString() );
112118

113119
setText( this.storageAttributes.data[ 1 ], memory.storageAttributes.toString() );

examples/webgpu_compute_reduce.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,15 @@ <h3 id="panel-title" style="flex: 0 0 auto;">Subgroup Reduction Explanation</h3>
963963
functionObj[ logFunctionName ] = async() => {
964964

965965
const selectedBuffer = buffers[ unifiedEffectController.loggedBuffer ];
966-
console.log( new Uint32Array( await renderer.getArrayBufferAsync( selectedBuffer.value ) ) );
966+
const readbackBuffer = new THREE.ReadbackBuffer( selectedBuffer.value );
967+
968+
const result = new Uint32Array( await renderer.getArrayBufferAsync( readbackBuffer ) );
969+
970+
console.log( result );
971+
972+
// Remove GPU/CPU readback buffer from memory
973+
974+
readbackBuffer.dispose();
967975

968976
};
969977

src/Three.WebGPU.Nodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { default as QuadMesh } from './renderers/common/QuadMesh.js';
1010
export { default as PMREMGenerator } from './renderers/common/extras/PMREMGenerator.js';
1111
export { default as RenderPipeline } from './renderers/common/RenderPipeline.js';
1212
export { default as PostProcessing } from './renderers/common/PostProcessing.js';
13+
export { default as ReadbackBuffer } from './renderers/common/ReadbackBuffer.js';
1314
import * as RendererUtils from './renderers/common/RendererUtils.js';
1415
export { RendererUtils };
1516
export { default as StorageTexture } from './renderers/common/StorageTexture.js';

src/Three.WebGPU.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { default as QuadMesh } from './renderers/common/QuadMesh.js';
1111
export { default as PMREMGenerator } from './renderers/common/extras/PMREMGenerator.js';
1212
export { default as RenderPipeline } from './renderers/common/RenderPipeline.js';
1313
export { default as PostProcessing } from './renderers/common/PostProcessing.js';
14+
export { default as ReadbackBuffer } from './renderers/common/ReadbackBuffer.js';
1415
import * as RendererUtils from './renderers/common/RendererUtils.js';
1516
export { RendererUtils };
1617
export { default as CubeRenderTarget } from './renderers/common/CubeRenderTarget.js';

src/core/BufferAttribute.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Vector2 } from '../math/Vector2.js';
33
import { denormalize, normalize } from '../math/MathUtils.js';
44
import { StaticDrawUsage, FloatType } from '../constants.js';
55
import { fromHalfFloat, toHalfFloat } from '../extras/DataUtils.js';
6+
import { EventDispatcher } from './EventDispatcher.js';
67

78
const _vector = /*@__PURE__*/ new Vector3();
89
const _vector2 = /*@__PURE__*/ new Vector2();
@@ -17,7 +18,7 @@ let _id = 0;
1718
* When working with vector-like data, the `fromBufferAttribute( attribute, index )`
1819
* helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}.
1920
*/
20-
class BufferAttribute {
21+
class BufferAttribute extends EventDispatcher {
2122

2223
/**
2324
* Constructs a new buffer attribute.
@@ -28,6 +29,8 @@ class BufferAttribute {
2829
*/
2930
constructor( array, itemSize, normalized = false ) {
3031

32+
super();
33+
3134
if ( Array.isArray( array ) ) {
3235

3336
throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
@@ -674,6 +677,15 @@ class BufferAttribute {
674677

675678
}
676679

680+
/**
681+
* Disposes of the buffer attribute. Available only in {@link WebGPURenderer}.
682+
*/
683+
dispose() {
684+
685+
this.dispatchEvent( { type: 'dispose' } );
686+
687+
}
688+
677689
}
678690

679691
/**

src/renderers/common/Info.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class Info {
100100
* @property {number} indexAttributes - The number of active index attributes.
101101
* @property {number} storageAttributes - The number of active storage attributes.
102102
* @property {number} indirectStorageAttributes - The number of active indirect storage attributes.
103+
* @property {number} readbackBuffers - The number of active readback buffers.
103104
* @property {number} programs - The number of active programs.
104105
* @property {number} renderTargets - The number of active renderTargets.
105106
* @property {number} total - The total memory size in bytes.
@@ -108,6 +109,7 @@ class Info {
108109
* @property {number} indexAttributesSize - The memory size of active index attributes in bytes.
109110
* @property {number} storageAttributesSize - The memory size of active storage attributes in bytes.
110111
* @property {number} indirectStorageAttributesSize - The memory size of active indirect storage attributes in bytes.
112+
* @property {number} readbackBuffersSize - The memory size of active readback buffers in bytes.
111113
* @property {number} programsSize - The memory size of active programs in bytes.
112114
*/
113115
this.memory = {
@@ -117,6 +119,7 @@ class Info {
117119
indexAttributes: 0,
118120
storageAttributes: 0,
119121
indirectStorageAttributes: 0,
122+
readbackBuffers: 0,
120123
programs: 0,
121124
renderTargets: 0,
122125
total: 0,
@@ -125,6 +128,7 @@ class Info {
125128
indexAttributesSize: 0,
126129
storageAttributesSize: 0,
127130
indirectStorageAttributesSize: 0,
131+
readbackBuffersSize: 0,
128132
programsSize: 0
129133
};
130134

@@ -329,6 +333,33 @@ class Info {
329333

330334
}
331335

336+
/**
337+
* Tracks a readback buffer memory explicitly.
338+
*
339+
* @param {ReadbackBuffer} readbackBuffer - The readback buffer to track.
340+
*/
341+
createReadbackBuffer( readbackBuffer ) {
342+
343+
const size = this._getAttributeMemorySize( readbackBuffer.attribute );
344+
this.memoryMap.set( readbackBuffer, { size, type: 'readbackBuffers' } );
345+
346+
this.memory.readbackBuffers ++;
347+
this.memory.total += size;
348+
this.memory.readbackBuffersSize += size;
349+
350+
}
351+
352+
/**
353+
* Tracks a readback buffer memory explicitly.
354+
*
355+
* @param {ReadbackBuffer} readbackBuffer - The readback buffer to track.
356+
*/
357+
destroyReadbackBuffer( readbackBuffer ) {
358+
359+
this.destroyAttribute( readbackBuffer );
360+
361+
}
362+
332363
/**
333364
* Tracks program memory explicitly, updating counts and byte tracking.
334365
*
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { EventDispatcher } from '../../core/EventDispatcher.js';
2+
3+
/**
4+
* A readback buffer is used to transfer data from the GPU to the CPU.
5+
* It is primarily used to read back compute shader results.
6+
*
7+
* @augments EventDispatcher
8+
*/
9+
class ReadbackBuffer extends EventDispatcher {
10+
11+
/**
12+
* Constructs a new readback buffer.
13+
*
14+
* @param {BufferAttribute} attribute - The buffer attribute.
15+
*/
16+
constructor( attribute ) {
17+
18+
super();
19+
20+
/**
21+
* The buffer attribute.
22+
*
23+
* @type {BufferAttribute}
24+
*/
25+
this.attribute = attribute;
26+
27+
/**
28+
* This flag can be used for type testing.
29+
*
30+
* @type {boolean}
31+
* @readonly
32+
* @default true
33+
*/
34+
this.isReadbackBuffer = true;
35+
36+
}
37+
38+
/**
39+
* Releases the mapped buffer data so the GPU buffer can be
40+
* used by the GPU again.
41+
*
42+
* Note: Any `ArrayBuffer` data associated with this readback buffer
43+
* are removed and no longer accessible after calling this method.
44+
*/
45+
release() {
46+
47+
this.dispatchEvent( { type: 'release' } );
48+
49+
}
50+
51+
/**
52+
* Frees internal resources.
53+
*/
54+
dispose() {
55+
56+
this.dispatchEvent( { type: 'dispose' } );
57+
58+
}
59+
60+
}
61+
62+
export default ReadbackBuffer;

src/renderers/common/Renderer.js

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Lighting from './Lighting.js';
1919
import XRManager from './XRManager.js';
2020
import InspectorBase from './InspectorBase.js';
2121
import CanvasTarget from './CanvasTarget.js';
22+
import ReadbackBuffer from './ReadbackBuffer.js';
2223

2324
import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
2425

@@ -1918,12 +1919,61 @@ class Renderer {
19181919
* from the GPU to the CPU in context of compute shaders.
19191920
*
19201921
* @async
1921-
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
1922+
* @param {StorageBufferAttribute|ReadbackBuffer} buffer - The storage buffer attribute.
19221923
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
19231924
*/
1924-
async getArrayBufferAsync( attribute ) {
1925+
async getArrayBufferAsync( buffer ) {
19251926

1926-
return await this.backend.getArrayBufferAsync( attribute );
1927+
let readbackBuffer = buffer;
1928+
1929+
if ( readbackBuffer.isReadbackBuffer !== true ) {
1930+
1931+
const attribute = buffer;
1932+
const attributeData = this.backend.get( attribute );
1933+
1934+
readbackBuffer = attributeData.readbackBuffer;
1935+
1936+
if ( readbackBuffer === undefined ) {
1937+
1938+
readbackBuffer = new ReadbackBuffer( attribute );
1939+
1940+
const dispose = () => {
1941+
1942+
attribute.removeEventListener( 'dispose', dispose );
1943+
1944+
readbackBuffer.dispose();
1945+
1946+
delete attributeData.readbackBuffer;
1947+
1948+
};
1949+
1950+
attribute.addEventListener( 'dispose', dispose );
1951+
1952+
attributeData.readbackBuffer = readbackBuffer;
1953+
1954+
}
1955+
1956+
}
1957+
1958+
if ( this.info.memoryMap.has( readbackBuffer ) === false ) {
1959+
1960+
this.info.createReadbackBuffer( readbackBuffer );
1961+
1962+
const disposeInfo = () => {
1963+
1964+
readbackBuffer.removeEventListener( 'dispose', disposeInfo );
1965+
1966+
this.info.destroyReadbackBuffer( readbackBuffer );
1967+
1968+
};
1969+
1970+
readbackBuffer.addEventListener( 'dispose', disposeInfo );
1971+
1972+
}
1973+
1974+
readbackBuffer.release();
1975+
1976+
return await this.backend.getArrayBufferAsync( readbackBuffer );
19271977

19281978
}
19291979

src/renderers/webgl-fallback/WebGLBackend.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,12 @@ class WebGLBackend extends Backend {
312312
* a storage buffer attribute from the GPU to the CPU.
313313
*
314314
* @async
315-
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
315+
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
316316
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
317317
*/
318-
async getArrayBufferAsync( attribute ) {
318+
async getArrayBufferAsync( readbackBuffer ) {
319319

320-
return await this.attributeUtils.getArrayBufferAsync( attribute );
320+
return await this.attributeUtils.getArrayBufferAsync( readbackBuffer );
321321

322322
}
323323

src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,15 @@ class WebGLAttributeUtils {
257257
* a storage buffer attribute from the GPU to the CPU.
258258
*
259259
* @async
260-
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
260+
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
261261
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
262262
*/
263-
async getArrayBufferAsync( attribute ) {
263+
async getArrayBufferAsync( readbackBuffer ) {
264264

265265
const backend = this.backend;
266266
const { gl } = backend;
267267

268+
const attribute = readbackBuffer.attribute;
268269
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
269270
const { bufferGPU } = backend.get( bufferAttribute );
270271

@@ -273,10 +274,40 @@ class WebGLAttributeUtils {
273274

274275
gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );
275276

276-
const writeBuffer = gl.createBuffer();
277+
const readbackBufferData = backend.get( readbackBuffer );
277278

278-
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
279-
gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );
279+
let { writeBuffer } = readbackBufferData;
280+
281+
if ( writeBuffer === undefined ) {
282+
283+
writeBuffer = gl.createBuffer();
284+
285+
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
286+
gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );
287+
288+
// dispose
289+
290+
const dispose = () => {
291+
292+
gl.deleteBuffer( writeBuffer );
293+
294+
backend.delete( readbackBuffer );
295+
296+
readbackBuffer.removeEventListener( 'dispose', dispose );
297+
298+
};
299+
300+
readbackBuffer.addEventListener( 'dispose', dispose );
301+
302+
// register
303+
304+
readbackBufferData.writeBuffer = writeBuffer;
305+
306+
} else {
307+
308+
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
309+
310+
}
280311

281312
gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength );
282313

@@ -289,12 +320,10 @@ class WebGLAttributeUtils {
289320

290321
gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer );
291322

292-
gl.deleteBuffer( writeBuffer );
293-
294323
gl.bindBuffer( gl.COPY_READ_BUFFER, null );
295324
gl.bindBuffer( gl.COPY_WRITE_BUFFER, null );
296325

297-
return dstBuffer.buffer;
326+
return dstBuffer;
298327

299328
}
300329

0 commit comments

Comments
 (0)