Skip to content
15 changes: 10 additions & 5 deletions src/renderers/common/Info.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,12 @@ class Info {
*/
createReadbackBuffer( readbackBuffer ) {

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

this.memory.readbackBuffers ++;
this.memory.total += size;
this.memory.readbackBuffersSize += size;
this.memory.total += maxByteLength;
this.memory.readbackBuffersSize += maxByteLength;

}

Expand All @@ -356,7 +356,12 @@ class Info {
*/
destroyReadbackBuffer( readbackBuffer ) {

this.destroyAttribute( readbackBuffer );
const { size } = this.memoryMap.get( readbackBuffer );
this.memoryMap.delete( readbackBuffer );

this.memory.readbackBuffers --;
this.memory.total -= size;
this.memory.readbackBuffersSize -= size;

}

Expand Down
24 changes: 19 additions & 5 deletions src/renderers/common/ReadbackBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,32 @@ class ReadbackBuffer extends EventDispatcher {
/**
* Constructs a new readback buffer.
*
* @param {BufferAttribute} attribute - The buffer attribute.
* @param {number} maxByteLength - The maximum size of the buffer to be read back.
*/
constructor( attribute ) {
constructor( maxByteLength ) {

super();

/**
* The buffer attribute.
* Name used for debugging purposes.
*
* @type {BufferAttribute}
* @type {string}
*/
this.attribute = attribute;
this.name = '';

/**
* The mapped, read back array buffer.
*
* @type {ArrayBuffer|null}
*/
this.buffer = null;

/**
* The maximum size of the buffer to be read back.
*
* @type {number}
*/
this.maxByteLength = maxByteLength;

/**
* This flag can be used for type testing.
Expand Down
56 changes: 15 additions & 41 deletions src/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import Lighting from './Lighting.js';
import XRManager from './XRManager.js';
import InspectorBase from './InspectorBase.js';
import CanvasTarget from './CanvasTarget.js';
import ReadbackBuffer from './ReadbackBuffer.js';

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

Expand Down Expand Up @@ -1919,61 +1918,36 @@ class Renderer {
* from the GPU to the CPU in context of compute shaders.
*
* @async
* @param {StorageBufferAttribute|ReadbackBuffer} buffer - The storage buffer attribute.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
* @param {BufferAttribute} attribute - The storage buffer attribute to read frm.
* @param {number} count - The offset from which to start reading the
* @param {number} offset - The storage buffer attribute.
* @param {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( buffer ) {
async getArrayBufferAsync( attribute, count = - 1, offset = 0, target = null ) {

let readbackBuffer = buffer;
// tally the memory for this readback buffer
if ( target !== null && target.isReadbackBuffer ) {

if ( readbackBuffer.isReadbackBuffer !== true ) {
if ( this.info.memoryMap.has( target ) === false ) {

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

readbackBuffer = attributeData.readbackBuffer;
const disposeInfo = () => {

if ( readbackBuffer === undefined ) {
target.removeEventListener( 'dispose', disposeInfo );

readbackBuffer = new ReadbackBuffer( attribute );

const dispose = () => {

attribute.removeEventListener( 'dispose', dispose );

readbackBuffer.dispose();

delete attributeData.readbackBuffer;
this.info.destroyReadbackBuffer( target );

};

attribute.addEventListener( 'dispose', dispose );

attributeData.readbackBuffer = readbackBuffer;
target.addEventListener( 'dispose', disposeInfo );

}

}

if ( this.info.memoryMap.has( readbackBuffer ) === false ) {

this.info.createReadbackBuffer( readbackBuffer );

const disposeInfo = () => {

readbackBuffer.removeEventListener( 'dispose', disposeInfo );

this.info.destroyReadbackBuffer( readbackBuffer );

};

readbackBuffer.addEventListener( 'dispose', disposeInfo );

}

readbackBuffer.release();

return await this.backend.getArrayBufferAsync( readbackBuffer );
return await this.backend.getArrayBufferAsync( attribute, count, offset, target );

}

Expand Down
15 changes: 10 additions & 5 deletions src/renderers/webgl-fallback/WebGLBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,15 +309,20 @@ class WebGLBackend extends Backend {

/**
* This method performs a readback operation by moving buffer data from
* a storage buffer attribute from the GPU to the CPU.
* a storage buffer attribute from the GPU to the CPU. ReadbackBuffer can
* be used to retain and reuse handles to the intermediate buffers and prevent
* new allocation.
*
* @async
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
* @param {BufferAttribute} attribute - The storage buffer attribute to read frm.
* @param {number} count - The offset from which to start reading the
* @param {number} offset - The storage buffer attribute.
* @param {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, count = - 1, offset = 0, target = null ) {

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

}

Expand Down
77 changes: 32 additions & 45 deletions src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,76 +254,63 @@ class WebGLAttributeUtils {

/**
* This method performs a readback operation by moving buffer data from
* a storage buffer attribute from the GPU to the CPU.
* a storage buffer attribute from the GPU to the CPU. ReadbackBuffer can
* be used to retain and reuse handles to the intermediate buffers and prevent
* new allocation.
*
* @async
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
* @param {BufferAttribute} attribute - The storage buffer attribute to read frm.
* @param {number} count - The offset from which to start reading the
* @param {number} offset - The storage buffer attribute.
* @param {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, count = - 1, offset = 0, target = null ) {

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

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

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

gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );

const readbackBufferData = backend.get( readbackBuffer );

let { writeBuffer } = readbackBufferData;

if ( writeBuffer === undefined ) {

writeBuffer = gl.createBuffer();

gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );

// dispose

const dispose = () => {

gl.deleteBuffer( writeBuffer );
const byteLength = count === - 1 ? attributeInfo.byteLength - offset : count;

backend.delete( readbackBuffer );
// read the data back
let dstBuffer;
if ( target === null ) {

readbackBuffer.removeEventListener( 'dispose', dispose );
dstBuffer = new Uint8Array( new ArrayBuffer( byteLength ) );

};
} else if ( target.isReadbackBuffer ) {

readbackBuffer.addEventListener( 'dispose', dispose );

// register

readbackBufferData.writeBuffer = writeBuffer;
// WebGL has no concept of a "mapped" data buffer so we create a new buffer, instead.
dstBuffer = new Uint8Array( new ArrayBuffer( byteLength ) );
target.buffer = dstBuffer.buffer;

} else {

gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
dstBuffer = new Uint8Array( target );

}

gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength );
// Ensure the buffer is bound before reading
gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );
gl.getBufferSubData( gl.COPY_READ_BUFFER, offset, dstBuffer );

await backend.utils._clientWaitAsync();
gl.bindBuffer( gl.COPY_READ_BUFFER, null );
gl.bindBuffer( gl.COPY_WRITE_BUFFER, null );

const dstBuffer = new attribute.array.constructor( array.length );
// return the appropriate type
if ( target && target.isReadbackBuffer ) {

// Ensure the buffer is bound before reading
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
return target;

gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer );
} else {

gl.bindBuffer( gl.COPY_READ_BUFFER, null );
gl.bindBuffer( gl.COPY_WRITE_BUFFER, null );
return dstBuffer.buffer;

return dstBuffer;
}

}

Expand Down
15 changes: 10 additions & 5 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,20 @@ class WebGPUBackend extends Backend {

/**
* This method performs a readback operation by moving buffer data from
* a storage buffer attribute from the GPU to the CPU.
* a storage buffer attribute from the GPU to the CPU. ReadbackBuffer can
* be used to retain and reuse handles to the intermediate buffers and prevent
* new allocation.
*
* @async
* @param {ReadbackBuffer} readbackBuffer - The readback buffer.
* @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready.
* @param {BufferAttribute} attribute - The storage buffer attribute to read frm.
* @param {number} count - The offset from which to start reading the
* @param {number} offset - The storage buffer attribute.
* @param {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, count = - 1, offset = 0, target = null ) {

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

}

Expand Down
Loading
Loading