August 2025
This month my objective was to further work on the procedural world generation. There were still some loose ends when it came to the foliage system and I wanted to improve upon the framework I have for experimenting with procedural generation. The hot reloading I implemented initially has been very valuable, especially for things like the world generation. I make a change, save the file, and the change pops up live in my game. However, the issue is that so far the world generation has been slow, which delays the feedback loop.
Voxels
I have mentioned in past posts that I use a 64-tree to represent my voxels. This is great for the GPU side of things. It is a pain for editing voxels though, since you need to traverse the tree a lot. So I made some changes. Instead of having to deal with one 64-tree per chunk (64x64x64 voxels) on the CPU, I opted for keeping a big brickmap for the whole world, holding 16x16x16-sized bricks, which makes edits and generation a breeze. As I stream things to the GPU I build the trees on the fly hierarchically. If a single brick has changed, I only need to rebuild the sub-tree representing that brick. As a bonus this worked very well for foliage as well, I just tell the system to rebuild the foliage for a brick by providing the root node of the corresponding sub-tree.
Now with everything working I can say it was well worth it, but it was an adventure to get there. Here is some screenshots of my journey:
I think most of my bugs was like the ones above, you have weird bit shifts all over the place. If I remember correctly there were 2 main causes, either me providing incorrect offsets when writing the 64-tree (which in reality is just a stream of bytes). Or, the fact that GLSL provides a horrible developer experience, I utilize 64 bit integers a lot, and they are a bit wonky.
Since I now also pack everything into a single tree, there were also some issues getting that thing right. Transforming world positions into brick index, into voxel index, etc.
Then I needed the foliage system to place nice as well.
Now on to the fun stuff! Previously, I’ve mostly used a SDF scene I wrote myself because I haven’t bothered to go back to actual procedural terrain. Here is a buggy view of some 3D noise. The bug itself is not really related to previous bugs, it just had to do with the noise being too high frequency for the “smart” parameters I setup to not generate more than what is needed.
Here is a more reasonable example of procedural terrain. In this case it’s just a heightmap.
Some ridged fractal noise. In the screenshots all the procedural terrain is just rock but I have some code setup for the coloring as well. It is still a bit slow though so I really want to tighten that feedback loop first.
Profiler
One important aspect of making things faster is actually knowing how fast (or slow) something is. So another thing I worked on this month was a very simple low-overhead profiler. It is similar to tracy but without all the features. I just mark a block of code like this:
{
profiler.beginZone("load_chunk");
defer profiler.endZone();
// Do stuff
}
and it collects statistics on how long that block of code ran. It works in a multi-threaded environment as well, which was needed since I generate chunks in a background thread.
I have no fancy visualizations yet, but we’ll see if I ever implement those. Tracy is a lot better for that. But I do get some nice live stats, and a summary when I close the program.
I also started collecting some more stats on memory usage for my voxels, a bit messy but has been very useful:
Slang
Did I mention that GLSL isn’t great? One thing that has gotten more and more frustrating as the shaders get more complicated is dealing with GLSL. Using Buffer device address in Vulkan is a lot of boilerplate, which was fine up to a point. Even more frustrating is that I have found myself often wrangle the language to make the compiler produce the SPIR-V I want. Since I develop on Mac sometimes I use MoltenVK, which has its owns set of quirks that I need to work around.
In comes Slang. It’s a more modern language for shaders that also compiles to SPIR-V (amongst others). I wanted to see if it could make the developer experience a bit better so I ported all my shaders to Slang. I haven’t dared to touch all the shiny features it provides compared to GLSL but I’m already very happy with it.
Ironically I haven’t been able to fully get it to work on macos yet. I did the mistake of updating the OS so now Xcode has blocked me out of using the graphics debugger until the new version of Xcode releases. I might be able to solve this by changing the build process to target older macos but my first attempts failed so I gave up.
Summary
This type of refactor can be a bit soul crushing but it’s also very exciting. You get the chance to actually do some problem solving and learn from your past mistakes. It’s a balancing act though, having visual feedback helps a lot in keeping yourself motivated but if you get stuck with code that won’t compile for too long, you just feel like giving up.
So, what’s next? Vacation ended in August and the autumn is closing in so I will probably try to focus on something high reward for next month to keep the motivation up. This means I should probably not spend that much time on the procedural generation. Adjusting parameters to get something you are happy with is a never-ending struggle and it tends to drain you, but we’ll see.