Skip to content

Behavior change in r180: TSL Fn with struct params loses field access inside function body (emits 0.0); r179 works #31818

@novogrammer

Description

@novogrammer

Description

Observed (r180)

Console warning:

THREE.TSL: Member "f" does not exist in struct.

Inside addFunction, struct member access is replaced with zeros:

// r180, inside addFunction (compute WGSL excerpt)
nodeVar0 = S( ( 0.0 + 0.0 ) );

In main, accessing the returned struct’s field is emitted correctly:

// r180, in main
sc = addFunction( sa, sb );
result = vec4<f32>( sc.f );

Same result when using the render path (material.colorNode + renderer.debug.getShaderAsync).

Expected (r179)

Member access is preserved in addFunction:

// r179, inside addFunction (compute WGSL excerpt)
nodeVar0 = S( f32( ( sa.f + sb.f ) ) );

Reproduction steps

  1. Define a user struct in TSL: const S=struct({f:'float'},"S");.
  2. Define a TSL Fn that accepts S parameters and returns S.
  3. Invoke that function from another TSL Fn.
  4. Generate the shader (compute or render) and inspect the generated source and console output.

Code

import * as THREE from "three/webgpu";
import {vec4,Fn,struct,float} from 'three/tsl'

const USE_RENDER_PIPELINE=false;

function log(message){
  document.querySelector(".log").append(message+"\n");
  console.log(message);
}

async function mainAsync(){
  const scene = new THREE.Scene();
  
  const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
  
  const geometry = new THREE.BoxGeometry( 1, 1, 1 );
  const material = new THREE.MeshStandardNodeMaterial();
  const cube = new THREE.Mesh( geometry, material );
  scene.add( cube );
  const renderer = new THREE.WebGPURenderer({
    forceWebGL:false,
  });

  {
    const S=struct({
      f:'float',
    },"S");
    const addFunction=Fn(([sa,sb])=>{
      const result = S(sa.get("f").add(sb.get("f")));
      return result;
    }).setLayout({
      name:"addFunction",
      type:"S",
      inputs:[
        {
          name:"sa",
          type:"S",
        },
        {
          name:"sb",
          type:"S",
        },
      ],
    });
    const fooFunction=Fn(([a,b])=>{
      const sa=S(a).toVar("sa");
      const sb=S(b).toVar("sb");
      const sc=addFunction(sa,sb).toVar("sc");
      const result = vec4(sc.get("f")).toVar("result");
      return result;
    });
    
    if(USE_RENDER_PIPELINE){
      material.colorNode=fooFunction(0.2,0.8);
      const rawShader = await renderer.debug.getShaderAsync( scene, camera, cube );
      log(rawShader.fragmentShader);
    }else{
      const computeNode=fooFunction(0.2,0.8).compute(1);
      await renderer.computeAsync(computeNode);
      log(renderer._nodes.getForCompute(computeNode).computeShader);
      
    }
  }
}

mainAsync().catch((e)=>log(e));

Live example

Screenshots

No response

Version

r180

Device

Desktop

Browser

Chrome

OS

MacOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    TSLThree.js Shading Language

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions