-
-
Notifications
You must be signed in to change notification settings - Fork 35.9k
TSL: Introduce uniformFlow()
#31531
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
TSL: Introduce uniformFlow()
#31531
Conversation
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
Ideally we could just overload select, switching between ConditionalNode select and nativeSelect depending on the renderer backend. However, I'm not sure if it's a great approach to push TempNode code into ConditionalNode just to make that happen. Is there any mechanism within the API that would allow us to do something like this? export const select = backend === 'WebGPU' ? nativeSelect : conditionalSelect Alternatively, you can just switch between select based on the backend. import {nativeSelect, select} from 'three/tsl
const selectFunc = WebGPU.isAvailable() ? nativeSelect : select |
Wouldn't they be different things? WGSL's |
Yes, on a technical level the TSL select and the WGSL select() (which is represented in Node form by nativeSelect), are two separate implementations/approachs to conditional logic within the shader. Accordingly, TSL select() and WGSL select()/TSL nativeSelect() will generate two different code strings. const computeFn = Fn(() => {
const {arbitraryUniform} = effectController;
currentStorage.element(instanceIndex).assign(select(arbitraryUniform.equal(0), 20, 40))
} // TSL select()
if ( ( object.nodeUniform1 == 0.0 ) ) {
nodeVar0 = 20.0;
} else {
nodeVar0 = 40.0;
}
Current_Left.value[ instanceIndex ] = u32( nodeVar0 ); // WGSL select()/TSL nativeSelect()
Current_Left.value[ instanceIndex ] = u32( select( 40.0, 20.0, ( object.nodeUniform1 == 0.0 ) ) ); However, the functional purpose of both of these code blocks is the same: When assigning a variable, select between two options based on the provided condition. For me, the benefits of having the later approach be the native WebGPU approach are obvious. We (hypothetically, depending on how WGSL's select works under the hood on different APIs) avoid unnecessary branch divergence, align more closely with how the user intuitively expects the select functionality to work, and, as you mentioned, For instance, in WGSL, this code is invalid when using the existing TSL select() Node. const computeArbitraryFn = Fn( () => {
const subgroupMetaRank = ( invocationLocalIndex.div( subgroupSize ) );
const currentElement = inputStorage.element( invocationLocalIndex );
// Add together values that are only in first subgroup of a workgroup
currentElement.assign( select( subgroupMetaRank.equal( 0 ), subgroupAdd( currentElement ), 0 ) );
} )().compute( size ); // TSL select()
if ( ( f32( ( invocationLocalIndex / subgroupSize ) ) == 0.0 ) ) {
nodeVar0 = subgroupAdd( Current_Right.value[ invocationLocalIndex ] );
} else {
nodeVar0 = 0u;
}
Current_Right.value[ invocationLocalIndex ] = nodeVar0;
// Error while parsing WGSL: :50:14 error: 'subgroupAdd' must only be called from uniform control flow
// nodeVar0 = subgroupAdd( Current_Right.value[ invocationLocalIndex ] ); When using nativeSelect() it works perfectly fine. // WGSL select/TSL nativeSelect()
Current_Left.value[ invocationLocalIndex ] = u32( select( 0.0, f32( subgroupAdd( Current_Left.value[ invocationLocalIndex ] ) ), ( f32( ( invocationLocalIndex / subgroupSize ) ) == 0.0 ) ) ); In fact, the whole impetus behind implementing nativeSelect is largely to facilitate the use of code like that seen above in the Compute Reduction (#31378) example. The only bottleneck to switching over every WebGPU example that uses select to nativeSelect is compatibility with the WebGL2 backend. As I suggested in the comment above, and since the core functionality of both select() and nativeSelect() is essentially the same, even if the implementation is different, this could be solved by a mechanism that providing a different select depending on the backend the program is using. That way, WebGPU users could get the most performant and flexible version of select while the WebGL backend continues to use the existing ConditionalNode logic. Alternatively, we can avoid this workaround all together and just expose the nativeSelect functionality to users who wish to exclusively target WebGPU and want the native WGSL select function's associated benefits. NOTE: May test other examples that use select with nativeSelect later to see if any marginal improvements exist. |
I think this could be easily solved with a TSL function.
I can't imagine how it could be more performant to execute two flows to deliver one, especially if we are executing complex operations within the conditionals.
The intention is not to make TSL similar to WGSL in this sense, but rather to simplify it. Uniform control flow is currently ignored, and in another case the implementation should replace this at runtime and detect this limitation through the compiler(NodeBuilder) and not leave this responsibility to the user. It's not the kind of redundancy I'd want to bring into TSL, that should be higher abstraction language than WGSL. |
That's understandable. For now, would it at least be possible to expose both options so there's an option for those who want to use the more abstracted version of select and another option for those who want to target specific behavior that's only possible with the WGSL select? The comment/function signature for native select can be adjusted to heavily emphasize that the nativeSelect should only be used in specific WebGPU use cases where select does not apply and where programs are only targeting WebGPU. I'm open to any solution that allows this functionality to enter the repository for certain use cases without harming the larger goals of the library. |
Could you contextualize an uniform control flow? This could be using // ContextNode.js
export const uniformFlow = ( node ) => context( node, { uniformFlow: true } );
addMethodChaining( 'uniformFlow', uniformFlow ); You can use it in myFn().uniformFlow(); This would be more in line with an automatic detection we can do in the future. The |
Sure, I'll try to implement it this way. I'll also try to find areas in the existing examples where setting uniformFlow to true makes sense. |
@sunag Would it make sense to rename nodeProperty in ConditionalNode to propertyName ala TempNode? It seems like they both hold property names for their respective nodes. |
…dvantage of more existing flow code functionality
Hi Christian,
I'm writing to confirm that the code has been reverted.
Thanks,
Brant
…On Mon, Aug 4, 2025, 1:58 PM Christian Helgeson ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/nodes/math/ConditionalNode.js
<#31531 (comment)>:
> @@ -167,7 +183,8 @@ class ConditionalNode extends Node {
}
- builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' );
+ builder.addLineFlowCode( ifSnippet, ifNode );
Code has been reverted.
—
Reply to this email directly, view it on GitHub
<#31531 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BVR3YHQOBIKVTDK57CE6YVT3L6UPDAVCNFSM6AAAAACCU6ED2GVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTAOBVGQYDEMRRHE>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Merging, thanks! |
Description
Add the ability to access the native select functionality within WGSL for programs specifically targeting the WebGPUBackend. Arguments are in the same order as the existing TSL select, and are rearranged in the generate build stage.