Using the SpriteRenderer
The SpriteRenderer is another tier above the Direct3D device and its ImmediateContext. It is specialized for rendering sprites, i.e. 2D images painted on the screen. Usually, an application needs only one SpriteRenderer that is responsible for all sprites.
Because the SpriteRenderer utilizes a Direct3D device for rendering, this device has to be passed to the constructor. Furthermore, a custom BufferSize can be passed to the constructor. The tasks of this buffer will be explained in the next section.
Initializing a SpriteRendererInstance for further usage is pretty easy. Just add the following code to your DirectX initialization code. Always use the subclass of
SpriteTextRenderer.SpriteRenderer that is appropriate for your DirectX library. In the following examples, we will use SlimDX.
public SpriteTextRenderer.SlimDX.SpriteRenderer Sprite;
Sprite = new SpriteTextRenderer.SlimDX.SpriteRenderer(D3DDevice);
The source of the sprite is a ShaderResourceView of a 2D texture that is passed to the Draw() method.
The ShaderResourceView of an image in a file can be created like this:
var SRV = new ShaderResourceView(D3DDevice, Texture2D.FromFile(D3DDevice, "image.jpg"))
ShaderResourceView can be passed to one of the SpriteRenderer’s Draw() methods. There are several overloads of the Draw() method that support different features. In this tutorial, we will use the most basic overload, which takes the
ShaderResourceView of the texture, the sprites position and size and the coordinate type. Further information about the different coordinate types can be found in the class documentation. Let’s draw a simple red circle:
Sprite.Draw(redCircleSRV, new Vector2(10, 10), new Vector2(50, 50), CoordinateType.Absolute);
However, this will not issue a draw call on the
D3DDevice. Instead, the sprite is added to the internal buffer, whose size can be set through the SpriteRenderer’s constructor.
Sprites are not necessarily drawn in the order they are created. If the
AllowReorder property is set to true, the
SpriteRenderer can rearrange the sprites for better performance. It will create groups of the same texture, which then can be drawn with a single draw call on the
Let’s draw another texture:
Sprite.Draw(blueCircleSRV, new Vector2(30, 10), new Vector2(50, 50), CoordinateType.Absolute);
There is not happening anything interesting here. As expected, the new sprite is added at the end of the buffer. Now we draw another red circle:
Sprite.Draw(redCircleSRV, new Vector2(50, 10), new Vector2(50, 50), CoordinateType.Absolute);
You can see that the new red circle is added right after the one in the buffer. This allows the SpriteRenderer to group them together, which results in a higher performance. However, because of this, the z-order of sprites is not preserved. This may or may
not be a problem. If you want to preserve the z-order, you can do this globally by disabeling AllowReorder. A less aggressive method is to do it locally with the ClearReorderBuffer() method. Let’s try that out:
Sprite.Draw(redCircleSRV, new Vector2(30, 30), new Vector2(50, 50), CoordinateType.Absolute);
The call of ClearReorderBuffer() tells the SpriteRenderer that no sprites should be grouped together with sprites that have been drawn before the call. Therefore, the new circle is added to the end of the buffer.
Actually drawing the sprites
Now, in the example the buffer is full. When this happens, the SpriteRenderer automatically calls its Flush() method, which will clear the buffer and issue draw calls for all groups. At the end of a frame, Flush() should be called, because there could still
be sprites in the buffer, which then would be drawn in the next frame.
As a first step, the SpriteRenderer will set a new DepthStencilState on the OutputMerger, because sprites must be drawn without any depth testing. The new state will have DepthEnable=false,DepthWriteMask=None and disabled Stencil. Furthermore, a new BlendState
with default Alpha-Blending is set. If you want to handle state management on your own, use the
HandleBlendState property. Then a vertex buffer is constructed from its complete internal buffer. After that, a draw call is issued for each texture group. At the end, the DepthStencilState is
reset to the active state before the Flush() call:
The result of the example is the following image: