diff --git a/examples/webgpu_multiple_rendertargets_readback.html b/examples/webgpu_multiple_rendertargets_readback.html index 96ca11ddbf62b3..93d1e5be6b915b 100644 --- a/examples/webgpu_multiple_rendertargets_readback.html +++ b/examples/webgpu_multiple_rendertargets_readback.html @@ -72,7 +72,6 @@ readbackTarget = new THREE.RenderTarget( size, size, { count: 2 } ); - pixelBuffer = new Uint8Array( size ** 2 * 4 ).fill( 0 ); pixelBufferTexture = new THREE.DataTexture( pixelBuffer, size, size ); pixelBufferTexture.type = THREE.UnsignedByteType; pixelBufferTexture.format = THREE.RGBAFormat; @@ -175,6 +174,8 @@ const selection = options.selection; + if ( pixelBuffer !== undefined ) renderer.recycleBuffer( pixelBuffer ); + if ( selection === 'diffuse' ) { pixelBuffer = await renderer.readRenderTargetPixelsAsync( readbackTarget, 0, 0, width, height, 0 ); // zero is optional diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index ca6f1461389f9c..d87554241e1bf7 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -1265,6 +1265,12 @@ class Renderer { } + recycleBuffer( buffer ) { + + this.backend.recycleBuffer( buffer ); + + } + _projectObject( object, camera, groupOrder, renderList ) { if ( object.visible === false ) return; diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index b1e1a76d34491a..a7fa98ccd2efa9 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -1279,6 +1279,12 @@ class WebGLBackend extends Backend { } + recycleBuffer( buffer ) { + + this.textureUtils.recycleBuffer( buffer ); + + } + _setFramebuffer( descriptor ) { const { gl, state } = this; diff --git a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js index dc839595ebbf39..63108c8b81a985 100644 --- a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js @@ -20,6 +20,9 @@ class WebGLTextureUtils { } + this.bufferCache = new WeakSet(); + this.readBufferCache = {}; + } _init( gl ) { @@ -835,7 +838,24 @@ class WebGLTextureUtils { await backend.utils._clientWaitAsync(); - const dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT ); + const cacheList = this.readBufferCache[ byteLength ]; + + let dstBuffer; + + if ( cacheList !== undefined ) { + + const arrayBuffer = cacheList.pop(); + if ( arrayBuffer !== undefined ) dstBuffer = new typedArrayType( arrayBuffer ); + + } + + if ( dstBuffer === undefined ) { + + dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT ); + + } + + this.bufferCache.add( dstBuffer ); gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer ); gl.getBufferSubData( gl.PIXEL_PACK_BUFFER, 0, dstBuffer ); @@ -847,6 +867,27 @@ class WebGLTextureUtils { } + recycleBuffer( buffer ) { + + if ( this.bufferCache.has( buffer ) ) { + + const arrayBuffer = buffer.buffer; + + const cache = this.readBufferCache; + const cacheKey = arrayBuffer.byteLength; + + let cacheList = cache[ cacheKey ]; + + if ( cacheList === undefined ) cacheList = cache[ cacheKey ] = []; + + cacheList.push( arrayBuffer ); + + this.bufferCache.delete( buffer ); + + } + + } + _getTypedArrayType( glType ) { const { gl } = this; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 4c4efe826a6834..b84bee3c25c21a 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -1148,6 +1148,11 @@ class WebGPUBackend extends Backend { } + recycleBuffer( buffer ) { + + return this.textureUtils.recycleBuffer( buffer ); + + } initTimestampQuery( renderContext, descriptor ) { diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index 5fb88c29446767..1698034414b77d 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -49,6 +49,9 @@ class WebGPUTextureUtils { this.depthTexture = new DepthTexture(); this.depthTexture.name = 'depthBuffer'; + this.bufferCache = new WeakMap(); + this.readBufferCache = {}; + } createSampler( texture ) { @@ -381,12 +384,28 @@ class WebGPUTextureUtils { let bytesPerRow = width * bytesPerTexel; bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes - const readBuffer = device.createBuffer( - { - size: width * height * bytesPerTexel, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ - } - ); + const size = width * height * bytesPerTexel; + + const cacheList = this.readBufferCache[ size ]; + + let readBuffer; + + if ( cacheList !== undefined ) { + + readBuffer = cacheList.pop(); + + } + + if ( readBuffer === undefined ) { + + readBuffer = device.createBuffer( + { + size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + + } const encoder = device.createCommandEncoder(); @@ -412,9 +431,34 @@ class WebGPUTextureUtils { await readBuffer.mapAsync( GPUMapMode.READ ); - const buffer = readBuffer.getMappedRange(); + const buffer = new typedArrayType( readBuffer.getMappedRange() ); - return new typedArrayType( buffer ); + this.bufferCache.set( buffer, readBuffer ); + + return buffer; + + } + + recycleBuffer( buffer ) { + + const bufferGPU = this.bufferCache.get( buffer ); + + if ( bufferGPU !== undefined ) { + + bufferGPU.unmap(); + + const cache = this.readBufferCache; + const cacheKey = bufferGPU.size; + + let cacheList = cache[ cacheKey ]; + + if ( cacheList === undefined ) cacheList = cache[ cacheKey ] = []; + + cacheList.push( bufferGPU ); + + this.bufferCache.delete( buffer ); + + } }