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
30 changes: 30 additions & 0 deletions src/nodes/utils/EventNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class EventNode extends Node {

this.updateType = NodeUpdateType.RENDER;

} else if ( eventType === EventNode.FRAME ) {

this.updateType = NodeUpdateType.FRAME;

} else if ( eventType === EventNode.BEFORE_OBJECT ) {

this.updateBeforeType = NodeUpdateType.OBJECT;
Expand All @@ -43,6 +47,10 @@ class EventNode extends Node {

this.updateBeforeType = NodeUpdateType.RENDER;

} else if ( eventType === EventNode.BEFORE_FRAME ) {

this.updateBeforeType = NodeUpdateType.FRAME;

}

}
Expand All @@ -63,8 +71,10 @@ class EventNode extends Node {

EventNode.OBJECT = 'object';
EventNode.MATERIAL = 'material';
EventNode.FRAME = 'frame';
EventNode.BEFORE_OBJECT = 'beforeObject';
EventNode.BEFORE_MATERIAL = 'beforeMaterial';
EventNode.BEFORE_FRAME = 'beforeFrame';

export default EventNode;

Expand Down Expand Up @@ -97,6 +107,16 @@ export const OnObjectUpdate = ( callback ) => createEvent( EventNode.OBJECT, cal
*/
export const OnMaterialUpdate = ( callback ) => createEvent( EventNode.MATERIAL, callback );

/**
* Creates an event that triggers a function every frame.
*
* The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
*
* @param {Function} callback - The callback function.
* @returns {EventNode}
*/
export const OnFrameUpdate = ( callback ) => createEvent( EventNode.FRAME, callback );

/**
* Creates an event that triggers a function before an object (Mesh|Sprite) is updated.
*
Expand All @@ -116,3 +136,13 @@ export const OnBeforeObjectUpdate = ( callback ) => createEvent( EventNode.BEFOR
* @returns {EventNode}
*/
export const OnBeforeMaterialUpdate = ( callback ) => createEvent( EventNode.BEFORE_MATERIAL, callback );

/**
* Creates an event that triggers a function before every frame.
*
* The event will be bound to the declared TSL function `Fn()`; it must be declared within a `Fn()` or the JS function call must be inherited from one.
*
* @param {Function} callback - The callback function.
* @returns {EventNode}
*/
export const OnBeforeFrameUpdate = ( callback ) => createEvent( EventNode.BEFORE_FRAME, callback );
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