Skip to content
Merged
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
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
26 changes: 21 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 All @@ -33,6 +47,8 @@ class ReadbackBuffer extends EventDispatcher {
*/
this.isReadbackBuffer = true;

this._mapped = false;

}

/**
Expand Down
54 changes: 17 additions & 37 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 @@ -1913,61 +1912,42 @@ 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 {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @param {number} offset - The storage buffer attribute.
* @param {number} count - The offset from which to start reading the
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( buffer ) {
async getArrayBufferAsync( attribute, target = null, offset = 0, count = - 1 ) {

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 );
if ( offset % 4 !== 0 || ( count > 0 && count % 4 !== 0 ) ) {

const disposeInfo = () => {

readbackBuffer.removeEventListener( 'dispose', disposeInfo );

this.info.destroyReadbackBuffer( readbackBuffer );

};

readbackBuffer.addEventListener( 'dispose', disposeInfo );
throw new Error( 'THREE.Renderer: "getArrayBufferAsync()" offset and count must be a multiple of 4.' );

}

readbackBuffer.release();

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

}

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 {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @param {number} offset - The storage buffer attribute.
* @param {number} count - The offset from which to start reading the
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, target = null, offset = 0, count = - 1 ) {

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

}

Expand Down
79 changes: 42 additions & 37 deletions src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,76 +254,81 @@ 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 {ReadbackBuffer|ArrayBuffer} target - The storage buffer attribute.
* @param {number} offset - The storage buffer attribute.
* @param {number} count - The offset from which to start reading the
* @return {Promise<ArrayBuffer|ReadbackBuffer>} A promise that resolves with the buffer data when the data are ready.
*/
async getArrayBufferAsync( readbackBuffer ) {
async getArrayBufferAsync( attribute, target = null, offset = 0, count = - 1 ) {

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 );
const byteLength = count === - 1 ? attributeInfo.byteLength - offset : count;

let { writeBuffer } = readbackBufferData;
// read the data back
let dstBuffer;
if ( target === null ) {

if ( writeBuffer === undefined ) {
dstBuffer = new Uint8Array( new ArrayBuffer( byteLength ) );

writeBuffer = gl.createBuffer();
} else if ( target.isReadbackBuffer ) {

gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );
if ( target._mapped === true ) {

// dispose
throw new Error( 'WebGPURenderer: ReadbackBuffer must be released before being used again.' );

const dispose = () => {

gl.deleteBuffer( writeBuffer );
}

backend.delete( readbackBuffer );
const releaseCallback = () => {

readbackBuffer.removeEventListener( 'dispose', dispose );
target.buffer = null;
target._mapped = false;
target.removeEventListener( 'release', releaseCallback );
target.removeEventListener( 'dispose', releaseCallback );

};

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

// 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, target = null, offset = 0, count = - 1 ) {

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

}

Expand Down
Loading
Loading