-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
Copy pathSpriteBatcher.cs
288 lines (254 loc) · 11.2 KB
/
SpriteBatcher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// MonoGame - Copyright (C) MonoGame Foundation, Inc
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections.Generic;
namespace Microsoft.Xna.Framework.Graphics
{
/// <summary>
/// This class handles the queueing of batch items into the GPU by creating the triangle tesselations
/// that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be
/// batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices
/// sent to the GPU).
/// </summary>
internal class SpriteBatcher
{
/*
* Note that this class is fundamental to high performance for SpriteBatch games. Please exercise
* caution when making changes to this class.
*/
/// <summary>
/// Initialization size for the batch item list and queue.
/// </summary>
private const int InitialBatchSize = 256;
/// <summary>
/// The maximum number of batch items that can be processed per iteration
/// </summary>
private const int MaxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad
/// <summary>
/// Initialization size for the vertex array, in batch units.
/// </summary>
private const int InitialVertexArraySize = 256;
/// <summary>
/// The list of batch items to process.
/// </summary>
private SpriteBatchItem[] _batchItemList;
/// <summary>
/// Index pointer to the next available SpriteBatchItem in _batchItemList.
/// </summary>
private int _batchItemCount;
/// <summary>
/// The target graphics device.
/// </summary>
private readonly GraphicsDevice _device;
/// <summary>
/// Vertex index array. The values in this array never change.
/// </summary>
private short[] _index;
private VertexPositionColorTexture[] _vertexArray;
public SpriteBatcher(GraphicsDevice device, int capacity = 0)
{
_device = device;
if (capacity <= 0)
capacity = InitialBatchSize;
else
capacity = (capacity + 63) & (~63); // ensure chunks of 64.
_batchItemList = new SpriteBatchItem[capacity];
_batchItemCount = 0;
for (int i = 0; i < capacity; i++)
_batchItemList[i] = new SpriteBatchItem();
EnsureArrayCapacity(capacity);
}
/// <summary>
/// Reuse a previously allocated SpriteBatchItem from the item pool.
/// if there is none available grow the pool and initialize new items.
/// </summary>
/// <returns></returns>
public SpriteBatchItem CreateBatchItem()
{
if (_batchItemCount >= _batchItemList.Length)
{
var oldSize = _batchItemList.Length;
var newSize = oldSize + oldSize/2; // grow by x1.5
newSize = (newSize + 63) & (~63); // grow in chunks of 64.
Array.Resize(ref _batchItemList, newSize);
for(int i=oldSize; i<newSize; i++)
_batchItemList[i]=new SpriteBatchItem();
EnsureArrayCapacity(Math.Min(newSize, MaxBatchSize));
}
var item = _batchItemList[_batchItemCount++];
return item;
}
/// <summary>
/// Resize and recreate the missing indices for the index and vertex position color buffers.
/// </summary>
/// <param name="numBatchItems"></param>
private unsafe void EnsureArrayCapacity(int numBatchItems)
{
int neededCapacity = 6 * numBatchItems;
if (_index != null && neededCapacity <= _index.Length)
{
// Short circuit out of here because we have enough capacity.
return;
}
short[] newIndex = new short[6 * numBatchItems];
int start = 0;
if (_index != null)
{
_index.CopyTo(newIndex, 0);
start = _index.Length / 6;
}
fixed (short* indexFixedPtr = newIndex)
{
var indexPtr = indexFixedPtr + (start * 6);
for (var i = start; i < numBatchItems; i++, indexPtr += 6)
{
/*
* TL TR
* 0----1 0,1,2,3 = index offsets for vertex indices
* | /| TL,TR,BL,BR are vertex references in SpriteBatchItem.
* | / |
* | / |
* |/ |
* 2----3
* BL BR
*/
// Triangle 1
*(indexPtr + 0) = (short)(i * 4);
*(indexPtr + 1) = (short)(i * 4 + 1);
*(indexPtr + 2) = (short)(i * 4 + 2);
// Triangle 2
*(indexPtr + 3) = (short)(i * 4 + 1);
*(indexPtr + 4) = (short)(i * 4 + 3);
*(indexPtr + 5) = (short)(i * 4 + 2);
}
}
_index = newIndex;
_vertexArray = new VertexPositionColorTexture[4 * numBatchItems];
}
/// <summary>
/// Sorts the batch items and then groups batch drawing into maximal allowed batch sets that do not
/// overflow the 16 bit array indices for vertices.
/// </summary>
/// <param name="sortMode">The type of depth sorting desired for the rendering.</param>
/// <param name="effect">The custom effect to apply to the drawn geometry</param>
public unsafe void DrawBatch(SpriteSortMode sortMode, Effect effect)
{
if (effect != null && effect.IsDisposed)
throw new ObjectDisposedException("effect");
// nothing to do
if (_batchItemCount == 0)
return;
// sort the batch items
switch ( sortMode )
{
case SpriteSortMode.Texture :
case SpriteSortMode.FrontToBack :
case SpriteSortMode.BackToFront :
Array.Sort(_batchItemList, 0, _batchItemCount);
break;
}
// Determine how many iterations through the drawing code we need to make
int batchIndex = 0;
int batchCount = _batchItemCount;
unchecked
{
_device._graphicsMetrics._spriteCount += batchCount;
}
// Iterate through the batches, doing short.MaxValue sets of vertices only.
while(batchCount > 0)
{
// setup the vertexArray array
var startIndex = 0;
var index = 0;
Texture2D tex = null;
int numBatchesToProcess = batchCount;
if (numBatchesToProcess > MaxBatchSize)
{
numBatchesToProcess = MaxBatchSize;
}
// Avoid the array checking overhead by using pointer indexing!
fixed (VertexPositionColorTexture* vertexArrayFixedPtr = _vertexArray)
{
var vertexArrayPtr = vertexArrayFixedPtr;
// Draw the batches
for (int i = 0; i < numBatchesToProcess; i++, batchIndex++, index += 4, vertexArrayPtr += 4)
{
SpriteBatchItem item = _batchItemList[batchIndex];
// if the texture changed, we need to flush and bind the new texture
var shouldFlush = !ReferenceEquals(item.Texture, tex);
if (shouldFlush)
{
FlushVertexArray(startIndex, index, effect, tex);
tex = item.Texture;
startIndex = index = 0;
vertexArrayPtr = vertexArrayFixedPtr;
_device.Textures[0] = tex;
}
// store the SpriteBatchItem data in our vertexArray
*(vertexArrayPtr+0) = item.vertexTL;
*(vertexArrayPtr+1) = item.vertexTR;
*(vertexArrayPtr+2) = item.vertexBL;
*(vertexArrayPtr+3) = item.vertexBR;
// Release the texture.
item.Texture = null;
}
}
// flush the remaining vertexArray data
FlushVertexArray(startIndex, index, effect, tex);
// Update our batch count to continue the process of culling down
// large batches
batchCount -= numBatchesToProcess;
}
// return items to the pool.
_batchItemCount = 0;
}
/// <summary>
/// Sends the triangle list to the graphics device. Here is where the actual drawing starts.
/// </summary>
/// <param name="start">Start index of vertices to draw. Not used except to compute the count of vertices to draw.</param>
/// <param name="end">End index of vertices to draw. Not used except to compute the count of vertices to draw.</param>
/// <param name="effect">The custom effect to apply to the geometry</param>
/// <param name="texture">The texture to draw.</param>
private void FlushVertexArray(int start, int end, Effect effect, Texture texture)
{
if (start == end)
return;
var vertexCount = end - start;
// If the effect is not null, then apply each pass and render the geometry
if (effect != null)
{
var passes = effect.CurrentTechnique.Passes;
foreach (var pass in passes)
{
pass.Apply();
// Whatever happens in pass.Apply, make sure the texture being drawn
// ends up in Textures[0].
_device.Textures[0] = texture;
_device.DrawUserIndexedPrimitives(
PrimitiveType.TriangleList,
_vertexArray,
0,
vertexCount,
_index,
0,
(vertexCount / 4) * 2,
VertexPositionColorTexture.VertexDeclaration);
}
}
else
{
// If no custom effect is defined, then simply render.
_device.DrawUserIndexedPrimitives(
PrimitiveType.TriangleList,
_vertexArray,
0,
vertexCount,
_index,
0,
(vertexCount / 4) * 2,
VertexPositionColorTexture.VertexDeclaration);
}
}
}
}