Skip to content

TSL: toInspector() is skipped on intent VarNodes, including Fn() results #34489

Description

@tigerxu255-lgtm

Description

Calling .toInspector() on the result of Fn() can silently fail to register an InspectorNode in three@0.185.1. The same expression registers successfully when wrapped with .toVar() or .debug() before .toInspector().

This was noticed when a node was missing from the Inspector Viewer. The registration failure has been independently reproduced at the node-builder setup stage without a browser or GPU. The reproduction below deliberately uses Fn(() => uv().x) to avoid dependencies on a larger scene, custom materials, or shader math.

Source analysis

In the installed version, makeValue() returns a VarNode with intent === true.

  • inspector() attaches an InspectorNode through node.before(...).
  • Node.build() processes the attached _beforeNodes.
  • VarNode.build() directly returns this.node.build(...) when the node is an unassigned intent variable, bypassing super.build(...) and the attached before-nodes.

This appears to explain why .toVar('value') works (an explicit non-intent VarNode) and .debug() also works (a DebugNode wrapper). The issue seems to concern preserving before-node behavior when forwarding an intent variable to its underlying node.

Reproduction steps

  1. In a scratch directory, install three@0.185.1 (npm install three@0.185.1).
  2. Save the following code as repro.mjs.
  3. Run node repro.mjs.
  4. Compare the number of registered InspectorNode instances. The Fn case returns 0, while the direct UV, explicit variable, and debug-wrapper cases return 1.

Code

import * as THREE from 'three/webgpu';
import { Fn, uv } from 'three/tsl';

const makeValue = () => Fn(() => uv().x)();

const cases = {
  uv: () => uv().toInspector('uv'),
  Fn: () => makeValue().toInspector('Fn'),
  'Fn + toVar': () => makeValue().toVar('value').toInspector('Fn + toVar'),
  'Fn + debug': () => makeValue().debug().toInspector('Fn + debug'),
};

for (const [name, createNode] of Object.entries(cases)) {
  const geometry = new THREE.PlaneGeometry();
  const material = new THREE.MeshBasicNodeMaterial();
  const mesh = new THREE.Mesh(geometry, material);

  // Only node setup is tested. This stub does not initialize a GPU or render.
  const renderer = {
    backend: { isWebGPUBackend: true },
    inspector: new THREE.InspectorBase(),
  };

  const builder = new THREE.NodeBuilder(mesh, renderer);
  builder.setBuildStage('setup');
  builder.setShaderStage('fragment');

  createNode().build(builder);

  const count = [...builder.nodes].filter(node => node.isInspectorNode).length;
  console.log(`${name}: ${count}`);

  geometry.dispose();
  material.dispose();
}

Actual output:

uv: 1
Fn: 0
Fn + toVar: 1
Fn + debug: 1

Expected: each case registers one InspectorNode. Inspecting a valid TSL expression should not require an extra variable declaration or debug wrapper.

Workaround

material.colorNode = makeValue().toVar('value').toInspector('Value');

Live example

No hosted browser example is provided. The self-contained Node.js reproduction above isolates the missing registration without needing a DOM, WebGPU device, or Inspector UI. The UI symptom has not been separately automated or captured in this report.

Screenshots

No response

Version

three@0.185.1 (REVISION 185). Verified using Node.js v24.19.0 on Windows. Not runtime-tested against the latest dev branch.

Device

Desktop

Browser

No response

OS

Windows

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    TSLThree.js Shading Language

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions