Compute Shaders Order Execution

What is the code that generates this log? It should not happen…

What is the material you are using to display your meshes? If it is a standard or PBR material, it should compute the normal if no normals are provided through a vertex buffer.

If you could provide a repro in the Playground that would help a lot!

It’s a bit difficult for me to extract some code in a Playground, as my developments are part of a bigger project. But to recap the procedure leading to a crash in computing the normals:

  1. I am generating a basic Ground mesh:

this._computeDomain = BABYLON.MeshBuilder.CreateGround("ground", { width: this.domWidth, height: this.domHeight, subdivisionsX: this.domSubdivx, subdivisionsY: this.domSubdivz, updatable: true}, 
        scene);
const mat = new BABYLON.StandardMaterial("ComputDomainMaterial", scene);
this._computeDomain.material = mat;
  1. My compute shader is generating a new set of vertices, so prior to Dispatch I am defining some buffers:
this._vertexBuffer_storage_update = new BABYLON.StorageBuffer(this._engine, this._vertexBuffer_byteLength, 
            BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_READWRITE);

this._vertexBuffers_update = new BABYLON.VertexBuffer(this._engine, this._vertexBuffer_storage_update.getBuffer(),
            BABYLON.VertexBuffer.PositionKind, true, false);
  1. In the compute shader, my new vertices data are stored in the “this._vertexBuffer_storage_update” storage buffer.

  2. After Dispatch, I update the Vertex Buffer of the original Ground Mesh:

this._geom.setVerticesBuffer(this._vertexBuffers_update, true);

The geometry is well updated, the mesh of the new shape is correct if I enable the wireframe. However the lighting is totally wrong because of the normals, and in the inspector, if I want to render the Vertex Normals, I get the following error:


Cannot read properties of null (reading '0')
TypeError: Cannot read properties of null (reading '0')
    at Vector3.FromArray (http://localhost:8080/js/babylonBundle.js:35161:33)
    at MeshPropertyGridComponent.renderNormalVectors (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:107068:84)
    at Object.onSelect (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:107329:3507)
    at CheckBoxLineComponent.onChange (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:115565:24)
    at onChange (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:115588:1348)
    at HTMLUnknownElement.callCallback (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:69644:14)
    at Object.invokeGuardedCallbackDev (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:69693:16)
    at invokeGuardedCallback (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:69755:31)
    at invokeGuardedCallbackAndCatchFirstError (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:69769:25)
    at executeDispatch (http://localhost:8080/js/vendors-node_modules_babylonjs_inspector_dist_babylon_inspector_bundle_max_js.babylonBundle.js:73942:3)

If I read manually the storage buffer and use the ComputeNormals method, it will work but it is indeed a copy to CPU memory that we would like to avoid.

I hope the problem is self-explanatory enough?

Everything looks normal, even the crash, as there is no “normal” vertex buffer, the “Render vertex normals” can’t work.

So, this question remains:

If you use a standard or a PBR material it should work, as this PG demonstrates:

I have removed the “normal” vertex buffer from the sphere, but the lighting still works (in a flat shaded mode, though, as the normal computed in the fragment shader is the same for all pixels of a triangle).

Ok it works, I think the issue is that “Render Vertex Normals” in the inspector was still accessible despite the empty buffer. With the RemoveVerticesData method, I have now the geometry correctly lighted in a flat shaded mode and the Render Vertex Normal disappeared in the inspector.

Now the question at 1b$: is it possible to get a smooth shaded mode from the fragment shader without initially providing the normals?

It’s not possible, there’s too few information in the fragment shader for that.

An alternative of doing the calculation of normals on the CPU would be to do it in a compute shader. You can get some pointers in this thread from the Unity forum:

https://forum.unity.com/threads/calculating-normals-of-a-mesh-in-compute-shader.1347302/

Ok thank you for your help, I think it is pretty clear now.

It could be a nice demo about the capabilities of WebGPU to accelerate the compute of some visualization elements, I think I’m going to put an engineer of my team on this topic :smiley:

Hi, sorry to reopen this thread, but I wanted to give an update on this topic.

As you suggested, I have developed a compute shader to perform the calculation of normals on the GPU rather than sending back the vertex buffer to the CPU and call ComputeNormals(). Here is a quick-and-dirty profiling analysis with PIX. To recall the context, I am solving the 2D wave equation on a plane discretized, here with 801x801 vertex.


The Compute Shader involves a couple of atomic operations, and as expected, it takes quite some time to execute the kernel. However, on this case, it is still faster than doing a copy of the buffer back to the CPU. Overall, one update call takes 2.45ms with the normals computed on the GPU, rather than 3.45ms if we copy back the buffer to the CPU.

Of course, more substantial analysis remains to be done with different mesh resolutions, but I’m quite happy with the direction this work is taking. Many thanks for your help, that was super instructive ! And I love WebGPU :smiley:

1 Like

Not to mention the fact that when reading from the GPU to the CPU, you’ll suffer a one-frame delay each time, due to the asynchronous nature of the call.

I have a doubt here. If I read a storage buffer in a promise, I am not doing a copy to CPU, right? For example here:

this._normals_storage.read().then((res) => { 
  this._geom.setVerticesData(BABYLON.VertexBuffer.NormalKind, <Float32Array>res.buffer);      
});

is it a good practice? I haven’t found another way to update the normals directly from a buffer. Is there something similar to “setVerticesBuffer” but working for normals ? Thanks !

Yes, you do a read to CPU by doing that, and it is delayed by one frame because it is using a promise (the promise is resolved after we finished processing the frame on Babylon side).

You should create a storage buffer with the VERTEX flag, so that you can use it as a vertex buffer.

See how it is done in the Boids compute shader example:

Creation of the buffers:

this.particleBuffers = [
    new BABYLON.StorageBuffer(engine, initialParticleData.byteLength, BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE),
    new BABYLON.StorageBuffer(engine, initialParticleData.byteLength, BABYLON.Constants.BUFFER_CREATIONFLAG_VERTEX | BABYLON.Constants.BUFFER_CREATIONFLAG_WRITE),
];

(there are two buffers, because we do a “ping-pong” in this example)

Create the vertex buffers by using the storage buffers created above:

this.vertexBuffers = [
    [
        new BABYLON.VertexBuffer(engine, this.particleBuffers[0].getBuffer(), "a_particlePos", false, false, 4, true, 0, 2),
        new BABYLON.VertexBuffer(engine, this.particleBuffers[0].getBuffer(), "a_particleVel", false, false, 4, true, 2, 2)
    ],
    [
        new BABYLON.VertexBuffer(engine, this.particleBuffers[1].getBuffer(), "a_particlePos", false, false, 4, true, 0, 2),
        new BABYLON.VertexBuffer(engine, this.particleBuffers[1].getBuffer(), "a_particleVel", false, false, 4, true, 2, 2)
    ]
];

Set the vertex buffers to the mesh:

this.mesh.setVerticesBuffer(this.vertexBuffers[this.t][0], false);
this.mesh.setVerticesBuffer(this.vertexBuffers[this.t][1], false);
2 Likes

Sorry it was a stupidity on my side. Actually, I already tried what you describe but it failed miserably, and I came too quickly to the conclusion that it was because “setVerticesBuffer” is only for vertex data. The issue is just that I forgot to put the "BUFFER_CREATIONFLAG_VERTEX " flag at the creation of the vertex buffer for normals.

Now it works very well, in PIX I can see that I no longer have any “CopyBufferRegion” calls and I am going down to 1.7ms by update call. That’s impressive! A performance gain of roughly 100% thanks to WebGPU !