The Multi-draw calls combine the functionality of many draw calls into one. By grouping many draw calls into one, the CPU is made available for a wider variety of game logic than simply setting state and making draw calls. To effectively use the multi-draw calls, consider grouping the calls around materials rather than objects - perhaps having one multi-draw call per material. This approach should enable slightly simpler architecture for game engines. GPU performance is not changed with the use of multi-draw calls; the CPU is released from mundane repetition, and material-based architecture is enabled.
Calling ID3D11DeviceContextX::MultiDrawIndexedInstancedIndirect draws multiple indexed, instanced, GPU-generated primitives. Calling MultiDrawIndexedInstancedIndirect is equivalent to calling ID3D11DeviceContext::DrawIndexedInstancedIndirect repeatedly, as follows:
for (UINT i = 0 ; i < PrimitiveCount ; i++)
{
UINT offsetBytes = AlignedByteOffsetForArgs + i * StrideByteOffsetForArgs;
pContext->DrawIndexedInstancedIndirect( pBufferForArgs, offsetBytes );
}
Also see ID3D11DeviceContextX::MultiDrawIndexedInstancedIndirectAuto, which draws multiple indexed, instanced, GPU-generated primitives using a GPU-generated count.
Calling ID3D11DeviceContextX::MultiDrawInstancedIndirect draws multiple non-indexed, instanced, GPU-generated primitives. Calling MultiDrawInstancedIndirect is equivalent to calling ID3D11DeviceContext::DrawInstancedIndirect repeatedly, as follows:
for (UINT i = 0 ; i < PrimitiveCount ; i++)
{
UINT offsetBytes = AlignedByteOffsetForArgs + i * StrideByteOffsetForArgs;
pContext->DrawInstancedIndirect( pBufferForArgs, offsetBytes );
}
Also refer to the ID3D11DeviceContextX::MultiDrawInstancedIndirectAuto call which draws multiple instanced, GPU-generated primitives using a GPU-generated count.
There are some specific synchronization requirements that apply to the following API sets:
And also to:
Additional synchronization may be necessary if the pBufferForArgs buffer is generated on the GPU to be consumed by one of the six listed APIs, or for the three Indexed variants of the APIs if the index buffer is filled on the GPU. This is typically the case for how these APIs are used.
The GPU draw or dispatch that populates pBufferForArgs and index buffers are not coherent with the memory read used by the DrawIndirect family of APIs. Thus it is the title’s responsibility to ensure the generating draw or dispatch is finished fully and that the Texture L2 cache write-back is fully flushed before the draw indirect function reads the data.
To correctly use these APIs, the L2 GPU cache must be flushed after all the data has been fully written. Depending on which shader stage writes the data, this will take one of various forms outlined below.
Insert the following code in between the Dispatch that writes the data and the Draw Indirect that reads the data:
pGraphicsContext->Dispatch(...); // Generate pBufferForArgs
pGraphicsContext->FlushGpuCacheRange(D3D11_FLUSH_DEFAULT_PFP, 0, 0);
If you use the flags D3D11_CREATE_DEVICE_IMMEDIATE_CONTEXT_FAST_SEMANTICS or D3D11_CREATE_DEFERRED_CONTEXT_FAST_SEMANTICS, or you call CSEnableAutomaticGpuFlush(FALSE); you will need to insert a Compute Shader Partial Flush to ensure that all of the outstanding compute shader work in finished before flushing the L2 cache. Insert the following in between the Dispatch that writes the data and the Draw Indirect that reads the data:
pGraphicsContext->Dispatch(...); // Generate pBufferForArgs
pGraphicsContext ->GpuSendPipelinedEvent(D3D11X_GPU_PIPELINED_EVENT_CS_PARTIAL_FLUSH);
pGraphicsContext ->FlushGpuCacheRange(D3D11_FLUSH_DEFAULT_PFP, 0, 0);
You will need to issue a bottom of pipe L2 cache flush after the data is written. Because this type of flush occurs at the bottom of the pipe, all outstanding compute shader work is guaranteed to be finished. On the Async Compute Context, there is no such thing as a PFP flush, so in this case use D3D11_FLUSH_DEFAULT. Insert the follow after the async compute Dispatch that writes the data:
pAsyncComputeContext->Dispatch(...); // Generate pBufferForArgs
pAsyncComputeContext->FlushGpuCachesBottomOfPipe(D3D11_FLUSH_DEFAULT);
You will need to insert a Vertex Shader or Pixel Shader Partial Flush, depending on which shader stage writes the data, and then flush the L2 cache. If the data is written by a Vertex, Geometry, Hull or Domain Shader, then use a VS_PARTIAL_FLUSH followed by the L2 cache flush. If the data is written by a Pixel Shader, then use a PS_PARTIAL_FLUSH followed by the L2 cache flush. Insert the following code in between the Draw that writes the data and the Draw Indirect that reads the data.
If pBufferForArgs is written by the Vertex, Geometry, Hull or Domain Shader, insert the following lines:
pGraphicsContext->Draw(...); // Generate pBufferForArgs
pGraphicsContext->GpuSendPipelinedEvent(D3D11X_GPU_PIPELINED_EVENT_VS_PARTIAL_FLUSH);
pGraphicsContext->FlushGpuCacheRange(D3D11_FLUSH_DEFAULT_PFP, 0, 0);
If pBufferForArgs is written by the Pixel Shader, insert the following lines:
pGraphicsContext->Draw(...); // Generate pBufferForArgs
pGraphicsContext->GpuSendPipelinedEvent(D3D11X_GPU_PIPELINED_EVENT_PS_PARTIAL_FLUSH);
pGraphicsContext->FlushGpuCacheRange(D3D11_FLUSH_DEFAULT_PFP, 0, 0);