Skip to content

TSL: Fix double expressions.#32665

Closed
Mugen87 wants to merge 1 commit into
mrdoob:devfrom
Mugen87:dev2
Closed

TSL: Fix double expressions.#32665
Mugen87 wants to merge 1 commit into
mrdoob:devfrom
Mugen87:dev2

Conversation

@Mugen87

@Mugen87 Mugen87 commented Jan 4, 2026

Copy link
Copy Markdown
Collaborator

Fixed #32589.

Description

I'm not sure this is the best fix but it seems calling toStack() is only required in the method chaining case e.g. when using .discard(). In all other cases, the call can be removed.

@github-actions

github-actions Bot commented Jan 4, 2026

Copy link
Copy Markdown

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 355.36
84.47
355.36
84.47
+0 B
+0 B
WebGPU 621.49
172.57
621.47
172.58
-24 B
+8 B
WebGPU Nodes 620.1
172.32
620.07
172.33
-24 B
+8 B

🌳 Bundle size after tree-shaking

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

Before After Diff
WebGL 487.23
119.23
487.23
119.23
+0 B
+0 B
WebGPU 692.41
187.81
692.41
187.81
-2 B
+1 B
WebGPU Nodes 642.21
175.03
642.21
175.04
-2 B
+6 B

@Mugen87

Mugen87 commented Jan 4, 2026

Copy link
Copy Markdown
Collaborator Author

Unfortunately, statements like break are now missing at other places. I don't understand yet why the behavior differs. Closing for now as it seems the fix must be done elsewhere, potentially in deeper parts of TSL.

@Mugen87 Mugen87 closed this Jan 4, 2026
@mrdoob

mrdoob commented Jan 5, 2026

Copy link
Copy Markdown
Owner

Claude (after 3 tries) suggested doing this... 👀

diff --git a/src/nodes/code/ExpressionNode.js b/src/nodes/code/ExpressionNode.js
--- a/src/nodes/code/ExpressionNode.js
+++ b/src/nodes/code/ExpressionNode.js
@@ -38,7 +38,19 @@ class ExpressionNode extends Node {
 
 		if ( type === 'void' ) {
 
-			builder.addLineFlowCode( snippet, this );
+			// Prevent duplicate generation of void expressions (e.g., discard, return, break, continue)
+			// This can happen when the same expression is both in stack.nodes (via .toStack())
+			// and stack.outputNode (returned from a callback like If(cond, () => Discard()))
+
+			const nodeData = builder.getDataFromNode( this, builder.shaderStage );
+
+			if ( nodeData.generated !== true ) {
+
+				nodeData.generated = true;
+
+				builder.addLineFlowCode( snippet, this );
+
+			}
 
 		} else {

/cc @sunag

@sunag

sunag commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

What makes TSL a middle ground between a shading language and a node system are the stacks; when added a node to the stack, it's equivalent to writing a line of code, while a pure node system creates a dynamic and independent flow.

The problem with the code below is that when we use () => Discard() we are saying that the conditional is returning a value:

If(diffuseColor.a.mul(alphaFactor).lessThanEqual(0.1337), () => Discard())

Which is the same as:

If(diffuseColor.a.mul(alphaFactor).lessThanEqual(0.1337), () => {

	return Discard();

} )

The correct approach would be to not return a value but using void syntax, like the example below:

If(diffuseColor.a.mul(alphaFactor).lessThanEqual(0.1337), () => void Discard())

Claude (after 3 tries) suggested doing this... 👀

I never cease to be amazed by AI. Certainly an approach we use for caching and avoid repetition. My only question is whether this should be linked to the stack and not necessarily to the node expression, because we might have other void nodes that could present an equivalent problem.

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.

duplicate output from TSL

3 participants