-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
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
WebGPURenderer: RenderBundle #28347
WebGPURenderer: RenderBundle #28347
Conversation
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
Ok I will investigate on this error today. Regarding const bundle = renderer.recordBundles(scene, camera)
// your anim loop
function animate() {
// your static scene gets drawn, for example in a video game it would be all the elements in the background
renderer.renderBundles(bundle)
// render your complex stuff that needs to be update every frame (or you can have a postprocess pipeline here)
renderer.render(sceneComplex, camera)
} I understand that it might seem optimal to be able to record and always execute in one batch all the renderer commands as it sounds like a super optimization, but in reality, it will just end up becoming so restrictive that there is nothing you can do with it. Cherry-picking a specific part of your pipeline to freeze it as you like sounds a lot more useful, in my opinion. Also I would say that I still prefer a lot more handling this per By the way the example should be fixed! 😄 /cc @sunag |
I found your second idea interesting... maybe something like? const renderBundle = new RenderBundle( scene, camera );
renderBundle.transparent = false;
// renderBundle.needsUpdate = true;
renderer.renderBundle( renderBundle ); I think |
I like the concept of having a In that case, I will propose another API for the static part in a dedicated pull request. |
My hypothesis would be, during UBO optimization should be independent of
In this sense, the current status is still that #27134, |
100% I'm not sure adding I still prefer the "static graph" approach better: const group = new Group();
group.static = true;
const mesh1 = new Mesh( geometry, material );
group.add( mesh1 );
const instances1 = new InstancedMesh( geometry, material );
group.add( instances1 ); We can make it so the renderer only traverses a static group when That way the developer is able to update the bundle data when needed. group.needsUpdate = true; // forces the renderer to traverse the children and update internal bundle
mesh1.position.x = Math.random();
mesh1.matrixWorldNeedsUpdate = true; // recomputes child matrices
instances1.setMatrixAt( index, matrix );
instances1.instanceMatrix.needsUpdate = true; API wise, it would be mostly adding the properties We can continue serializing the scene graph as usual while letting the developer "flatten" parts of the graph at render time. @gkjohnson Maybe this approach could also be used for |
Re-using Group this way is an nice API approach. It may run into some tension with the way to get maximum performance... I do think we would typically want to flatten transforms and other uniforms into one flat array for the whole Bundle. For the best performance, the client code would want to do something like group.setMatrixAt(group.indexOf(mesh1), matrix)); Along those lines, my hope is to push nearly everything projectObject must do per-frame onto the GPU, including visibility testing, layer testing, frustum culling, etc. So I do think you want to do things like group.setVisibleAt(group.indexOf(mesh1), false); (Doing a full traverse just to update one matrix or visibility flag is probably undesirable) Exposing this behavior into group could work but it also might make the API a bit bulky |
I think it would be better to deal with the |
My impression is that Batching and RenderBundles are different techniques and it's valuable to use both together. Here's a quick overview of my understanding:
RenderBundles won't save any draw calls, though. If you record a set of commands that issues 1000 draw calls then 1000 draw calls will still be issued via native code (though faster). So batching saves draw calls, render bundles save JS overhead, and they should be used together. Sources: Since I'm here I'll say I think a more transparent API like
Explicit calls like this shouldn't be necessary I don't think. These things should be implicitly determined based on the hierarchy state when generating the render bundle, no? |
It seems that we're all in agreement regarding the concepts and direction that the bundle and static techniques should take.
As @sunag explained, the newly introduced
With this RenderBundle PR, it is still possible to update most buffers and uniforms per mesh dynamically, as demonstrated in the live example, where all matrices are dynamically updated. Another performance optimization to consider is the writeBuffer cost involved with each uniform/buffer update, which will be partially addressed by the single uniform buffer update #27388 PR. /cc @nkallen @gkjohnson Here's how I see the implementation unfolding: Part 1 (basic bundle + static pipeline):
Part 2 (more advanced features):
|
I added This PR should now be ready for review @sunag 😊 |
group.setMatrixAt(group.indexOf(mesh1), matrix));
group.setVisibleAt(group.indexOf(mesh1), false);
I am thinking ahead a bit to Part 2 ("more advanced features") which is our usecase. We have a 3d modeling/editor program. The scene is primarily static (most objects have unchanging geometry and transforms). The camera is moving frequently as the user navigates the scene. But when the user initiates an editing operation, some very small subset of items have visibility flags and transforms change as a result of user edits. These are changing per frame (e.g., onpointermove). So we do want to be able to change these attributes per frame (transforms and visibility), without rebuilding the render bundle, without traversing all items to update a very few matrixWorlds, and without re-uploading all buffers. The existing "live example" as mentioned above does allow transforms to be updated per frame, but at the cost of iterating through every item in the bundle and spending additional cpu and and memory bandwidth, erasing a good portion of the gains from the optimization. (correct me if I'm mistaken?) We additionally want to render the scene from multiple camera angles (multiple viewports). So for a given user edit, we will issue (for example) four render calls with four cameras. Thus our hope is to get this usecase supported in such a way as to remove anything O(n) on the CPU from the (per-frame part of) the render pipeline. I'm not sure if this clarifies anything, but we would want an API vaguely like the following. const bundle = buildBundle();
onUserEdit(edit => {
updateUniforms(bundle, edit);
setNeedsRender(allViewports)
} );
for (const viewport of allViewports)
viewport.orbitControls.onMove(() => setNeedsRender([viewport])); |
@RenaudRohlinger I'm reviewing and did this second part |
@mrdoob Let's adopt this strategy, the current PR will be part of this as @RenaudRohlinger commented. @RenaudRohlinger After this step, I find it interesting to include the management of bindings by group, today we are dealing with bindings in the same group.
|
About For example in this case https://webgpu.github.io/webgpu-samples/?sample=renderBundles I think the implementation of #27388 should be after that. |
Awesome thanks @sunag!
If I understand correctly, there will be a shared frame buffer ( All the global scene-related data would be bound to index 0, and all the per-object level data and shaders would be bound to index 1 (global stuff bound to 0 in the shaders). This way, we could render the scene from multiple camera angles in a split-view manner, for example, without having to update anything except that specific buffer. Or do you have something different in mind? |
I have this in mind:
Each //
Will be:
We'll probably have to sort them. Now any Node can be stored in an individual group at buffer level, as the code part would already be ready here. Defining a uniform in a group would be very simple, currently e.g: import { uniform, renderGroup } ...
// the buffer will be updated only once per render call
const myGlobalPosition = uniform( new THREE.Vector( 0, 100, 0 ) ).setGroup( renderGroup );
// or
const customGroup = sharedUniformGroup( 'myGroup' );
const myGlobalPosition = uniform( new THREE.Vector( 0, 100, 0 ) ).setGroup( customGroup );
// custom groups must be updated using `.needsUpdate`
customGroup.needsUpdate = true; --
In each |
Related issue: #26876 #26983
Description
WebGPU RenderBundle offers performance benefits and introduces a new approach to batch processing the instructions of our scene, reducing the amount of CPU time spent issuing repeated rendered commands.
This PR adds the WebGPU support for
RenderBundle
and a new faster Renderer pipeline in order to reduce JS overhead and is the first step towards Threejs Static Scenes.Using the WebGLBackend also works and will still benefit from a reduction of JS overhead by skipping most of the renderer pipeline code.
Example for
8000
meshes,23.7ms
average in the default renderer and15.9ms
average in the RenderBundle mode (32%+ performance increase):https://raw.githack.com/renaudrohlinger/three.js/utsubo/feat/render-bundles/examples/webgpu_renderbundle.html
This contribution is funded by Utsubo and Plasticity