documenting the command buffer
continuous-integration/drone/push Build is passing Details

main
cosmonaut 2021-01-31 19:34:15 -08:00
parent 771cf4f8aa
commit da1353be9e
1 changed files with 150 additions and 0 deletions

View File

@ -7,3 +7,153 @@ weight: 4
Now that we have our rendering state set up, we can talk about issuing commands.
There are three kinds of overarching operations that we perform using the command buffer: applying render state, uploading uniform data, and binding resources. Let's go through each of these and then tie it all together.
First, we need to acquire a command buffer.
```cs
var myCommandBuffer = GraphicsDevice.AcquireCommandBuffer();
```
Note that it is an **error** to use the same command buffer on multiple threads. If you want to do multithreaded draw calls, acquire one command buffer per thread.
## Beginning the render pass
All bindings and draw calls must be made within a render pass. You should think of a render pass as being a set of draw calls made to the same group of render targets.
Beginning a render pass requires a minimum of three parameters: a render pass object, a framebuffer object, a rectangle representing the area of the framebuffer that will be rendered to. You may also optionally provide clear colors per color target, and a depth/stencil clear value for the depth/stencil target.
There can only be one active render pass at a time.
```cs
myCommandBuffer.BeginRenderPass(
myRenderPass,
myFramebuffer,
myRenderArea,
Color.CornflowerBlue
);
```
This example assumes there is one color target in the framebuffer, and we will clear it to the cornflower blue color.
You can also clear the framebuffer by calling `CommandBuffer.Clear`. It's strongly recommended to clear when beginning a render pass whenever possible, but it is possible to clear mid-pass if you wish.
### Binding the graphics pipeline
When we wish to use a graphics pipeline, we "bind" it, meaning that we make it active. We can only have one bound graphics pipeline at a time per command buffer.
```cs
myCommandBuffer.BindGraphicsPipeline(myGraphicsPipeline);
```
Note that if you need to use multiple graphics pipelines that all share a render pass, you can call this method again and it will bind a new graphics pipeline without needing to end the render pass.
## Pushing shader uniforms
Remember when we talked about shader uniforms earlier? This is where we apply them.
```cs
var myVertexUniformOffset = myCommandBuffer.PushVertexShaderParams(myVertexShaderUniforms);
var myFragmentUniformOffset = myCommandBuffer.PushFragmentShaderParams(myFragmentShaderUniforms);
```
Note that these offset values are later accepted by the draw calls, and also note that you can pass multiple shader uniform structs at once. Remember that sending data between the CPU and GPU is expensive and we want to do that as infrequently as possible. Batching your uniform updates minimizes data transfer.
## Binding Buffers
```cs
myCommandBuffer.BindVertexBuffers(myVertexBuffer);
myCommandBuffer.BindIndexBuffer(myIndexBuffer);
```
"Binding" a buffer makes it available to be used by draw calls. If you are doing instanced rendering, you will bind multiple vertex buffers, otherwise you will just bind one. You will need to bind an index buffer if you are doing indexed or instanced rendering, otherwise you will not bind one.
You may also bind buffers while providing an offset value.
```cs
myCommandBuffer.BindVertexBuffers(new BufferBinding(myVertexBuffer, myOffsetValue));
```
## Binding Samplers
"Binding" a sampler makes it available to be used by the shaders in the currently bound graphics pipeline. In order to bind a sampler, you must provide a sampler object and a texture object.
```cs
myCommandBuffer.BindVertexSamplers(
new TextureSamplerBinding(myVertexTexture, myVertexSampler)
);
myCommandBuffer.BindFragmentSamplers(
new TextureSamplerBinding(myFragmentSamplerOne, myFragmentSamplerTwo)
);
```
Note that you may provide one or more samplers to a shader, and the samplers you provide will be indexed by the shader in the order they are provided to the function.
## Drawing Primitives
Now that we've set up all this render state, we can start issuing draw calls.
To draw using a single vertex buffer and no index buffer, call `DrawPrimitives`. You must provide a starting vertex, a primitive count, and offset values for each shader stage. (If you do not provide uniforms for a particular stage, just use `0`.)
```cs
myCommandBuffer.DrawPrimitives(0, myTriangleCount, myVertexUniformOffset, myFragmentUniformOffset);
```
Note that the existence of the uniform offset pattern enables you to loop over a bunch of draw calls and update the uniforms without having to push uniforms every loop. Remember that you can greatly increase your performance by packing as much data into a single vertex buffer as possible.
To draw using a single vertex buffer and an index buffer, call `DrawIndexedPrimitives`. You must provide a starting vertex, a starting index, a primitive count, and offset values for each shader stage.
```cs
myCommandBuffer.DrawIndexedPrimitives(0, 0, myTriangleCount, myVertexUniformOffset, myFragmentUniformOffset);
```
To draw using multiple vertex buffers and an index buffer, call `DrawInstancedPrimitives`. You must provide a starting vertex, a starting index, a primitive count, an instance count, and offset values for each shader stage.
```cs
myCommandBuffer.DrawInstancedPrimitives(0, 0, myTriangleCount, myInstanceCount, myVertexUniformOffset, myFragmentUniformOffset);
```
## Ending the Render Pass
When you need to change framebuffers or present to the screen, it's time to end the render pass.
```cs
myCommandBuffer.EndRenderPass();
```
## Presenting the Frame
"Presenting" is when we provide a final image that we want to show in our game window.
To present, we must provide a texture or texture slice, a filter, and an optional destination rectangle. If a destination rectangle is not provided the resulting image will be automatically scaled to the size of the game window.
```cs
myCommandBuffer.QueuePresent(myColorTarget, Filter.Nearest);
```
Note that if you use multiple command buffers, you still only want to call `QueuePresent` exactly once per frame.
## Copying Data
Sometimes you might want to copy the contents of a texture to another texture.
```cs
myCommandBuffer.CopyTextureToTexture(
new TextureSlice(myTexture, rectOne),
new TextureSlice(myOtherTexture, rectTwo)
Filter.Linear
);
```
Sometimes you might want to read the contents of an image.
```cs
myCommandBuffer.CopyTextureToBuffer(
new TextureSlice(myTexture, myRectangle),
myBuffer
)
```
Note that it is an error to provide too small of a buffer to contain the image data.
Remember that you have to call `GraphicsDevice.Submit` and `GraphicsDevice.Wait` before the contents of the buffer are guaranteed to be filled. Then you can call `myBuffer.GetData` to get the image data.