Skip to content

TSL: Store nodes in temporary variables if they are used in a loop #31636

@shotamatsuda

Description

@shotamatsuda

Description

Related: #30759

A computed node created outside the loop and used multiple times within it is not stored in a temporary variable. In other cases, such nodes are stored in a temporary variable if they are used multiple times.

With #31459, we don't have to explicitly call toVar() before assigning to a variable, and the number of uses is automatically tracked by TSL so we usually don't have to use toVar() to create a mutable node, or look for multiple uses and add caching statements by calling toVar() or toConst(). But with the behavior above, and in a fairly complex node graph, it's very hard to track which nodes rely on a node created outside of the loop, so I'm inclined to add toVar() or toConst() for a safety reason when I cannot afford to debug the transpiled code. But doing this obscures the intent of the code and take less advantage of the automatic optimization.

Example

// Some expensive function:
const g = Fn(() => {
  const sum = vec3(0)
  Loop({ start: 0, end: 10_000, condition: '<' }, () => {
    sum.addAssign(0.0001)
  })
  return sum
})

// A loop relies on the result of the above function.
const f = Fn(() => {
  const t = g()
  const sum = vec3(0)
  Loop({ start: 0, end: 10, condition: '<' }, () => {
    sum.addAssign(t)
  })
  return sum
})

outoutNode = f()

The above outoutNode is transpiled as:

// vars

var nodeVar0 : vec3<f32>;
var nodeVar1 : vec3<f32>;

// flow
// code

nodeVar0 = vec3<f32>( 0.0, 0.0, 0.0 );

for ( var i : i32 = 0; i < 10; i ++ ) {

	nodeVar1 = vec3<f32>( 0.0, 0.0, 0.0 );

	for ( var i : i32 = 0; i < 10000; i ++ ) {

		nodeVar1 = ( nodeVar1 + vec3<f32>( 0.0001 ) );

	}

	nodeVar0 = ( nodeVar0 + nodeVar1 );

}

// result

output.color = vec4<f32>( nodeVar0, 1.0 );

Calling toVar() or toConst() on t creates a temporary variable outside of the loop, as stated before.

If we have another loop, the latter loop uses the result in the previous loop, which is also a weird behavior:

const f = Fn(() => {
  const t = g()
  const sum = vec3(0)
  Loop({ start: 0, end: 10, condition: '<' }, () => {
    sum.addAssign(t)
  })
  Loop({ start: 0, end: 10, condition: '<' }, () => {
    sum.addAssign(t)
  })
  return sum
})

This is transpiled as:

var nodeVar0 : vec3<f32>;
var nodeVar1 : vec3<f32>;

// flow
// code

nodeVar0 = vec3<f32>( 0.0, 0.0, 0.0 );

for ( var i : i32 = 0; i < 10; i ++ ) {

  nodeVar1 = vec3<f32>( 0.0, 0.0, 0.0 );

  for ( var i : i32 = 0; i < 10000; i ++ ) {

    nodeVar1 = ( nodeVar1 + vec3<f32>( 0.0001 ) );

  }

  nodeVar0 = ( nodeVar0 + nodeVar1 );

}

for ( var i : i32 = 0; i < 10; i ++ ) {

  nodeVar0 = ( nodeVar0 + nodeVar1 );

}

// result

output.color = vec4<f32>( nodeVar0, 1.0 );

Solution

If a node used in a loop satisfies the following conditions, for example:

  • It is not a variable or constant node.
  • It has no dependencies on the nodes in the loop stack.

then assume it may be used multiple times, and store it in a temporary variable outside of the loop stack.

Alternatives

Add documentation on the limitation about the automatic optimization.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugTSLThree.js Shading Language

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions