diff --git a/src/nodes/pmrem/PMREMNode.js b/src/nodes/pmrem/PMREMNode.js index 44b35fb104079a..fc65e547aab918 100644 --- a/src/nodes/pmrem/PMREMNode.js +++ b/src/nodes/pmrem/PMREMNode.js @@ -81,6 +81,31 @@ function _getPMREMFromTexture( texture, renderer, generator ) { cacheTexture.pmremVersion = texture.pmremVersion; + // add dispose event listener for new PMREMs + + if ( cache.has( texture ) === false ) { + + const onDispose = () => { + + texture.removeEventListener( 'dispose', onDispose ); + + const pmrem = cache.get( texture ); + + if ( pmrem !== undefined ) { + + pmrem.dispose(); + cache.delete( texture ); + + } + + }; + + texture.addEventListener( 'dispose', onDispose ); + + } + + // + cache.set( texture, cacheTexture ); } diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index 1e0e661dcbe2e1..867f222e3e55ac 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -284,11 +284,18 @@ class Backend { * Updates a GPU sampler for the given texture. * * @abstract - * @param {Texture} texture - The texture to update the sampler for. - * @param {TextureNode} textureNode - The texture node to update the sampler with. + * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ - updateSampler( /*texture, textureNode*/ ) { } + updateSampler( /*binding*/ ) { } + + /** + * Frees the GPU sampler for the given sampler binding. + * + * @abstract + * @param {Sampler} binding - The sampler binding to free. + */ + destroySampler( /*binding*/ ) { } /** * Creates a default texture for the given texture that can be used diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js index d6c16863e372fa..5f3d213a016b37 100644 --- a/src/renderers/common/Bindings.js +++ b/src/renderers/common/Bindings.js @@ -210,7 +210,7 @@ class Bindings extends DataMap { } else if ( binding.isSampler ) { - this.textures.updateSampler( binding.texture, binding.textureNode ); + this.textures.updateSampler( binding ); } else if ( binding.isStorageBuffer ) { @@ -267,6 +267,12 @@ class Bindings extends DataMap { } else if ( binding.isSampler ) { + if ( binding.isSampledTexture !== true ) { + + this.backend.destroySampler( binding ); + + } + binding.release(); } @@ -422,7 +428,7 @@ class Bindings extends DataMap { if ( updated ) { - const samplerKey = this.textures.updateSampler( binding.texture, binding.textureNode ); + const samplerKey = this.textures.updateSampler( binding ); if ( binding.samplerKey !== samplerKey ) { diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js index 4c6479055dd3fe..f42b90eac69ca0 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js @@ -426,13 +426,12 @@ class Textures extends DataMap { * In WebGPU, samplers are objects like textures and it's possible to share * them when the texture parameters match. * - * @param {Texture} texture - The texture to update the sampler for. - * @param {TextureNode} textureNode - The texture node to update the sampler with. + * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ - updateSampler( texture, textureNode ) { + updateSampler( binding ) { - return this.backend.updateSampler( texture, textureNode ); + return this.backend.updateSampler( binding ); } @@ -625,6 +624,12 @@ class Textures extends DataMap { if ( binding.isSampler && binding.texture === texture ) { + if ( binding.isSampledTexture !== true ) { + + this.backend.destroySampler( binding ); + + } + binding.reset(); binding.release(); diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index bd8bd9d075bca7..68c2c6942b23a5 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -1429,11 +1429,10 @@ class WebGLBackend extends Backend { /** * This method does nothing since WebGL 2 has no concept of samplers. * - * @param {Texture} texture - The texture to update the sampler for. - * @param {TextureNode} textureNode - The texture node to update the sampler with. + * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ - updateSampler( /*texture, textureNode*/ ) { + updateSampler( /*binding*/ ) { return ''; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 91ef251aaa118b..06e203693ed9b1 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -2147,13 +2147,23 @@ class WebGPUBackend extends Backend { /** * Updates a GPU sampler for the given texture. * - * @param {Texture} texture - The texture to update the sampler for. - * @param {TextureNode} textureNode - The texture node to update the sampler with. + * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ - updateSampler( texture, textureNode ) { + updateSampler( binding ) { - return this.textureUtils.updateSampler( texture, textureNode ); + return this.textureUtils.updateSampler( binding ); + + } + + /** + * Frees the GPU sampler for the given sampler binding. + * + * @param {Sampler} binding - The sampler binding to free. + */ + destroySampler( binding ) { + + this.textureUtils.destroySampler( binding ); } diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index 696fbc7e5a906f..da4bd473bd8df0 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -392,9 +392,9 @@ class WebGPUBindingUtils { } else if ( binding.isSampler ) { - const textureGPU = backend.get( binding.texture ); + const bindingData = backend.get( binding ); - _bindGroupDescriptor.entries.push( { binding: bindingPoint, resource: textureGPU.sampler } ); + _bindGroupDescriptor.entries.push( { binding: bindingPoint, resource: bindingData.sampler } ); } diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index a0c470c50708ac..ff8ee2dbace281 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -148,13 +148,14 @@ class WebGPUTextureUtils { /** * Creates a GPU sampler for the given texture. * - * @param {Texture} texture - The texture to create the sampler for. - * @param {TextureNode} textureNode - The texture node to update the sampler with. + * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ - updateSampler( texture, textureNode ) { + updateSampler( binding ) { const backend = this.backend; + const texture = binding.texture; + const textureNode = binding.textureNode; const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' + texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' + @@ -205,35 +206,62 @@ class WebGPUTextureUtils { } - const textureData = backend.get( texture ); + const bindingData = backend.get( binding ); - if ( textureData.sampler !== samplerData.sampler ) { + if ( bindingData.sampler !== samplerData.sampler ) { - // check if previous sampler is unused so it can be deleted + // release the previous sampler (if any) so it can be deleted when unused - if ( textureData.sampler !== undefined ) { + this._releaseSampler( bindingData ); - const oldSamplerData = this._samplerCache.get( textureData.samplerKey ); - oldSamplerData.usedTimes --; + // update to new sampler data - if ( oldSamplerData.usedTimes === 0 ) { + bindingData.samplerKey = samplerKey; + bindingData.sampler = samplerData.sampler; - this._samplerCache.delete( textureData.samplerKey ); + samplerData.usedTimes ++; - } + } - } + return samplerKey; - // update to new sampler data + } - textureData.samplerKey = samplerKey; - textureData.sampler = samplerData.sampler; + /** + * Frees the GPU sampler referenced by the given sampler binding. + * + * @param {Sampler} binding - The sampler binding to free. + */ + destroySampler( binding ) { - samplerData.usedTimes ++; + this._releaseSampler( this.backend.get( binding ) ); - } + } - return samplerKey; + /** + * Releases the pooled sampler referenced by the given binding data and + * removes it from the cache when no binding references it anymore. + * + * @private + * @param {Object} bindingData - The binding data holding the sampler reference. + */ + _releaseSampler( bindingData ) { + + if ( bindingData.sampler !== undefined ) { + + const samplerData = this._samplerCache.get( bindingData.samplerKey ); + samplerData.usedTimes --; + + if ( samplerData.usedTimes === 0 ) { + + this._samplerCache.delete( bindingData.samplerKey ); + + } + + bindingData.sampler = undefined; + bindingData.samplerKey = undefined; + + } }