-
-
Notifications
You must be signed in to change notification settings - Fork 36.1k
WebGPUBindingUtils - Change bindGroupLayout cache system #32249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
|
TBH, I have the feeling the issue is fixed at the wrong place. There is already code in place that should ensure unique bind group layouts. three.js/src/renderers/webgpu/utils/WebGPUBindingUtils.js Lines 253 to 260 in 4421df4
This block is not correct. Instead of directly using the array const bindLayoutCacheKey = getBindLayoutCacheKey( bindGroup.bindingsReference );
let bindLayoutGPU = bindGroupLayoutCache.get( bindLayoutCacheKey );
if ( bindLayoutGPU === undefined ) {
bindLayoutGPU = this.createBindingsLayout( bindGroup );
bindGroupLayoutCache.set( bindLayoutCacheKey, bindLayoutGPU );
} |
So you would prefer that I create a key from the full entries object rather than creating keys for each entry resource than collecting them? I understand why this structure would be preferable, but the reason I create keys as the entries are created is that creating a key from the bindingsReference would essentially require us to iterate through each binding's properties to construct the hash for the overall layout entry. Then, if the hash is incorrect, we'd have to iterate through each binding again to actually generate the entries. Could we just append to one key (rather than creating keys for buffer, texture, storageTexture, etc) over time as the bindGroupLayoutEntries are created? Then, if the key generated from the entries doesn't correspond to an entry in the map, we can execute the device.createBindGroupLayout call. |
|
TBH, I found the PR somewhat hard to follow. The code from #32249 (comment) should be easier to read even if that means to iterate through the array twice. This should no be an performance issue since this array will never be large. |
|
|
||
| dispose() { | ||
|
|
||
| this.bindGroupLayoutCache.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a method to do the individual dispose the layouts as well, since we're storing in a Map instead of a WeakMap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The layouts don't appear to have a destroy() method in the WebGPU spec like buffer, texture, or querySet. I can look into where else the bindGroupLayout may be referenced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue would be that layout is referenced in pipeline utils as well. I can draft this and see if there's a way to set the bindingsData's layout to null somewhere so that reference is cleared.
EDIT: Will probably be later today sometime after work.
EDIT 2: Reading through the code, it seems like the places where bindingsData.layout is invoked is alligned with getForRender/getForCompute so hopefully it should just be modifying deleteForRender and deleteForCompute.
src/renderers/common/Bindings.js
Outdated
|
|
||
| for ( const bindGroup of bindings ) { | ||
|
|
||
| this.backend.deleteBindGroupData( bindGroup ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refactor this part and remove the if ( this.backend.isWebGPUBackend ) { check. The idea is to define deleteBindGroupData() as an abstract method in Backend and don't provide an implementation for the WebGL backend.
| const { backend } = this; | ||
|
|
||
| const bindingsData = backend.get( bindGroup ); | ||
| bindingsData.layout = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's important to clean up bindGroupLayoutCache as well when the bind group is deleted. Otherwise the map could end up as a memory leak.
You can do this by saving the layout key in the bind group data and then use it to delete the entry from the cache. For this, you need to know though how often it is used by maintaining a usedTimes variable for each layout. We use this approach multiple times in the renderer, I suggest you add it here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can implement this, but would that mean the map.clear() invoked on backend.dispose -> bindingUtils.dispose() is not sufficient to remove the references even when layout has been removed from each bindingsData?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented usedTimes and encapsulated some of the entry creation functionality. I tried to adjust where the functions are located to make the diff less difficult to read.
a4423b9 to
66e3e33
Compare
Related issue: #31682
Description
Access ids of previous used layout entries based on stringified result of bindingGPU, then use overall layout key to reuse existing bind group layouts.
Results
WebGPU Materials Toon goes from 440 Bind Group Layouts to 4
WebGPU GLTF Loader Transmission goes from 16 to 7