So I am attempting those new ways of sending data to the GPGPU-aspects of a graphics API through Unity for many entities concurrently, and came across this:
It’s a showcase of how to update 1 million cubers from the CPU to the GPU. I tried this approach, but even with less entities (tens of thousands) and with less data (2xuint4) while writing less data (right now I just add inputs to player 1) it still seems to be a lot slower (in ms) to call .BeginWrite and .EndWrite for writing to a buffer than I had thought. However, I have two buffers, not one, as they are both updated independently for different compute kernels.
When not calling .Begin/.EndWrite the renderthread sits at a “rock solid” ~0.3ms, but with it, it goes up to ~9ms.
I found I can call it every other frame at 30 updates per second, but this gives some really awkward frame-time pacing. I see in the other suggested threads that I might have to do a lot of things which this example of the 1 million cubes aren’t doing, yet somehow works for this specific example. What am I missing?
Thank you all very much in advance for helping out.
You are potentially missing a lot of things. It would help to show code and the timeline view of the profiler capture.
Here’s a few quick tips just in case:
Make sure you are cycling your upload buffers. Calling BeginWrite() on the same buffer multiple frames in a row can cause problems.
Check your poly count per frame. If it is cubes, fine. But if it is something else, you might have blown your polygon budget.
Make sure you are using compute shaders to transfer the upload buffer data into GPU-resident buffers. The GraphicsBuffers that are made with BeginWrite() compatibility are CPU-resident, GPU-accessible buffers.
I have tried a few different variations now. The code for the test is here:
However, as mentioned I have doubled this as I have two buffers that updates independently as they write to different kernels based on demand. I update right now every other frame, at 30 updates per second, sending about 1048576 bytes (or around 1mb) per frame in data at most from the CPU to the GPU. That’s bitpacc’d position, rotation, scale, and user inputs, as both come from the player and gameplay logic existing on the CPU. The GPU only animates and renders accordingly.
I know that cascaded shadowmaps add a lot to the rendering, but when turnt off it manages to keep a stable 90fps. Polycount has never been an issue somehow.
And yes, this is compute shader buffers with ComputeBufferMode.SubPrimeMortgage or Updates or whatever unity calls it these days (I have severe graphics API fatigue after doing this since I expected someone else to have made this solution and I couldn’t find a single one that does exactly this and I had to spend almost 3 months fighting unity to make it work and I am now way behind on everything).
I will try a few more versions to see how it goes. The overhead isn’t “bad” as it’s still within 60fps at worst, but I want to be able to write to the GPU without that much overhead. I wouldn’t think it would be that much.
[Update] I have now tried a bunch of different ways of doing this. It seems the render thread overhead is a constant and consistent overhead from writing a lot of data from the CPU to the GPU. Nothing has changed from my original post.
Here’s a code example of what I tried (the ONLY version that didn’t give any errors), but still gave 9ms overhead with 65536 entities:
This code does not show where you are rotating buffers. The original example you are referring to did not rotate buffers either, and it was also seeing the performance penalties of that. It just so happened the author’s computer did not suffer as badly from those issues. You need to rotate buffers, because GPUs like to process several frames at a time. And if you start a new write on a buffer the GPU is still using, then it needs to pause and finish that frame before the write can commit. Usually a rotation of 4 frames is a safe bet, but it is platform-specific.
What is the problem? Odds are, someone already did. Though there’s a chance they solved the problem with BatchRendererGroup instead, since it handles persistent instances with culling with much less data rearrangement requirements.
Ok, so I will assume the GPU will always be X frames behind in processing as I cannot allow for variations to happen here as it’s about processing positions and animations.
I had originally intended the inputs to be updated at 200ms intervals to mimic human reaction time since it’s for NPC-animation data. The positions have to be more frequent, but I can interpolate that, as I interpolate the animations otherwise.
Edit:
Reducing updates helped a lot. Adding more buffers will increase memory use by a significant amount, so I have to be cautious.