DirectX 10 Stream Output with Geometry Shaders

DirectX 10 introduced the Stream Output (SO) stage to allow the GPU to write primitive data directly to memory buffers before or instead of sending it to the rasterizer. By pairing this stage with the Geometry Shader, developers can dynamically generate, amplify, or modify geometry and store the results in GPU-accessible vertex buffers. This process enables advanced techniques such as multi-pass rendering, GPU-driven particle simulations, and dynamic level-of-detail without requiring costly round-trips back to the CPU.

The Role of the Geometry Shader

The Geometry Shader (GS) sits between the Vertex Shader and the Stream Output stage. Unlike the Vertex Shader, which operates on single vertices in isolation, the Geometry Shader processes complete primitives—such as points, lines, or triangles—along with optional adjacency information.

During execution, the Geometry Shader can modify incoming vertices, destroy primitives, or amplify geometry by emitting entirely new primitives. The output of the Geometry Shader is delivered via predefined HLSL output stream objects, such as PointStream<T>, LineStream<T>, or TriangleStream<T>.

Configuring the Stream Output Stage

To route geometry data from the shader to memory, the DirectX 10 runtime requires a Geometry Shader created specifically with Stream Output support. This is accomplished using the ID3D10Device::CreateGeometryShaderWithStreamOutput method.

When configuring this stage, an application defines an array of D3D10_SO_DECLARATION_ENTRY structures. This declaration specifies:

  • Which output parameters from the GS should be captured (such as position, normal, or color).
  • Which output buffer slot will receive the data.
  • The component offset and component count for each captured attribute.

DirectX 10 allows data to be streamed to up to four target buffers simultaneously, provided each buffer receives a single, contiguous stride of vertex attributes.

Writing Primitives to Memory Buffers

Once configured, vertex buffers created with the D3D10_BIND_STREAM_OUTPUT flag are bound to the pipeline using ID3D10Device::SOSetTargets. As the Geometry Shader emits primitives through its stream objects, the Stream Output stage processes the output according to standard topology rules:

  1. Topology Deconstruction: Primitives emitted by the GS are written out as sequential vertex lists. For instance, a TriangleStream emits lists of three vertices per triangle. If a triangle strip is output, the SO stage automatically converts it into an explicit, flattened triangle list in the destination buffer.
  2. Buffer Striding: The SO stage tracks internal write offsets within the bound buffers. As vertices are written, the destination pointer advances by the stride specified during shader creation.
  3. Truncation and Discarding: If an output buffer runs out of allocated space while a primitive is being processed, the incomplete primitive is discarded, preventing memory corruption or half-written geometry.

Bypassing Rasterization and Re-feeding Data

Stream Output can function concurrently with rasterization, or it can bypass it entirely. If an application only needs to perform geometry generation or simulation without rendering to the screen, it can disable the rasterization stage by binding a null pixel shader and detaching all render targets.

After data is written to a stream-output buffer, the resulting buffer can be rebound as an input vertex buffer via ID3D10Device::IASetVertexBuffers. Because the exact number of primitives generated by the Geometry Shader is dynamic and maintained exclusively on the GPU, DirectX 10 provides the ID3D10Device::DrawAuto call. This method reads the internally tracked primitive count recorded by the Stream Output stage, eliminating the need for the CPU to query the GPU for the vertex count before executing subsequent render passes.