Skip to content

WebGPURenderer: Array-Based RenderTarget Refactor #30959

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

Merged
merged 18 commits into from
Apr 30, 2025

Conversation

RenaudRohlinger
Copy link
Collaborator

@RenaudRohlinger RenaudRohlinger commented Apr 19, 2025

Related issue: #30155 #30920 #30830

Description
This PR refactors the concept of TextureArray and RenderTargetArray, adding capabilities for more advanced use cases such as Multi Render Target Arrays.

Following the same idea behind deprecating WebGLMultipleRenderTargets in favor of RenderTarget using the options.count property, this PR leverages the existing image.depth property in Texture to introduce support for array-like behavior in RenderTarget.

By adding a new options.depth parameter to RenderTarget, it’s now possible to create a RenderTargetArray in a much simpler and more intuitive way:

const fboArray = new THREE.RenderTarget( size.width, size.height, { depth: 2 } ); // fbo array of depth 2

This opens the door for defining multi-layered render targets using both count and depth:

const multifboArray = new THREE.RenderTarget( size.width, size.height, { count: 2, depth: 2 } ); // 2 layouts with an fbo array of depth 2

As a result, redundant classes such as DepthArrayTexture become unnecessary. Equivalent functionality is now achieved more cleanly:

const depthArray = DepthTexture()
depthArray.depth = 2

I made updates to parts of the code that were affected by your multiview PR, @rcabanier. Since I don’t currently have access to an XR device, I’d appreciate it if you could verify whether everything still works as expected with this change.

This contribution is funded by Renaud Rohlinger @ Utsubo

@RenaudRohlinger RenaudRohlinger added this to the r176 milestone Apr 19, 2025
Copy link

github-actions bot commented Apr 19, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 336.33
78.33
336.29
78.32
-36 B
-8 B
WebGPU 547.85
151.9
547.44
151.78
-407 B
-122 B
WebGPU Nodes 547.2
151.74
546.79
151.62
-407 B
-122 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 466.15
112.56
466.23
112.58
+77 B
+20 B
WebGPU 623.1
168.69
622.26
168.48
-836 B
-203 B
WebGPU Nodes 577.97
157.98
577.14
157.79
-836 B
-193 B

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 19, 2025

Are you okay if we move this PR to the r177 target? The release is already next week and this PR implements a quite substantial refactoring. I would prefer to have more time on dev to test/verify the changes.

@RenaudRohlinger RenaudRohlinger modified the milestones: r176, r177 Apr 19, 2025
@@ -147,7 +138,6 @@ class Textures extends DataMap {

const texture = textures[ i ];

texture.isTextureArray = renderTarget.multiview === true && size.depth > 1;
Copy link
Collaborator

@Mugen87 Mugen87 Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear this removal breaks multiview. When rendering in XR, the framebuffer render target is resized with a depth value larger 1.

frameBufferTarget.setSize( width, height, outputRenderTarget !== null ? outputRenderTarget.depth : 1 );

But since the isTextureArray /isArrayTexture is only set in the ctor, the flag isn't set correctly. So there are two options: Restore this line or make sure Texture.isArrayTexture is also set in RenderTarget.setSize() for all render target textures.

Copy link
Collaborator Author

@RenaudRohlinger RenaudRohlinger Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure Texture.isArrayTexture is also set in RenderTarget.setSize() for all render target textures.

I think this option makes a lot more sense I will try.

*/
constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat ) {
constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) {
Copy link
Collaborator

@sunag sunag Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's at times like these that I think that object-based function parameters are better for optional values, functions with more than 4 parameters start to get confusing for me.

Copy link
Collaborator

@sunag sunag Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just remove the parameter and leave it as in the example below?

const depthMap = new THREE.DepthTexture( ... );
depthMap.depth = value; 

Copy link
Collaborator Author

@RenaudRohlinger RenaudRohlinger Apr 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go this route we would need to apply the isArrayTexture patch later in the code, for example in the Textures class, but I don't even think it would work.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we are dealing with limitations but the function signature is inappropriate. Until we adopt a convention we can make this more explicit perhaps even with DepthArrayTexture again. The class name and parameters also has the function of making the code easier to read.

@sunag
Copy link
Collaborator

sunag commented Apr 27, 2025

I think the webgpu_compute_texture example is broken

@RenaudRohlinger
Copy link
Collaborator Author

All good except:

We could just remove the parameter and leave it as in the example below?

const depthMap = new THREE.DepthTexture( ... );
depthMap.depth = value;

However, I think we should have gone for an object as the argument from the start just like RenderTarget.
If we want to continue expanding Three.js features, we'll inevitably need to add many more parameters in the future. For instance, just for the depth settings alone:
image

This leads me to think that maybe we should define a clear convention:

  • Use a new property layers for array formats
  • Use depth for cube formats

This way, we avoid future confusion.
Adding a new class for every small feature (like layers or depth), as we're currently doing, feels unnecessary and confusing especially since WebGL and WebGPU don't work like this. (Cubes are a different story, so I think that exception is fine.)

@sunag
Copy link
Collaborator

sunag commented Apr 27, 2025

I think that face is for cube, depth for mipmaps and count for array. But to not change too much depth could be used for array, while face is for cube formats.

@RenaudRohlinger
Copy link
Collaborator Author

I think we can revisit these specific details in a separate PR. For now, this PR focuses on cleaning up some confusing parts of the code. By simply adding an extra depth argument, it not only simplifies the logic but also unlocks support for very advanced features like Multi Render Target Arrays. All that while removing some code, which is a nice demonstration of how necessary this refactor is.

@Mugen87 It would be great if you could confirm that XR is still working, as I currently don't have access to a device for testing.

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 28, 2025

Do you mind temporarily updating the builds so I can test with the following URL?

https://rawcdn.githack.com/mrdoob/three.js/596833465197d377a447b0819afd77e954fb5cf3/examples/index.html

@RenaudRohlinger
Copy link
Collaborator Author

I pushed the build files @Mugen87 👍

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 29, 2025

The example does not work anymore with the new build. It's not even related to multiview. XR does not work in general.

I have to debug so I can extract the error message.

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 29, 2025

Okay, the error is:

Uncaught (in promise) Error: DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat
at new DepthTexture (three.core.js:29668:10)
at XRManager.setSession (three.webgpu.js:49492:26)
at async onSessionStarted (XRButton.js:38:5)
DepthTexture @ three.core.js:29668
setSession @ three.webgpu.js:49492

Copy link
Collaborator

@Mugen87 Mugen87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you remove the build files from the PR, it is ready to be merged.

@RenaudRohlinger RenaudRohlinger merged commit 84b6feb into mrdoob:dev Apr 30, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants