Skip to main content

Module voxel

Module voxel 

Source
Expand description

GPU-accelerated 3D rendering

§Theory

This module implements an algorithm similar to the one described in Massively Parallel Rendering of Complex Closed-Form Implicit Surfaces (Keeter ’20). The rest of this section is intended for people who have read that paper (“MPR” for short).

We use interval arithmetic on a high-fanout hierarchy of tiles (64³, 16³, 4³), followed by voxel and normal evaluation. At each stage of interval arithmetic, we compute a simplified tape for each tile containing only portions of the expression which are active.

After the root tile evaluation, tiles are sparse. Tiles and tapes use an atomic bump allocator to claim portions of a fixed buffer. The tile buffer is always sized to fit all possible tiles; the tape buffer can run out of space, in which case we fall back to the previous (unsimplified) tape.

§Changes versus MPR

There are a few notable changes compared to the MPR paper and reference implementation.

First, modern GPU APIs support indirect dispatch based on buffers on the GPU itself. This saves a round-trip: the 64³ shader can compute a dispatch size for the 16³ shader and store it in a buffer (and so on for subsequent stages).

In a more significant change, evaluation is broken into strata:

  • The initial pass of 64³ tiles renders all of those tiles, any which are active are accumulated into a set of depth / 64 strata
  • Strata are evaluated one at a time in z-sorted order; this is where 16³, 4³, voxel, and normal evaluation happens. You can think of this as doing raymarching on 64³ voxels at a time.

Strata-sorted evaluation has a few advantages:

  • We can statically allocate enough space for all tiles: all 64³ in the image, then all 16³ and 4³ tiles in a single strata. It would be prohibitive to allocate storage for all 4³ tiles in the entire volume, but doing per-strata evaluation reduces the memory scaling from N³ to N².
  • We get some amount of Z culling, because each pass can bail out if the result in the heightmap fully covers the tile

§Practice

There are four core objects, each with different lifetimes

  • Context contains all of the pipelines used for 3D rendering. It is very expensive to build and should be constructed once per thread / worker.
  • RenderShape contains serialized bytecode to render a particular shape. Best practice is to rebuild it only when a shape changes (i.e. not once per frame), although in practice it’s pretty fast to construct.
  • Workspace contains GPU buffers needed for rendering at a particular image size. It is primarily expensive in GPU memory, as it contains several full-frame buffers. Best practice is to construct one Workspace object per worker context (or per simultaneous render); it will be automatically resized when used. Systems with high variability in image size may want to periodically compare size versus capacity and fully reallocate buffers (by constructing a new Workspace object) if they get too out of whack.
  • RenderConfig sets the transform matrix for rendering. This is cheap to construct and could be built once per frame

With all that out of the way, usage is pretty simple:

§Sync and async operation

GPU operations are asynchronous; operations are submitted to a queue, and are completed at some point in the future. Context::run blocks until operations are complete, but is only valid on the desktop (and is disabled by feature flag on the web); it uses wgpu::Device::poll, which is a no-op on the web. Context::run_async is the async equivalent, and can be used either natively or on the web.

§Low-level building blocks

Context::run and run_async do four things:

  • Run the GPU kernels to produce an output image, which is a GeometryPixel array in a GPU storage buffer
  • Copy from that GPU storage buffer to a mappable buffer (for read-back)
  • Map that buffer into a MappedImage
  • Read image data back to the CPU

Lower-level building blocks are also available: Context::submit submits the render operations to the GPU, and Workspace::output returns the output buffer.

Modules§

effects
Voxel post-processing effects

Structs§

BufferError
Error returned when resizing buffers in a Workspace object
Context
Context for 3D (combined heightmap and normal) rendering
GeomBufferTag
Tag for a on-GPU buffer storing GeometryPixel values
RenderConfig
Settings for 3D rendering
RootTileBuffersError
Error type when resizing root tile buffers
TileBuffersError
Error type when resizing intermediate tile buffers
Workspace
Workspace for rendering

Enums§

BufferName
Names of all buffers, used for error reporting
RootTileBufferName
Names of buffers used by the root tile rendering pass (for error reporting)
SubmitError
Error returned when submitting a voxel rasterization job to the GPU
TileBufferName
Names of buffers used by the intermediate tile rendering pass

Type Aliases§

RenderSize
Size type for 3D rendering