Electricity in a tilemap with compute shader limitations

Started by
7 comments, last by JWColeman 5 years, 4 months ago

Hello there.

I'm not 100% sure this is the appropriate sub-forum but this question is probably more about programming than design.

I'm making a game with some mechanics loosely inspired by Factorio (plays like Terraria) and since I want to allow the player to build massive machines and factories all the tilemap logic runs in the GPU and the map data stays there in the GPU memory since it's potentially several gigabytes. That implies in many constraints, especially because there's no recursion in compute and overall it's quite limited (I'm using direct compute SM5, OpenCL is too cringy, Sycl is still in its infancy, Cuda is vendor locked and there's no console support for these techs anyway).

So here's the problem: I want electricity to play a role in the game mechanics. The simplest solution by far is just making it global and adding/removing from it as generators/consumers are ticked, but that's gonna suck terribly, so I've been thinking about decent solutions to that problem that still meet the technical constraints imposed.

I thought about an electrified 'area' around the power generators and also power lines like Factorio (similar to Sim City). The problem is transmission, I'll have to calculate the electricity flow and that's not simple to perform in the GPU. What I could do is make a bitmask for each power network and then do a bfs on CPU and attribute an ID for each connected power network, then when I'm ticking the power generators and the consumers I get the ID of the power network (for that tile) and add/remove electricity from that network, so the electricity is stored in a value accessed through the ID for that map tile. One of the problems here is when power networks overlap in area but don't interconnect, I think in this case exclusion is acceptable (only one network can supply a tile), or just automatically connecting networks that intersect.

The real problem though is the potentially catastrophic result of having too many networks. As I said this is a really massive game with possibly hundreds of millions of actively updating 'tiles', and since each network will require not only a bitmask but also an entry in some array that stores the electricity, the number of networks will have to be capped much before a quantity that's reasonable for the overall magnitude of the game. Maybe say 16 or something because passing data to GPU is very slow. So while it's entirely possible to simply arbitrarily limit the number of power networks in the game, that goes against the basic premise of it.

Maybe you guys have another idea on how to handle that. The usual practices are already taken into account like for example using reduced maps for the power grid and such (2x2 or maybe even 4x4), but those aren't solutions. A 'solution' would be to pass an int32 for each power grid tile (holding the network ID) but that's prohibitive in terms of performance. It is possible to flood fill the networks iteratively in the GPU so no data has to be passed, but that would not only cause some delay in converging to the correct solution but would require double buffering and optimizations aren't going to be trivial, for a starter looping the whole map and allocating values for the worst case scenario could work, but optimizing that is going to be a major pain in the rear (especially compartmentalization) and definitely bug prone because it's a whole other layer dependent on the base map data that has do bee synced.

So do you guys have any other idea on how that can be done?

Advertisement

An easy solution to this problem would be to simply split the world in chunks of say 8x8 tiles and store the energy in each of them and propagate iteratively in the shader (only in the chunks with network ofc). Additionally a vector field could be calculated form the energy 'flow' in order to propagate it directionally. The only problem is that there are gameplay implications, and a vector field even with halves takes a lot of memory. Maybe a simplified direction could be an option. It would be nice if I could use 16 bit for the energy and 16 for the flow direction but unfortunately the types and interlocked operations are very limited.

Could you draw on paper how actually you see this design of yours how power generators are attached to what. Because from what you already wrote i would store an array of connected devices per generator.

Well, to be honest I didn't have any strict design, my only requirements were something that makes sense and isn't lame (as global energy), and that is doable in compute shader. I've managed to come up with a decent solution though, I'm maintaining a map of 8x8 tiles, for each power consumer I add the consumption to the respective tile group, and then I simply propagate that number iteratively through the network, so when the electricity 'flows' it 'splits' according to that difference between the groups.

One easy way to 'visualize' my solution is to think of the power consumers as having 'weight' and that deforms the terrain accordingly, and then electricity flows down the hills or something. It's not the same thing because the way it's split is according to the proportion of the 'slope' of neighbor cells, but you get the idea.

In any case thanks for replying @_WeirdCat_ ?

Performing Game Logic in the GPU seems cringy altogether.

Quote

I'm making a game with some mechanics loosely inspired by Factorio (plays like Terraria) and since I want to allow the player to build massive machines and factories all the tilemap logic runs in the GPU and the map data stays there in the GPU memory since it's potentially several gigabytes. That implies in many constraints, especially because there's no recursion in compute and overall it's quite limited (I'm using direct compute SM5, OpenCL is too cringy, Sycl is still in its infancy, Cuda is vendor locked and there's no console support for these techs anyway).

Why not bring a portion of your map data onto the CPU, at least, tile locations as represented by a grid? As soon as you start talking about electricity(a game concept) rather than the color of a tile, the GPU is not the thing to figure this out?

 

Of course, you don't need all the data for each tile, just its logical position on your map, x and y. Not sure how quickly this would reach "gigabytes" of data?

I agree that doing game logic on the GPU isn't good for everything, especially in compute shaders which are very limited (I didn't know they were so limited before starting this though). I've tried OpenCL before, but like pretty much anything Khronos do it's unbearable. Sycl seems to be a more sane alternative, but there's no implementation yet, there's a beta one but it's commercial and I'm an indie. Also, OpenCL means no console support, I want to be able to deploy on consoles eventually.

I surely could use the CPU for the electricity, I could simply keep an array of the generators/network elements and store the position in the array entries, that would allow me to run some decent simulation, but the problem is scalability. This game is supposed to be massive on an scale never seen before, and that will not scale decently, and to make things worse the interface I use to communicate with the GPU will already be saturated in normal gameplay (because some elements are CPU).

Gigabytes of data are unthinkable, I want the game to run at 60fps so the data per frame has to be at most some dozens of MBs.

There are of course many limitations in this approach I'm taking and compromises have to be made, but I realized that compromises even though they might sound bad at first they not necessarily affect gameplay negatively. After all the important is having fun, a typical example is constraining positions to a grid and object sizes to a cell of said grid. That's a constraint many games in the past used for technical limitations, but it turns out they might be desirable so even these days when games just use float vectors for positions and unaligned height maps for terrain many still arbitrarily apply those constrains.

The challenge I'm facing here is designing fun mechanics within those limitations.

EDIT: btw, your github link is incorrect @JWColeman

Many games with electricity grids use an approach where the grid is not perfectly updated each frame but loses/gains connections more slowly as spare cycles are used in the simulation. You could also store only where the electricity has changed and propagate this out, so you are only updating electricity where it has potentially changed rather than needing to constantly update the whole grid.

On 12/8/2018 at 11:42 AM, AlanGameDev said:

EDIT: btw, your github link is incorrect @JWColeman

I can't really speak on the scale of things, but for this, yeah. Currently I'm using microsoft team services GDnet does not provide a special link for that, even though its technically a github. All you have to do to make the link work is remove the github stuff and just paste in the https://jwcoleman87.visualstudio.com/MyGame2/_git/MYGAME2 part. 

But, for the other long part, I'm afraid I can't fathom the limitations you are facing with the scope of the "bigness" of your map, so I'm generally unhelpful in that part :(

 

This topic is closed to new replies.

Advertisement