-
Notifications
You must be signed in to change notification settings - Fork 409
PrimitiveBatch
This is a helper for easily and efficiently drawing dynamically generated geometry using Direct3D 12 such as lines or trianges. It fills the same role as the legacy Direct3D 9 APIs DrawPrimitiveUP
and DrawIndexedPrimitiveUP
. Dynamic submission is a highly effective pattern for drawing procedural geometry, and convenient for debug rendering, but is not nearly as efficient as static buffers which is more suited to traditional meshes where the VBs and IBs do not change every frame. Excessive dynamic submission is a common source of performance problems in apps. Therefore, you should prefer to use Model, GeometricPrimitive, or your own VB/IB over PrimitiveBatch unless you really need the flexibility to regenerate the topology every frame.
PrimitiveBatch manages the vertex and index buffers for you. It automatically merges adjacent draw requests, so if you call DrawLine 100 times in a row, only a single GPU draw call will be generated.
PrimitiveBatch is responsible for setting the vertex buffer, index buffer, and primitive topology, then issuing the final draw call. Unlike the higher level SpriteBatch helper, it does not provide the Pipeline State Object (PSO). PrimitiveBatch is often used in conjunction with BasicEffect and the structures from VertexTypes, but it can work with any other shader or vertex formats of your own.
Related tutorial: Simple rendering
#include <PrimitiveBatch.h>
Initialize a PrimitiveBatch for drawing VertexPositionColor
data
std::unique_ptr<PrimitiveBatch<VertexPositionColor>> primitiveBatch;
primitiveBatch = std::make_unique<PrimitiveBatch<VertexPositionColor>>(device);
For exception safety, it is recommended you make use of the C++ RAII pattern and use a std::unique_ptr
or std::shared_ptr
The default values assume that your maximum batch size is 4096 vertices arranged in triangles. If you want to use larger batches, you need to provide the additional constructor parameter.
PrimitiveBatch<T>( ID3D12Device* device,
size_t maxIndices = DefaultBatchSize * 3,
size_t maxVertices = DefaultBatchSize)
Setting up a suitable BasicEffect for the given input layout:
std::unique_ptr<BasicEffect> lineEffect;
RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(),
m_deviceResources->GetDepthBufferFormat());
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE);
lineEffect= std::make_unique<BasicEffect>(device,
EffectFlags::VertexColor,
pd);
lineEffect->SetProjection(XMMatrixOrthographicOffCenterRH(0,
screenWidth, screenHeight, 0, 0, 1));
lineEffect->Apply(commandList);
primitiveBatch->Begin(commandList);
primitiveBatch->DrawLine(VertexPositionColor(...), VertexPositionColor(...));
primitiveBatch->End();
PrimitiveBatch provides five drawing methods:
- DrawLine(v1, 2): Draws a single-pixel line between two vertices
- DrawTriangle(v1, v2, v3): Draws a triangle between three vertices
- DrawQuad(v1, v2, v3, v4): draws a quad from four corner vertices (submitted as two triangles)
- Draw(topology, vertices, vertexCount): Draws an array of vertices with the given topology
- DrawIndexed(topology, indices, indexCount, vertices, vertexCount): Draws an indexed array of vertices with a given topology.
Note that if you are drawing using a textured effect, you would also need to call
SetDescriptorHeaps
before applying the effect and drawing.
Because of the way that Direct3D 12 Pipeline State Objects (PSOs) work, you must submit all the same 'topology type' items together that match the active PSO, end the batch, set a new PSO with a different topology type, and then start new batch.
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT);
This effect works with Draw
/DrawIndexed
using D3D_PRIMITIVE_TOPOLOGY_POINTLIST
.
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE);
This effect works with DrawLine
and Draw
/DrawIndexed
using D3D_PRIMITIVE_TOPOLOGY_LINELIST
, D3D_PRIMITIVE_TOPOLOGY_LINESTRIP
.
The default for EffectPipelineStateDescription
is to use D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE
:
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState);
This effect works with DrawTriangle
, DrawQuad
, and Draw
/DrawIndexed
using D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST
, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ
, D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
, or D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ
.
Direct3D does not have native support for quads, so they are drawing as two triangles.
For best performance, draw as much as possible inside the fewest separate Begin/End blocks. This will reduce overhead and maximize potential for batching.
Ideally submit draws of the same topology to avoid flushing, and preferably use D3D_PRIMITIVE_TOPOLOGY_POINTLIST
, D3D_PRIMITIVE_TOPOLOGY_LINELIST
, or D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST
.
The PrimitiveBatch constructor allows you to specify what size index and vertex buffers to allocate. You may want to tweak these values to fit your workload, or if you only intend to draw non-indexed geometry, specify maxIndices = 0 to entirely skip creating the index buffer.
Until End
is called on PrimitiveBatch, the various Draw
statements are likely still buffered. They are always drawn in the order of the individual Draw
statements, but if mixing PrimitiveBatch drawing with other drawing, you need to call End
before they will all be submitted to Direct3D.
Primitive batch is best used for dynamic submission, which is commonly needed for things like drawing debug shapes. See DebugDraw.
Each PrimitiveBatch instance only supports drawing from one thread at a time, but you can simultaneously submit primitives on multiple threads if you create a separate PrimitiveBatch instance per command-list.
PrimitiveBatch sets the Primitive Topology, Vertex Buffer in slot 0, and Index Buffer to use. See above for related restrictions based on the Pipeline State Object (PSO).
The PrimitiveBatch class assumes you've already set the Render Target view, Depth Stencil view, Viewport, ScissorRects, Descriptor Heaps (for textures and samplers), root signature, and PSO to the command-list provided to Begin
.
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Xbox One
- Xbox Series X|S
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20