Skip to content
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 struct #30394

Merged
merged 13 commits into from
Jan 28, 2025
Merged

TSL: Introduce struct #30394

merged 13 commits into from
Jan 28, 2025

Conversation

sunag
Copy link
Collaborator

@sunag sunag commented Jan 25, 2025

Related issue: #29908, #29760

Description

PR introduces struct for TSL following the syntax below:

const BoundingBox = struct( {
	min: 'vec3',
	max: 'vec3'
} );

// style 1
const bb = BoundingBox( vec3( 0 ), vec3( 1 ) );

// style 1 - default value
const bb = BoundingBox();

// style 2
const bb = BoundingBox( {
	min: vec3( 0 ), 
	max: vec3( 1 ) 
} );

// style 2 - optional values
const bb = BoundingBox( {
	// min: vec3( 0 ),  // use optional value
	max: vec3( 1 ) 
} );

// assigns in TSL Fn
const bbFn = Fn( () => {

	const bb = BoundingBox();

	const bbMax = bb.get( 'max' );
	bbMax.assign( vec3( 1 ) );
	// or -> bb.get( 'max' ).assign( vec3( 1 ) );

	return bb;

} );

// debug
material.colorNode = bbFn().get( 'max' );

Copy link

github-actions bot commented Jan 25, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 336.27
78.29
336.27
78.29
+0 B
+0 B
WebGPU 513.63
142.61
516.74
143.47
+3.11 kB
+858 B
WebGPU Nodes 513.09
142.51
516.2
143.37
+3.11 kB
+857 B

🌳 Bundle size after tree-shaking

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

Before After Diff
WebGL 465.25
112.13
465.25
112.13
+0 B
+0 B
WebGPU 587.56
159.27
588.75
159.64
+1.19 kB
+364 B
WebGPU Nodes 542.94
148.83
544.13
149.2
+1.19 kB
+370 B

@sunag
Copy link
Collaborator Author

sunag commented Jan 28, 2025

I updated webgpu_compute_water, now we can use a single storage buffer to send multiples datas.

Previous

// Sphere Instance Storage
const sphereInstancePositionStorage = instancedArray( spherePositionArray, 'vec3' ).label( 'SpherePosition' );
const sphereVelocityStorage = instancedArray( sphereVelocityArray, 'vec2' ).label( 'SphereVelocity' );

Now

const SphereStruct = struct( {
	position: 'vec3',
	velocity: 'vec2'
} );

// Sphere Instance Storage
const sphereVelocityStorage = instancedArray( sphereArray, SphereStruct ).label( 'SphereData' );

@sunag sunag marked this pull request as ready for review January 28, 2025 17:38
@sunag sunag merged commit 2dc9a7e into mrdoob:dev Jan 28, 2025
12 checks passed
@sunag
Copy link
Collaborator Author

sunag commented Jan 28, 2025

It was somewhat well worked, but it worked, I had to make a lot of changes for this to create a new PR. Currently the struct works in both backends except if it is used in storage().

@Spiri0 I added your example and added you as a co-author. I had to make some small modifications to the example as well.

@cmhhelgeson
Copy link
Contributor

I updated webgpu_compute_water, now we can use a single storage buffer to send multiples datas.

Looks cool. If I have time this week, I think I'll update the helper functions in compute_water to return structs and have defined function layouts as well. The current helper functions for indices work fine, but are more fragile than explicitly defining named WGSL/GLSL functions using setLayout, especially if someone tries to call toVar() and assign the same name to variables.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jan 28, 2025

@sunag @Spiri0 Awesome work! 🎉

@Mugen87 Mugen87 added this to the r173 milestone Jan 28, 2025
@Mugen87
Copy link
Collaborator

Mugen87 commented Jan 28, 2025

@cmhhelgeson While you are at it: Can you also check why webgpu_compute_water currently breaks with a WebGL 2 backend? The demo reports a runtime error in the vertex shader:

THREE.WebGLProgram: Shader Error 1282 - VALIDATE_STATUS false
Program Info Log: Must have a compiled vertex shader attached:
SHADER_INFO_LOG:
ERROR: 0:53: 'ndefined' : syntax error
VERTEX
ERROR: 0:53: 'ndefined' : syntax error

The ndefined looks suspicious...


/** @module StructNode **/

class StructNode extends Node {
Copy link
Collaborator

@Mugen87 Mugen87 Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It would be great to document this module and StructType 😇 .

@Spiri0
Copy link
Contributor

Spiri0 commented Jan 28, 2025

Ah here we go, very nice. Now threejs is really drawIndirect capable 💪 😊
Maybe I'll do more extensive examples in the future, but these simple ones convey quite a lot.
Docu is a good idea. I admit that I am not yet familiar with the standards. I have to read the threejs wiki.

@cmhhelgeson
Copy link
Contributor

@cmhhelgeson While you are at it: Can you also check why webgpu_compute_water currently breaks with a WebGL 2 backend? The demo reports a runtime error in the vertex shader:

THREE.WebGLProgram: Shader Error 1282 - VALIDATE_STATUS false
Program Info Log: Must have a compiled vertex shader attached:
SHADER_INFO_LOG:
ERROR: 0:53: 'ndefined' : syntax error
VERTEX
ERROR: 0:53: 'ndefined' : syntax error

The ndefined looks suspicious...

I'll see what I can do, but I've been time limited since the fall, so it might be a bit. Still got to get back to subgroupFunctionNode and additional compute samples as well.

@Methuselah96 Methuselah96 mentioned this pull request Jan 31, 2025
29 tasks
"imports": {
"three": "../src/three.webgpu.js",
"three/webgpu": "../src/three.webgpu.js",
"three/tsl": "../src/three.tsl.js",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmhhelgeson
Copy link
Contributor

@cmhhelgeson While you are at it: Can you also check why webgpu_compute_water currently breaks with a WebGL 2 backend? The demo reports a runtime error in the vertex shader:

THREE.WebGLProgram: Shader Error 1282 - VALIDATE_STATUS false
Program Info Log: Must have a compiled vertex shader attached:
SHADER_INFO_LOG:
ERROR: 0:53: 'ndefined' : syntax error
VERTEX
ERROR: 0:53: 'ndefined' : syntax error

The ndefined looks suspicious...

@Mugen87 I just started looking into this issue twenty minutes ago, and it seems like it might be an issue with the getTypeFromAttribute function in GLSLNodeBuilder? In some of the vertex shaders, it appears like the nodeVarying5 variable is correctly assigned a value of float, but in each shader, nodeVarying5 is being assigned to the value of a node buffer attribute. I'm not sure why this would be necessary, as none of the position/vertex shaders are explicitly passing any new varyings to the fragment shader. The water position shader only modifies the existing varying of v_normalView.

@Spiri0
Copy link
Contributor

Spiri0 commented Feb 7, 2025

I'm looking for the example webgpu_struct_drawindirect
That probably didn't end up in r173. I'm trying to understand why I get the following error message in my app:

127.0.0.1/:1 Error while parsing WGSL: :26:39 error: unresolved type 'DrawBuffer'
fn compute ( drawBuffer: ptr<storage, DrawBuffer, read_write>, )  {
                                      ^^^^^^^^^^


 - While validating [ShaderModuleDescriptor "compute"]
 - While calling [Device].CreateShaderModule([ShaderModuleDescriptor "compute"]).

@sunag
Copy link
Collaborator Author

sunag commented Feb 7, 2025

It looks like the master branch is out of date.

@sunag
Copy link
Collaborator Author

sunag commented Feb 7, 2025

@Spiri0 I updated the master branch.

@Spiri0
Copy link
Contributor

Spiri0 commented Feb 7, 2025

@Spiri0 I updated the master branch.

Ah yes, that was it, thanks sunag. Now I have also found the cause of my error message.
At the end of the structs there must be the name as used in wgslFn. I like it.

Unfortunately it still doesn't work, but I think I understand the reason
It seems like no struct arrays are recognized?

Error while parsing WGSL: :114:185 error: type mismatch for argument 10 in call to 'main_vertex', expected 'ptr<storage, array<ObjectInfo>, read>', got 'ptr<storage, ObjectInfo, read>'
  varyings.Vertex = main_vertex( render.cameraProjectionMatrix, render.cameraViewMatrix, object.nodeUniform2, 0.0, u32( 0.0 ), vertexIndex, instanceIndex, 128.0, &NodeBuffer_572.value, &NodeBuffer_573, &NodeBuffer_574, &NodeBuffer_575 );
                                                                                                                                                                                         ^^^^^^^^^^^^^^^

 - While validating [ShaderModuleDescriptor "vertex"]
 - While calling [Device].CreateShaderModule([ShaderModuleDescriptor "vertex"]).

In my extension, the length of the struct was compared with the length of the StorageBufferAttribute. If the lengths were equal then it was a simple struct
ptr<storage, ObjectInfo, read>
and if the length was an integer multiple then it was a struct array and the WgslNodeBuilder used
ptr<storage, array<ObjectInfo>, read>

const objectInfoStruct = struct( {
	meshID: 'uint',
	meshletID: 'uint',
	padding: 'uvec2',
}, 'ObjectInfo' );

const meshMatrixInfoStruct = struct( {
	modelWorldMatrix: 'mat4',
}, 'MeshMatrixInfo' );

...

//for each meshlet a struct is in the storage array. 
//In this way you can store datas from a large number of meshes excellently

this.objectInfoBuffer = new THREE.StorageBufferAttribute( new Uint32Array( size * this.meshletCount * 4 ), 4 );
this.meshWorldMatrixBuffer = new THREE.StorageBufferAttribute( new Float32Array( size * 16 ) , 16 );
...

I store hundreds or thousands of mesh matrices in one storage. Thanks to the struct arrays,
I can immediately retrieve the data of a specific mesh at any time using just an index.
I use several such struct arrays. Hence the very good performance so that I don't see any frame drops.
struct also doesn't seem to have a .length function with which you can get the number of members.
I didn't use a struct array in the drawIndirect example to keep it simple.
That's probably why it fell by the wayside.

I have to take a closer look at your extension. Maybe you do things differently with the arrays so that I have to explicitly mark struct arrays as struct arrays

@Spiri0
Copy link
Contributor

Spiri0 commented Feb 8, 2025

@sunag For a simple test, I simply intercepted my specific structs in the WGSLNodeBuilder and provided them with an array

if( uniform.node.structTypeNode.name == "MeshMatrixInfo" || uniform.node.structTypeNode.name == "DrawBuffer") {

	bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : ${ bufferType };` );
					
}
else{

	bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : array< ${ bufferType }>;` );
}

In addition, I gave the struct a property to be able to read the number of members:

Object.defineProperty( struct, 'length', {
		
	get() {

		  return Object.keys( membersLayout ).length;

	}

} );

And everything is working fine.
So it's just the little thing with the array for array structs, not a big thing. I can take a closer look at it and think about how to neatly incorporate the case distinction if you wish. Unless you already have a clear idea of ​​it.

P.S. I made a PR for this

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.

5 participants