After the changes to its size and general layout for the last release I have decided to make the seaport a much more modular building than before. The following screenshot shows a seaport with 2 container terminals and 3 terminals for oil/gas/liquids:
A regular seaport will start out with enough space for 2 terminals. A pier can then be constructed, allowing up to 5 terminals at a single seaport. Not all of them have to be built immediately or at all; and they can be any combination of all available terminals: Dry Bulk, Container, Oil/Gas/Liquids, ‘General’ (= everything else).
Animations for the seaport are probably the most - or at least one of the most - complex animations of the game. Not only is the footprint bigger than any other building, but it also requires support vessels (actual ships) of sizes equal to multiple other whole buildings. Add to that animations to dock/undock ships and all the pathfinding code to have those ships move from a terminal towards the edge of the map and hilarity ensues. It might well be that animations for the seaport amount for nearly half of all the games art workload.
Not So Many Optimizings
My first optimization attempt for this release was aimed at the Apple side of things: using MoltenVK (I wrote something about it here). Everything went swimmingly until I tried to load the shaders, which failed. While the included tools could reconstruct the GLSL code from SPIR-V perfectly, they just dropped a declaration for a struct when reconstructing MSL. The same happened when converting to MSL from GLSL directly. So for the moment I will leave it at that and keep an eye on further development of MoltenVK. Especially if there is an uptick in its development: since its release there were only superficial updates and not so many issues reported. I wonder what the future has in store for it.
For Vulkan I tried to further optimize the shadow rendering. By moving the relevant code over to a geometry shader based solution (the more classic approach for stencil shadows) I have noticed two things:
Using the geometry shader results in a very clean and compact implementation of shadow volumes, but without many options for tweaking it further, since it is very straightforward code.
It does not change the overall time taken to calculate and render the shadow volumes in any significant way. But for the compute based approach I have at least a few ideas left that I can try to reduce its footprint.
However, in the end it all comes down to fill-rate limitations, the underlying problem which I cannot change. I guess this is the price one has to pay for sample perfect shadows. The only real up-side of using the geometry shader for shadow volumes is that it can render an unlimited amount of shadow volumes. For the compute shader I need a temporary buffer to write the shadow geometry to, which can then be used to render the volumes. This begs the question: how big should this buffer be? No such buffer is needed for shadows via the geometry buffer. But in the end the fill-rate puts an upper limit on the number of concurrent shadow volumes anyway.
Another failed attempt at optimization was to teach the exporter for LP3 models (that is the name of the file format Margin Magnate uses for its 3D models) to only output edges which can happen to be silhouette edges. And while this reduced the file size and memory consumption of the models, it did nothing significant for the time it takes to run the compute shader doing the silhouette detection. Actually I would have expected a noticeable difference since the shader has to be dispatched for less edges (up to 50% less!), but there was no significant change at all.
TL;DR: Went straight into three dead-ends; lessons learned.
But I was able to reduce the time it takes to render the shadow volumes by a factor of 2-3, depending on scene composition. This is huge, considering it is the most time-consuming part of a frame. The problem with this: I am quickly running out of ideas on how to further optimize this section. On the other hand: my guess is, that this is only 2 or 3 times slower than using shadow maps, looking on how long it takes to render everything to the g-buffer (the shadow map requires a much bigger resolution than the g-buffer, while the g-buffer has to write to multiple images instead of just depth values; give some, take some). I will most certainly look for more ways to optimize this part of the engine, but I still think that the performance cost is worth sample-accurate shadows. In other engines textures add noise, aiding in hiding aliasing artifacts of shadows. Texture-less low poly graphics do not have that luxury. Further more there will be lots of tiny geometry, which in turn does not play well with shadowmaps, unless it is really high-resolution (8k textures and above? That is a whopping 256 MiB texture).
Either I will continue working on models and their animation or I will take a closer look at the map generating code (since it is home to the only known source of crashes (actually failed assertions) and needs some major polishing touches anyway).