How to understand metal compute shader code?

Hello @caroline

I hope this question is allowed, as it is related to a different repo/different tutorial, but it is Metal based.

and I am using Your book, to try to understand that code.

and related youtube video about marching cubes implementation in Swift.

what I see when I execute the app is 5 spheres, which blend into objects, if the spheres interfere with each other. what I also ( hopefully ) understand is, that if certain vertices are located within BOTH spheres, then they are not rendered, so at the end only outer shell is visible.

what I however fail to understand quite desperately is… HOW the spheres itself are rendered? I know, they are centered around those red center points… but I could never find piece of code ( and its hidden somewhere in compute shaders ), which provides vertices for these spheres.I was able to get snapshot of GPU frame, find the vertices in memory, in buffer, etc… but HOW are they calculated? if you have time and would like to help me… or anyone else. if you look at the shaders.metal code… can you see, where the spheres do get their vertices? can someone give me some short explanation to this?

many many thanks

Marek

As far as I can make out, this is what is happening.

Along with the MTKView’s render delegate draw, there’s a parallel compute update going on.

In ViewController.swift, viewDidAppear(), a timer is set up to run paceTimerHandler().

paceTimerHandler() performs world.update(self) which calls computeShader.processBallMovement(). This creates a separate command buffer, which has compute shaders that update ballData.

world.update then takes ballData and copies it to ballPoints.

The render loop’s draw(in:) calls world.render, which creates a vertex buffer out of ballPoints and renders the balls and then the cube.

1 Like

Thx a lot for the answer.

Can you understand There, how exactly the vertices are generated from/around these Ball points? This is what I am missing. Also… How can I control number of vertices for a Ball? And e. G. Radius?

This seems to be hidden in one of the compute shaders

Thx a lot!!

I don’t know the algorithm at all, so I can only tell you what I’m seeing in the code.

If the Metal debugger doesn’t help, then I find variable and function names that look useful and do Cmd-Shift-F to search for them in the project and follow them through. Sometimes I do a print, so for example, I know that by printing out iCount in Cube.render, I can see the number of indices being rendered.

I also use Cmd-Shift-A to jump to the definition of an object.

The balls are drawn in Cube.render.

Background.keyDown shows that you can press the letter V to change from textured triangle to line drawing and 1 & 2 to make the spheres larger and smaller respectively. This updates control.isoValue or cd.isoValue on the GPU, which is used by the compute shaders as, I guess, the radius of the sphere.

ComputeShader.update runs several compute shaders. The first updates the grid and the second updates the flux points. I have no idea about the algorithm, so I guess that’s where the magic happens.

The third compute shader is pipeline[PIPELINE_UPDATE_VERTICES], which runs the shader determineVertices where I guess the vertices are written to the buffer. That function uses eTable and tTable which are look-up tables. I don’t know what they are, but tTable values are 0 to 11, which implies that they are less than the “flux points” count of 12 (BCOUNT).

GSPAN is the size of the cube.

The 12 balls’ central point position data is created in the shader calcBallMovement.

Aside from that, the code is quite complex, and I don’t know the algorithm, so I don’t really understand what’s happening.

Edit: But this might help! Polygonising a scalar field (Marching Cubes). That explains the lookup tables at least.

1 Like

Thx a lot

Probably Last question here

Those shader func have ID param As a thread_pos_in_grid

Can you explain, What is the grid the function is using? If I have img, its clear. But can I tell metal to e. G. Proces s volume from 0,0,0 to 10,10,10? I am missing bit of logic here

Thx

Have you read Chapter 16, GPU Compute Programming? This chapter explains threads and thread groups. The grid is the size of whatever you process, and you decide that.

You can have a 1d, 2d or 3d grid.

The chapter code sets up a 1d grid, to process each of the vertices in turn. An image is 2d, but your app deals with volumetrics, and so is 3d. With a 3d grid, to identify the exact thread, you need the x, y and z of the thread_position_in_grid.

GSPAN is the value for the number of threads in each direction. This is the size of the enclosing box. If you change it from 50 to 30, it’s a smaller box. By having an x, y, z, you can identify each voxel inside the enclosed box area.

1 Like

ok… tbh I have probably missed this, or missed the dependencies.
so if I apply this to 183756th Minecraft clone available… as a part of learning Metal/3D graphics in general, it means, that I can freely choose size of box ( when dispatching Threads), then code will run for my given points, AND… when rendering e.g. indexed vertices ( because my box for compute shader might e.g. be 100x100x25 big, but the world might be magnitude bigger ) , I can specify base point for part of the world to be rendered, so the compute shader runs only for the relevant part of the world, that is being updated… is it like this?

thx a lot

That isn’t a question I can answer. I do know from experience that copying code that I don’t completely understand never works in the long run.