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:
- Topology Deconstruction: Primitives emitted by the
GS are written out as sequential vertex lists. For instance, a
TriangleStreamemits 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. - 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.
- 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.