public sealed class GrassDispatchThe host for GrassScatter.rvn: the cell records, the ring of device buffers, and the dispatch that fills them.
Remarks
[docs/plan/31 § T6]'s owed device half. GrassRenderer is the CPU reference — what a headless test and a machine with no compute queue use — and this is the dispatch that does the same arithmetic where a forest can afford it. Neither is the definition: Hash is, and both transliterate it.
⚠ One counter per cell, not one for the dispatch. A global head would serialise every invocation of every cell on one cache line, and it would make a cell's run depend on the order the workgroups happened to retire in — which is the one thing a scatter that has to be reproducible cannot afford.
⚠ The instance buffer is the ring, and its slots are assigned by the host. Each resident cell owns a fixed run, so an invocation never negotiates for space with another cell — only with the other candidates of its own. That is what makes the atomic local and the memory bounded.
⚠ The counts buffer is cleared before every dispatch and not after. A pass that cleared afterwards leaves the buffer holding last frame's numbers for anything that reads it in between — and what reads it is the indirect draw, which would then draw a cell's previous population out of its current bytes.
⚠ The candidate grid is clamped so a cell can never generate more candidates than its run holds, and that clamp is what makes the field the same field twice. The kernel's atomicAdd hands out places in the order the invocations happen to arrive, and the ones past the end return without writing — so on an oversubscribed cell the survivors are whichever blades the scheduler ran first, re-decided on every dispatch. The count came out right and the picture did not: a field drawn from a different subset of itself every frame, with the losers clustered wherever the workgroups happened to retire last. Thinning the grid before the dispatch makes membership a fact about the candidate again — the same thing Density already is — and leaves the atomic deciding only the order of a run that everybody fits into, which nothing can see.
Fields and properties (15)
public const int GroupSizeHow many candidates a workgroup covers on each axis. `[ComputeShader(8, 8, 1)]`.
public static int DrawCommandBytesHow many bytes one indirect command is — DrawIndexedIndirect's stride.
public int CapacityHow many cells one dispatch may cover.
public DrawCommand MeshWhat one blade is drawn with: the template the last Prepare baked.
public int BladesPerCellHow many blades a cell's run holds.
public int CandidateSideThe candidate grid the last Prepare scattered a cell over, per axis.
public int AuthoredSideWhat the type asked for, before the run's capacity thinned it.
public bool ThinnedWhether the last Prepare had to thin the type's grid to fit the run.
public int CellCountHow many cells the last Prepare wrote records for.
public long CeilingHow many blades the last dispatch could produce at most.
public (int X, int Y, int Z) GroupsHow many workgroups the last Prepare would dispatch, on each axis.
public int RefusedHow many cells were refused because the dispatch was full.
public BufferHandle InstancesThe buffer the draw reads its instances from.
public BufferHandle CountsAnd the one holding how many each cell produced.
public BufferHandle CommandsAnd the indirect arguments the draw reads.
Methods (8)
public GrassDispatch(IGraphicsDevice device, ShaderHandle shader, ShaderHandle arguments, int capacity = 256, int bladesPerCell = 4096)Creates the pass and its buffers.
public static int SideThatFits(int bladesPerCell)The widest square candidate grid whose every slot has a place in one cell's run.
public int Prepare(in GrassType type, FoliageCellGrid grid, IReadOnlyList<GrassSlot> resident, in GrassTerrainSource terrain, in DrawCommand mesh, float densityScale = 1)Writes the cell records and the constants for one grass type.
public int Record(ICommandList commands)Records the dispatch.
public int RecordDraws(ICommandList commandList)Issues one indirect draw per resident cell.
public long CommandOf(int index)Where a cell's indirect command is, for the draw that reads it.
public (long Offset, long Length) RunOf(int slot)Where a cell's blades are, for the draw that reads them.
public void Dispose()Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
Used by (9)
- GrassDispatchTestsVixen.Rendering.Terrain.Tests
- GrassDrawPassVixen.Rendering.Terrain
- GrassDrawPassTestsVixen.Rendering.Terrain.Tests
- GrassFieldVixen.Rendering.Terrain
- GrassVelocityPassVixen.Rendering.Terrain
- TerrainSceneRendererVixen.Rendering.Terrain
- GrassFieldVixen.Editor.App
- GrassLaneVixen.Editor.App
- VegetationPresenterVixen.Editor.App