Skip to content
Closed
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
4 changes: 2 additions & 2 deletions examples/webgpu_compute_reduce.html
Original file line number Diff line number Diff line change
Expand Up @@ -963,9 +963,9 @@ <h3 id="panel-title" style="flex: 0 0 auto;">Subgroup Reduction Explanation</h3>
functionObj[ logFunctionName ] = async() => {

const selectedBuffer = buffers[ unifiedEffectController.loggedBuffer ];
const readbackBuffer = new THREE.ReadbackBuffer( selectedBuffer.value );
const readbackBuffer = new THREE.ReadbackBuffer( selectedBuffer.value.array.length );

const result = new Uint32Array( await renderer.getArrayBufferAsync( readbackBuffer ) );
const result = new Uint32Array( await renderer.getArrayBufferAsync( selectedBuffer.value, readbackBuffer ) );

console.log( result );

Expand Down
2 changes: 1 addition & 1 deletion src/renderers/common/Info.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class Info {
*/
createReadbackBuffer( readbackBuffer ) {

const size = this._getAttributeMemorySize( readbackBuffer.attribute );
const size = readbackBuffer.size;
this.memoryMap.set( readbackBuffer, { size, type: 'readbackBuffers' } );

this.memory.readbackBuffers ++;
Expand Down
39 changes: 21 additions & 18 deletions src/renderers/common/ReadbackBuffer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { EventDispatcher } from '../../core/EventDispatcher.js';

let _id = 0;

/**
* A readback buffer is used to transfer data from the GPU to the CPU.
* It is primarily used to read back compute shader results.
Expand All @@ -11,18 +13,32 @@ class ReadbackBuffer extends EventDispatcher {
/**
* Constructs a new readback buffer.
*
* @param {BufferAttribute} attribute - The buffer attribute.
* @param {BufferAttribute} size - The buffer attribute.
*/
constructor( attribute ) {
constructor( size ) {

super();

/**
* The buffer attribute.
* The size of the buffer in bytes.
*
* @type {number}
*/
this.size = size;

/**
* A unique identifier for this readback buffer.
*
* @type {number}
*/
this.id = _id ++;

/**
* A name for this readback buffer.
*
* @type {BufferAttribute}
* @type {string}
*/
this.attribute = attribute;
this.name = '';

/**
* This flag can be used for type testing.
Expand All @@ -35,19 +51,6 @@ class ReadbackBuffer extends EventDispatcher {

}

/**
* Releases the mapped buffer data so the GPU buffer can be
* used by the GPU again.
*
* Note: Any `ArrayBuffer` data associated with this readback buffer
* are removed and no longer accessible after calling this method.
*/
release() {

this.dispatchEvent( { type: 'release' } );

}

/**
* Frees internal resources.
*/
Expand Down
57 changes: 48 additions & 9 deletions src/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1919,23 +1919,26 @@ class Renderer {
* from the GPU to the CPU in context of compute shaders.
*
* @async
* @param {StorageBufferAttribute|ReadbackBuffer} buffer - The storage buffer attribute.
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
* @param {ReadbackBuffer} [readbackBuffer=null] - The readback buffer.
* @param {number} [offset=0] - The offset in bytes.
* @param {number} [size=null] - The size in bytes.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( buffer ) {
async getArrayBufferAsync( attribute, readbackBuffer = null, offset = 0, size = null ) {

let readbackBuffer = buffer;
if ( readbackBuffer === null ) {

if ( readbackBuffer.isReadbackBuffer !== true ) {

const attribute = buffer;
const attributeData = this.backend.get( attribute );

readbackBuffer = attributeData.readbackBuffer;

if ( readbackBuffer === undefined ) {

readbackBuffer = new ReadbackBuffer( attribute );
const byteLength = attribute.array.byteLength;

readbackBuffer = new ReadbackBuffer( byteLength );
readbackBuffer.name = attribute.name;

const dispose = () => {

Expand Down Expand Up @@ -1971,9 +1974,45 @@ class Renderer {

}

readbackBuffer.release();
if ( size === null ) {

size = attribute.array.byteLength - offset;

}

const readbackBufferData = this.backend.get( readbackBuffer );

if ( readbackBufferData.running ) {

warn( 'Renderer: "ReadbackBuffer" is already in use. To obtain multiple acquisitions in parallel of the same attribute, use instances of "ReadbackBuffer".' );

return new ArrayBuffer();

}

readbackBufferData.running = true;

const attributeByteLength = attribute.array.byteLength;

if ( offset + size > attributeByteLength ) {

warn( 'Renderer: The given offset and size exceed the attribute size.' );
size = attributeByteLength - offset;

}

if ( size > readbackBuffer.size ) {

warn( 'Renderer: "ReadbackBuffer" size is not compatible with the selected attribute range.' );
size = readbackBuffer.size;

}

const arrayBuffer = await this.backend.getArrayBufferAsync( attribute, readbackBuffer, offset, size );

readbackBufferData.running = false;

return await this.backend.getArrayBufferAsync( readbackBuffer );
return arrayBuffer;

}

Expand Down
7 changes: 5 additions & 2 deletions src/renderers/webgl-fallback/WebGLBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,15 @@ class WebGLBackend extends Backend {
* a storage buffer attribute from the GPU to the CPU.
*
* @async
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @param {number} offset - The offset in bytes.
* @param {number} size - The size in bytes.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, readbackBuffer, offset, size ) {

return await this.attributeUtils.getArrayBufferAsync( readbackBuffer );
return await this.attributeUtils.getArrayBufferAsync( attribute, readbackBuffer, offset, size );

}

Expand Down
17 changes: 9 additions & 8 deletions src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,21 +257,20 @@ class WebGLAttributeUtils {
* a storage buffer attribute from the GPU to the CPU.
*
* @async
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @param {number} offset - The offset in bytes.
* @param {number} size - The size in bytes.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, readbackBuffer, offset, size ) {

const backend = this.backend;
const { gl } = backend;

const attribute = readbackBuffer.attribute;
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
const { bufferGPU } = backend.get( bufferAttribute );

const array = attribute.array;
const byteLength = array.byteLength;

gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );

const readbackBufferData = backend.get( readbackBuffer );
Expand All @@ -280,6 +279,8 @@ class WebGLAttributeUtils {

if ( writeBuffer === undefined ) {

const byteLength = readbackBuffer.size;

writeBuffer = gl.createBuffer();

gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
Expand Down Expand Up @@ -309,11 +310,11 @@ class WebGLAttributeUtils {

}

gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength );
gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, offset, 0, size );

await backend.utils._clientWaitAsync();

const dstBuffer = new attribute.array.constructor( array.length );
const dstBuffer = new Uint8Array( size );

// Ensure the buffer is bound before reading
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
Expand All @@ -323,7 +324,7 @@ class WebGLAttributeUtils {
gl.bindBuffer( gl.COPY_READ_BUFFER, null );
gl.bindBuffer( gl.COPY_WRITE_BUFFER, null );

return dstBuffer;
return dstBuffer.buffer;

}

Expand Down
7 changes: 5 additions & 2 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,15 @@ class WebGPUBackend extends Backend {
* a storage buffer attribute from the GPU to the CPU.
*
* @async
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @param {number} offset - The offset in bytes.
* @param {number} size - The size in bytes.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, readbackBuffer, offset, size ) {

return await this.attributeUtils.getArrayBufferAsync( readbackBuffer );
return await this.attributeUtils.getArrayBufferAsync( attribute, readbackBuffer, offset, size );

}

Expand Down
38 changes: 21 additions & 17 deletions src/renderers/webgpu/utils/WebGPUAttributeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,51 +318,49 @@ class WebGPUAttributeUtils {
* a storage buffer attribute from the GPU to the CPU.
*
* @async
* @param {StorageBufferAttribute} attribute - The storage buffer attribute.
* @param {ReadbackBuffer} readbackBuffer - The storage buffer attribute.
* @param {number} offset - The offset in bytes.
* @param {number} size - The size in bytes.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, readbackBuffer, offset, size ) {

const backend = this.backend;
const device = backend.device;
const attribute = readbackBuffer.attribute;

const data = backend.get( this._getBufferAttribute( attribute ) );
const bufferGPU = data.buffer;
const size = bufferGPU.size;

const readbackBufferData = backend.get( readbackBuffer );

const name = `readback_${ readbackBuffer.id || readbackBuffer.name }`;

let { readBufferGPU } = readbackBufferData;

if ( readBufferGPU === undefined ) {

const bufferSize = readbackBuffer.size + 4; // 4 extra bytes to accommodate unaligned offsets
const alignedBufferSize = bufferSize + ( ( 4 - ( bufferSize % 4 ) ) % 4 );

readBufferGPU = device.createBuffer( {
label: `${ attribute.name }_readback`,
size,
label: `${ name }_readback`,
size: alignedBufferSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
} );

// release / dispose

const release = () => {

readBufferGPU.unmap();

};

const dispose = () => {

readBufferGPU.destroy();

backend.delete( readbackBuffer );

readbackBuffer.removeEventListener( 'release', release );
readbackBuffer.removeEventListener( 'dispose', dispose );

};

readbackBuffer.addEventListener( 'release', release );
readbackBuffer.addEventListener( 'dispose', dispose );

// register
Expand All @@ -372,23 +370,29 @@ class WebGPUAttributeUtils {
}

const cmdEncoder = device.createCommandEncoder( {
label: `readback_encoder_${ attribute.name }`
label: `readback_encoder_${ name }`
} );

const diff = offset % 4;
const alignedOffset = offset - diff;
const alignedCopySize = ( size + diff ) + ( ( 4 - ( ( size + diff ) % 4 ) ) % 4 );

cmdEncoder.copyBufferToBuffer(
bufferGPU,
0,
alignedOffset,
readBufferGPU,
0,
size
alignedCopySize
);

const gpuCommands = cmdEncoder.finish();
device.queue.submit( [ gpuCommands ] );

await readBufferGPU.mapAsync( GPUMapMode.READ );

const arrayBuffer = readBufferGPU.getMappedRange();
const arrayBuffer = readBufferGPU.getMappedRange().slice( diff, diff + size );

readBufferGPU.unmap();

return arrayBuffer;

Expand Down
Loading