/
MeshBatch.as
318 lines (280 loc) · 13.6 KB
/
MeshBatch.as
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.display
{
import flash.geom.Matrix;
import starling.rendering.IndexData;
import starling.rendering.MeshEffect;
import starling.rendering.Painter;
import starling.rendering.VertexData;
import starling.styles.MeshStyle;
import starling.utils.MatrixUtil;
import starling.utils.MeshSubset;
/** Combines a number of meshes to one display object and renders them efficiently.
*
* <p>The most basic tangible (non-container) display object in Starling is the Mesh.
* However, a mesh typically does not render itself; it just holds the data describing its
* geometry. Rendering is orchestrated by the "MeshBatch" class. As its name suggests, it
* acts as a batch for an arbitrary number of Mesh instances; add meshes to a batch and they
* are all rendered together, in one draw call.</p>
*
* <p>You can only batch meshes that share similar properties, e.g. they need to have the
* same texture and the same blend mode. The first object you add to a batch will decide
* this state; call <code>canAddMesh</code> to find out if a new mesh shares that state.
* To reset the current state, you can call <code>clear</code>; this will also remove all
* geometry that has been added thus far.</p>
*
* <p>Starling will use MeshBatch instances (or compatible objects) for all rendering.
* However, you can also instantiate MeshBatch instances yourself and add them to the display
* tree. That makes sense for an object containing a large number of meshes; that way, that
* object can be created once and then rendered very efficiently, without having to copy its
* vertices and indices between buffers and GPU memory.</p>
*
* @see Mesh
* @see Sprite
*/
public class MeshBatch extends Mesh
{
/** The maximum number of vertices that fit into one MeshBatch. */
public static const MAX_NUM_VERTICES:int = 65535;
private var _effect:MeshEffect;
private var _batchable:Boolean;
private var _vertexSyncRequired:Boolean;
private var _indexSyncRequired:Boolean;
// helper object
private static var sFullMeshSubset:MeshSubset = new MeshSubset();
/** Creates a new, empty MeshBatch instance. */
public function MeshBatch()
{
var vertexData:VertexData = new VertexData();
var indexData:IndexData = new IndexData();
super(vertexData, indexData);
}
/** @inheritDoc */
override public function dispose():void
{
if (_effect) _effect.dispose();
super.dispose();
}
/** This method must be called whenever the mesh's vertex data was changed. Makes
* sure that the vertex buffer is synchronized before rendering, and forces a redraw. */
override public function setVertexDataChanged():void
{
_vertexSyncRequired = true;
super.setVertexDataChanged();
}
/** This method must be called whenever the mesh's index data was changed. Makes
* sure that the index buffer is synchronized before rendering, and forces a redraw. */
override public function setIndexDataChanged():void
{
_indexSyncRequired = true;
super.setIndexDataChanged();
}
private function setVertexAndIndexDataChanged():void
{
_vertexSyncRequired = _indexSyncRequired = true;
}
private function syncVertexBuffer():void
{
_effect.uploadVertexData(_vertexData);
_vertexSyncRequired = false;
}
private function syncIndexBuffer():void
{
_effect.uploadIndexData(_indexData);
_indexSyncRequired = false;
}
/** Removes all geometry. */
public function clear():void
{
if (_parent) setRequiresRedraw();
_vertexData.numVertices = 0;
_indexData.numIndices = 0;
_vertexSyncRequired = true;
_indexSyncRequired = true;
}
/** Adds a mesh to the batch by appending its vertices and indices.
*
* <p>Note that the first time you add a mesh to the batch, the batch will duplicate its
* MeshStyle. All subsequently added meshes will then be converted to that same style.</p>
*
* @param mesh the mesh to add to the batch.
* @param matrix transform all vertex positions with a certain matrix. If this
* parameter is omitted, <code>mesh.transformationMatrix</code>
* will be used instead (except if the last parameter is enabled).
* @param alpha will be multiplied with each vertex' alpha value.
* @param subset the subset of the mesh you want to add, or <code>null</code> for
* the complete mesh.
* @param ignoreTransformations when enabled, the mesh's vertices will be added
* without transforming them in any way (no matter the value of the
* <code>matrix</code> parameter).
*/
public function addMesh(mesh:Mesh, matrix:Matrix=null, alpha:Number=1.0,
subset:MeshSubset=null, ignoreTransformations:Boolean=false):void
{
addMeshAt(mesh, -1, -1, matrix, alpha, subset, ignoreTransformations);
}
/** Adds a mesh to the batch by copying its vertices and indices to the given positions.
* Beware that you need to check for yourself if those positions make sense; for example,
* you need to make sure that they are aligned within the 3-indices groups making up
* the mesh's triangles.
*
* <p>It's easiest to only add objects with an identical setup, e.g. only quads.
* For the latter, indices are aligned in groups of 6 (one quad requires six indices),
* and the vertices in groups of 4 (one vertex for every corner).</p>
*
* <p>Note that the first time you add a mesh to the batch, the batch will duplicate its
* MeshStyle. All subsequently added meshes will then be converted to that same style.</p>
*
* @param mesh the mesh to add to the batch.
* @param indexID the position at which the mesh's indices should be added to the batch.
* If negative, they will be added at the very end.
* @param vertexID the position at which the mesh's vertices should be added to the batch.
* If negative, they will be added at the very end.
* @param matrix transform all vertex positions with a certain matrix. If this
* parameter is omitted, <code>mesh.transformationMatrix</code>
* will be used instead (except if the last parameter is enabled).
* @param alpha will be multiplied with each vertex' alpha value.
* @param subset the subset of the mesh you want to add, or <code>null</code> for
* the complete mesh.
* @param ignoreTransformations when enabled, the mesh's vertices will be added
* without transforming them in any way (no matter the value of the
* <code>matrix</code> parameter).
*/
public function addMeshAt(mesh:Mesh, indexID:int=-1, vertexID:int=-1,
matrix:Matrix=null, alpha:Number=1.0,
subset:MeshSubset=null, ignoreTransformations:Boolean=false):void
{
if (ignoreTransformations) matrix = null;
else if (matrix == null) matrix = mesh.transformationMatrix;
if (subset == null) subset = sFullMeshSubset;
var oldNumVertices:int = _vertexData.numVertices;
var targetVertexID:int = vertexID >= 0 ? vertexID : oldNumVertices;
var targetIndexID:int = indexID >= 0 ? indexID : _indexData.numIndices;
var meshStyle:MeshStyle = mesh._style;
if (oldNumVertices == 0)
setupFor(mesh);
meshStyle.batchVertexData(_style, targetVertexID, matrix, subset.vertexID, subset.numVertices);
meshStyle.batchIndexData(_style, targetIndexID, targetVertexID - subset.vertexID,
subset.indexID, subset.numIndices);
if (alpha != 1.0) _vertexData.scaleAlphas("color", alpha, targetVertexID, subset.numVertices);
if (_parent) setRequiresRedraw();
_indexSyncRequired = _vertexSyncRequired = true;
}
private function setupFor(mesh:Mesh):void
{
var meshStyle:MeshStyle = mesh._style;
var meshStyleType:Class = meshStyle.type;
if (_style.type != meshStyleType)
{
var newStyle:MeshStyle = new meshStyleType() as MeshStyle;
newStyle.copyFrom(meshStyle);
setStyle(newStyle, false);
}
else
{
_style.copyFrom(meshStyle);
}
}
/** Indicates if the given mesh instance fits to the current state of the batch.
* Will always return <code>true</code> for the first added mesh; later calls
* will check if the style matches and if the maximum number of vertices is not
* exceeded.
*
* @param mesh the mesh to add to the batch.
* @param numVertices if <code>-1</code>, <code>mesh.numVertices</code> will be used
*/
public function canAddMesh(mesh:Mesh, numVertices:int=-1):Boolean
{
var currentNumVertices:int = _vertexData.numVertices;
if (currentNumVertices == 0) return true;
if (numVertices < 0) numVertices = mesh.numVertices;
if (numVertices == 0) return true;
if (numVertices + currentNumVertices > MAX_NUM_VERTICES) return false;
return _style.canBatchWith(mesh._style);
}
/** If the <code>batchable</code> property is enabled, this method will add the batch
* to the painter's current batch. Otherwise, this will actually do the drawing. */
override public function render(painter:Painter):void
{
if (_vertexData.numVertices == 0) return;
if (_pixelSnapping) MatrixUtil.snapToPixels(
painter.state.modelviewMatrix, painter.pixelSize);
if (_batchable)
{
painter.batchMesh(this);
}
else
{
painter.finishMeshBatch();
painter.drawCount += 1;
painter.prepareToDraw();
painter.excludeFromCache(this);
if (_vertexSyncRequired) syncVertexBuffer();
if (_indexSyncRequired) syncIndexBuffer();
_style.updateEffect(_effect, painter.state);
_effect.render(0, _indexData.numTriangles);
}
}
/** @inheritDoc */
override public function setStyle(meshStyle:MeshStyle=null,
mergeWithPredecessor:Boolean=true):void
{
super.setStyle(meshStyle, mergeWithPredecessor);
if (_effect)
_effect.dispose();
_effect = style.createEffect();
_effect.onRestore = setVertexAndIndexDataChanged;
setVertexAndIndexDataChanged(); // we've got a new set of buffers!
}
/** The total number of vertices in the mesh. If you change this to a smaller value,
* the surplus will be deleted. Make sure that no indices reference those deleted
* vertices! */
public function set numVertices(value:int):void
{
if (_vertexData.numVertices != value)
{
_vertexData.numVertices = value;
_vertexSyncRequired = true;
setRequiresRedraw();
}
}
/** The total number of indices in the mesh. If you change this to a smaller value,
* the surplus will be deleted. Always make sure that the number of indices
* is a multiple of three! */
public function set numIndices(value:int):void
{
if (_indexData.numIndices != value)
{
_indexData.numIndices = value;
_indexSyncRequired = true;
setRequiresRedraw();
}
}
/** Indicates if this object will be added to the painter's batch on rendering,
* or if it will draw itself right away.
*
* <p>Only batchable meshes can profit from the render cache; but batching large meshes
* may take up a lot of CPU time. Activate this property only if the batch contains just
* a handful of vertices (say, 20 quads).</p>
*
* @default false
*/
public function get batchable():Boolean { return _batchable; }
public function set batchable(value:Boolean):void
{
if (_batchable != value)
{
_batchable = value;
setRequiresRedraw();
}
}
}
}