A compute shader can generate terrain data on the GPU and still leave the streamed mesh path doing CPU work. The deciding question is not whether a project contains a compute shader. It is whether the chunk request actually reaches it, where the result goes next, and what the full trip costs.
I traced that route in Infinite Planet Tech, a private Unity prototype for procedural planetary terrain. The reviewed source has both a CPU mesh generator and a separate compute-shader component. In this snapshot, the streaming queue calls the CPU generator directly; I found no caller connecting that queue to the GPU component. This is a source review, not a Unity runtime trace or a performance test.
INFINITE PLANET TECH / TERRAIN STREAMING
Two generators. One connected path.The chunk queue that is connected
CPU mesh generation / used by the current streaming path
- 01 / LODObserver & quadtreeDistance selects which leaf needs detail.
- 02 / QUEUEChunkManagerProcesses up to the configured per-frame budget.
- 03 / CPUGenerateChunkMeshBuilds vertex, normal, UV and triangle arrays.
- 04 / MESHChunkMesh.InitializeCopies those arrays into a Unity Mesh.
The separate compute path
Compute shader / implemented, but no call from the chunk queue was found
- 01 / DISPATCHComputeShaderTwo kernels write chunk data into ComputeBuffers.
- 02 / READBACKComputeBuffer.GetDataCalled after one yielded frame; results return to CPU arrays.
- 03 / CALLBACKMeshDataThe coroutine invokes a callback with managed arrays.
Missing connection: the reviewed ChunkManager calls the CPU generator directly. The compute component is not part of that request-to-visible-chunk route.
First, make a chunk request reviewable
The planet controller creates six faces, one for each cube direction. Each face owns a quadtree. As the observer moves, a node compares its center distance with an LOD threshold: a leaf can subdivide for more detail, and a branch can merge again once it is far enough away. The merge threshold includes a 1.5 multiplier, giving the tree some separation between its split and merge decisions.
A leaf eventually requests a mesh. The request enters ChunkManager’s queue, which processes at most the configured number of chunks per frame; the settings asset defaults that budget to four. Pooling reuses ChunkMesh objects, but it does not make the mesh data itself free to generate.
The important call is in the queue consumer. ChunkManager.GenerateMeshForNode calls MeshGenerator.GenerateChunkMesh, then passes the returned vertex, normal, UV and triangle arrays to ChunkMesh.Initialize. The generator fills those arrays in nested loops and recalculates normals before returning. Because the queue is processed from Update, this particular route is synchronous CPU work on the main thread.
PlanetSettings also exposes a UseAsyncGeneration option, but the inspected ChunkManager does not read that property when it generates a chunk. A setting name is not evidence of an active scheduling strategy; the caller and the method it invokes define the current behavior.
Where the GPU path stops
GPUMeshGenerator contains a separate route. When compute is available and a shader is assigned, it creates ComputeBuffers, dispatches noise and mesh kernels, and retrieves vertices, normals, UVs and triangles. Its coroutine yields one frame before calling ComputeBuffer.GetData for those arrays. The class then builds a MeshData value and invokes a callback.
That callback boundary matters: the code shown here returns the data to managed CPU arrays. It does not leave the generated mesh resident on the GPU for later rendering, and I found no call site from the current ChunkManager queue. Even the method named GenerateChunkAsync falls back to synchronous CPU generation and invokes its callback immediately when the GPU path is unavailable.
One yielded frame is not the same thing as a non-blocking GPU readback. Unity documents that ComputeBuffer.GetData reads data back to an array and waits for submitted GPU work to finish; the API documentation recommends AsyncGPUReadback when that wait would block the main thread. The separate AsyncGPUReadback API reports results when they are available and trades that stall for additional latency. The reviewed coroutine does not call it.
This does not prove the compute code is slower. It identifies the work still needed before calling it an asynchronous streaming pipeline: connect the scheduler, handle the lifetime of buffers and requests, decide what happens when a leaf becomes obsolete while work is in flight, and measure both the readback and the later Mesh upload.
What a real comparison would measure
I would begin with identical terrain settings, seed, chunk resolution and camera route for both paths. Then I would record:
- time from a leaf requesting a chunk to that chunk becoming visible;
- main-thread time spent generating data and applying the Mesh;
- GPU dispatch time and readback time as separate stages;
- allocations and peak memory while several chunks are in flight;
- p50 and p95 results after a documented warm-up, on named hardware and graphics APIs.
I would also repeat the route with quick direction changes. A terrain streamer needs to discard or deprioritize chunks that the observer no longer needs, and that cancellation behavior changes the amount of useful GPU work. A microbenchmark that dispatches one isolated chunk would not answer that system-level question.
Until those measurements exist, I would not publish a speedup, frame-rate claim or compatibility promise. The source supports a narrower conclusion: CPU chunk generation is connected to the current queue; a compute-shader generator is present as a separate path; and its coroutine still reads data back into CPU arrays.
The current queue ends on the CPU
In the reviewed path, the LOD request enters a queue, MeshGenerator.GenerateChunkMesh builds CPU arrays synchronously, and ChunkMesh.Initialize uploads them. The separate compute component dispatches kernels but reads the result back to CPU arrays, and the queue does not call it. So the current queue is not using GPU mesh generation, and the compute coroutine is not evidence of non-blocking streaming.
The next implementation step is one chunk-generation interface used by the queue, followed by a controlled CPU-versus-compute comparison under the same camera path. Measure request-to-visible time, main-thread generation and upload, GPU dispatch, readback, and p95 frame time; if the renderer still needs CPU arrays, report transfer and upload costs rather than calling the path GPU-resident.
The private repository was reviewed at commit 8966ef78ca94ad5e8bdb72391e8b5855edca37c6. This article describes source-visible control flow only. I did not run the Unity project, validate a graphics API or device, or collect profiler data. The private source and its assets are not linked or reproduced.