1 /*******************************************************************************
\r
2 * Copyright 2011 See AUTHORS file.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
15 ******************************************************************************/
\r
17 package com.badlogic.gdx.graphics.g2d;
\r
19 import static com.badlogic.gdx.graphics.g2d.Sprite.*;
\r
21 import com.badlogic.gdx.Gdx;
\r
22 import com.badlogic.gdx.graphics.Color;
\r
23 import com.badlogic.gdx.graphics.GL10;
\r
24 import com.badlogic.gdx.graphics.GL11;
\r
25 import com.badlogic.gdx.graphics.GL20;
\r
26 import com.badlogic.gdx.graphics.GLCommon;
\r
27 import com.badlogic.gdx.graphics.Mesh;
\r
28 import com.badlogic.gdx.graphics.Mesh.VertexDataType;
\r
29 import com.badlogic.gdx.graphics.Texture;
\r
30 import com.badlogic.gdx.graphics.VertexAttribute;
\r
31 import com.badlogic.gdx.graphics.VertexAttributes.Usage;
\r
32 import com.badlogic.gdx.graphics.glutils.ShaderProgram;
\r
33 import com.badlogic.gdx.math.MathUtils;
\r
34 import com.badlogic.gdx.math.Matrix4;
\r
35 import com.badlogic.gdx.utils.NumberUtils;
\r
37 /** A PolygonSpriteBatch is used to draw 2D polygons that reference a texture (region). The class will batch the drawing commands
\r
38 * and optimize them for processing by the GPU.
\r
40 * To draw something with a PolygonSpriteBatch one has to first call the {@link PolygonSpriteBatch#begin()} method which will
\r
41 * setup appropriate render states. When you are done with drawing you have to call {@link PolygonSpriteBatch#end()} which will
\r
42 * actually draw the things you specified.
\r
44 * All drawing commands of the PolygonSpriteBatch operate in screen coordinates. The screen coordinate system has an x-axis
\r
45 * pointing to the right, an y-axis pointing upwards and the origin is in the lower left corner of the screen. You can also
\r
46 * provide your own transformation and projection matrices if you so wish.
\r
48 * A PolygonSpriteBatch is managed. In case the OpenGL context is lost all OpenGL resources a PolygonSpriteBatch uses internally
\r
49 * get invalidated. A context is lost when a user switches to another application or receives an incoming call on Android. A
\r
50 * SpritPolygonSpriteBatcheBatch will be automatically reloaded after the OpenGL context is restored.
\r
52 * A PolygonSpriteBatch is a pretty heavy object so you should only ever have one in your program.
\r
54 * A PolygonSpriteBatch works with OpenGL ES 1.x and 2.0. In the case of a 2.0 context it will use its own custom shader to draw
\r
55 * all provided sprites. You can set your own custom shader via {@link #setShader(ShaderProgram)}.
\r
57 * A PolygonSpriteBatch has to be disposed if it is no longer used.
\r
59 * @author Stefan Bachmann
\r
60 * @author Nathan Sweet */
\r
61 public class PolygonSpriteBatch {
\r
63 private Mesh[] buffers;
\r
64 private int bufferIndex;
\r
66 private final float[] vertices;
\r
67 private final short[] triangles;
\r
68 private int vertexIndex, triangleIndex;
\r
69 private Texture lastTexture;
\r
70 private boolean drawing;
\r
72 private final Matrix4 transformMatrix = new Matrix4();
\r
73 private final Matrix4 projectionMatrix = new Matrix4();
\r
74 private final Matrix4 combinedMatrix = new Matrix4();
\r
76 private boolean blendingDisabled;
\r
77 private int blendSrcFunc = GL11.GL_SRC_ALPHA;
\r
78 private int blendDstFunc = GL11.GL_ONE_MINUS_SRC_ALPHA;
\r
80 private final ShaderProgram shader;
\r
81 private ShaderProgram customShader;
\r
82 private boolean ownsShader;
\r
84 float color = Color.WHITE.toFloatBits();
\r
85 private Color tempColor = new Color(1, 1, 1, 1);
\r
87 /** Number of render calls since the last {@link #begin()}. **/
\r
88 public int renderCalls = 0;
\r
90 /** Number of rendering calls, ever. Will not be reset unless set manually. **/
\r
91 public int totalRenderCalls = 0;
\r
93 /** The maximum number of triangles rendered in one batch so far. **/
\r
94 public int maxTrianglesInBatch = 0;
\r
96 /** Constructs a new PolygonSpriteBatch with a size of 2000, the default shader, and one buffer.
\r
97 * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
\r
98 public PolygonSpriteBatch () {
\r
102 /** Constructs a PolygonSpriteBatch with the default shader and one buffer.
\r
103 * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
\r
104 public PolygonSpriteBatch (int size) {
\r
105 this(size, 1, null);
\r
108 /** Constructs a new PolygonSpriteBatch with one buffer.
\r
109 * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
\r
110 public PolygonSpriteBatch (int size, ShaderProgram defaultShader) {
\r
111 this(size, 1, defaultShader);
\r
114 /** Constructs a PolygonSpriteBatch with the default shader.
\r
115 * @see PolygonSpriteBatch#PolygonSpriteBatch(int, int, ShaderProgram) */
\r
116 public PolygonSpriteBatch (int size, int buffers) {
\r
117 this(size, buffers, null);
\r
120 /** Constructs a new PolygonSpriteBatch. Sets the projection matrix to an orthographic projection with y-axis point upwards,
\r
121 * x-axis point to the right and the origin being in the bottom left corner of the screen. The projection will be pixel perfect
\r
122 * with respect to the current screen resolution.
\r
124 * The defaultShader specifies the shader to use. Note that the names for uniforms for this default shader are different than
\r
125 * the ones expect for shaders set with {@link #setShader(ShaderProgram)}. See {@link SpriteBatch#createDefaultShader()}.
\r
126 * @param size The max number of vertices and number of triangles in a single batch. Max of 10920.
\r
127 * @param buffers The number of meshes to use. This is an expert function. It only makes sense with VBOs (see
\r
128 * {@link Mesh#forceVBO}).
\r
129 * @param defaultShader The default shader to use. This is not owned by the PolygonSpriteBatch and must be disposed separately. */
\r
130 public PolygonSpriteBatch (int size, int buffers, ShaderProgram defaultShader) {
\r
131 // 32767 is max index, so 32767 / 3 - (32767 / 3 % 3) = 10920.
\r
132 if (size > 10920) throw new IllegalArgumentException("Can't have more than 10920 triangles per batch: " + size);
\r
134 this.buffers = new Mesh[buffers];
\r
135 for (int i = 0; i < buffers; i++) {
\r
136 this.buffers[i] = new Mesh(VertexDataType.VertexArray, false, size, size * 3, new VertexAttribute(Usage.Position, 2,
\r
137 ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
\r
138 new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));
\r
140 mesh = this.buffers[0];
\r
142 vertices = new float[size * VERTEX_SIZE];
\r
143 triangles = new short[size * 3];
\r
145 if (Gdx.graphics.isGL20Available() && defaultShader == null) {
\r
146 shader = SpriteBatch.createDefaultShader();
\r
149 shader = defaultShader;
\r
151 projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
\r
154 /** Sets up the PolygonSpriteBatch for drawing. This will disable depth buffer writting. It enables blending and texturing. If
\r
155 * you have more texture units enabled than the first one you have to disable them before calling this. Uses a screen
\r
156 * coordinate system by default where everything is given in pixels. You can specify your own projection and modelview matrices
\r
157 * via {@link #setProjectionMatrix(Matrix4)} and {@link #setTransformMatrix(Matrix4)}. */
\r
158 public void begin () {
\r
159 if (drawing) throw new IllegalStateException("PolygonSpriteBatch.end must be called before begin.");
\r
162 Gdx.gl.glDepthMask(false);
\r
163 if (Gdx.graphics.isGL20Available()) {
\r
164 if (customShader != null)
\r
165 customShader.begin();
\r
169 Gdx.gl.glEnable(GL10.GL_TEXTURE_2D);
\r
176 /** Finishes off rendering. Enables depth writes, disables blending and texturing. Must always be called after a call to
\r
177 * {@link #begin()} */
\r
178 public void end () {
\r
179 if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before end.");
\r
180 if (vertexIndex > 0) flush();
\r
181 lastTexture = null;
\r
184 GLCommon gl = Gdx.gl;
\r
185 gl.glDepthMask(true);
\r
186 if (isBlendingEnabled()) gl.glDisable(GL10.GL_BLEND);
\r
188 if (Gdx.graphics.isGL20Available()) {
\r
189 if (customShader != null)
\r
190 customShader.end();
\r
194 gl.glDisable(GL10.GL_TEXTURE_2D);
\r
198 /** Sets the color used to tint images when they are added to the PolygonSpriteBatch. Default is {@link Color#WHITE}. */
\r
199 public void setColor (Color tint) {
\r
200 color = tint.toFloatBits();
\r
203 /** @see #setColor(Color) */
\r
204 public void setColor (float r, float g, float b, float a) {
\r
205 int intBits = (int)(255 * a) << 24 | (int)(255 * b) << 16 | (int)(255 * g) << 8 | (int)(255 * r);
\r
206 color = NumberUtils.intToFloatColor(intBits);
\r
209 /** @see #setColor(Color)
\r
210 * @see Color#toFloatBits() */
\r
211 public void setColor (float color) {
\r
212 this.color = color;
\r
215 /** @return the rendering color of this PolygonSpriteBatch. Manipulating the returned instance has no effect. */
\r
216 public Color getColor () {
\r
217 int intBits = NumberUtils.floatToIntColor(color);
\r
218 Color color = this.tempColor;
\r
219 color.r = (intBits & 0xff) / 255f;
\r
220 color.g = ((intBits >>> 8) & 0xff) / 255f;
\r
221 color.b = ((intBits >>> 16) & 0xff) / 255f;
\r
222 color.a = ((intBits >>> 24) & 0xff) / 255f;
\r
226 /** Draws a polygon region with the bottom left corner at x,y having the width and height of the region. */
\r
227 public void draw (PolygonRegion region, float x, float y) {
\r
228 if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");
\r
230 final short[] triangles = this.triangles;
\r
231 final short[] regionTriangles = region.triangles;
\r
232 final int regionTrianglesLength = regionTriangles.length;
\r
233 final float[] regionVertices = region.vertices;
\r
234 final int regionVerticesLength = regionVertices.length;
\r
236 final Texture texture = region.region.texture;
\r
237 if (texture != lastTexture)
\r
238 switchTexture(texture);
\r
239 else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
\r
242 int triangleIndex = this.triangleIndex;
\r
243 int vertexIndex = this.vertexIndex;
\r
244 final int startVertex = vertexIndex / VERTEX_SIZE;
\r
246 for (int i = 0; i < regionTrianglesLength; i++)
\r
247 triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
\r
248 this.triangleIndex = triangleIndex;
\r
250 final float[] vertices = this.vertices;
\r
251 final float color = this.color;
\r
252 final float[] textureCoords = region.textureCoords;
\r
254 for (int i = 0; i < regionVerticesLength; i += 2) {
\r
255 vertices[vertexIndex++] = regionVertices[i] + x;
\r
256 vertices[vertexIndex++] = regionVertices[i + 1] + y;
\r
257 vertices[vertexIndex++] = color;
\r
258 vertices[vertexIndex++] = textureCoords[i];
\r
259 vertices[vertexIndex++] = textureCoords[i + 1];
\r
261 this.vertexIndex = vertexIndex;
\r
264 /** Draws a polygon region with the bottom left corner at x,y and stretching the region to cover the given width and height. */
\r
265 public void draw (PolygonRegion region, float x, float y, float width, float height) {
\r
266 if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");
\r
268 final short[] triangles = this.triangles;
\r
269 final short[] regionTriangles = region.triangles;
\r
270 final int regionTrianglesLength = regionTriangles.length;
\r
271 final float[] regionVertices = region.vertices;
\r
272 final int regionVerticesLength = regionVertices.length;
\r
273 final TextureRegion textureRegion = region.region;
\r
275 final Texture texture = textureRegion.texture;
\r
276 if (texture != lastTexture)
\r
277 switchTexture(texture);
\r
278 else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
\r
281 int triangleIndex = this.triangleIndex;
\r
282 int vertexIndex = this.vertexIndex;
\r
283 final int startVertex = vertexIndex / VERTEX_SIZE;
\r
285 for (int i = 0, n = regionTriangles.length; i < n; i++)
\r
286 triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
\r
287 this.triangleIndex = triangleIndex;
\r
289 final float[] vertices = this.vertices;
\r
290 final float color = this.color;
\r
291 final float[] textureCoords = region.textureCoords;
\r
292 final float sX = width / textureRegion.regionWidth;
\r
293 final float sY = height / textureRegion.regionHeight;
\r
295 for (int i = 0; i < regionVerticesLength; i += 2) {
\r
296 vertices[vertexIndex++] = regionVertices[i] * sX + x;
\r
297 vertices[vertexIndex++] = regionVertices[i + 1] * sY + y;
\r
298 vertices[vertexIndex++] = color;
\r
299 vertices[vertexIndex++] = textureCoords[i];
\r
300 vertices[vertexIndex++] = textureCoords[i + 1];
\r
302 this.vertexIndex = vertexIndex;
\r
305 /** Draws the polygon region with the bottom left corner at x,y and stretching the region to cover the given width and height.
\r
306 * The polygon region is offset by originX, originY relative to the origin. Scale specifies the scaling factor by which the
\r
307 * polygon region should be scaled around originX, originY. Rotation specifies the angle of counter clockwise rotation of the
\r
308 * rectangle around originX, originY. */
\r
309 public void draw (PolygonRegion region, float x, float y, float originX, float originY, float width, float height,
\r
310 float scaleX, float scaleY, float rotation) {
\r
311 if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");
\r
313 final short[] triangles = this.triangles;
\r
314 final short[] regionTriangles = region.triangles;
\r
315 final int regionTrianglesLength = regionTriangles.length;
\r
316 final float[] regionVertices = region.vertices;
\r
317 final int regionVerticesLength = regionVertices.length;
\r
318 final TextureRegion textureRegion = region.region;
\r
320 Texture texture = textureRegion.texture;
\r
321 if (texture != lastTexture)
\r
322 switchTexture(texture);
\r
323 else if (triangleIndex + regionTrianglesLength > triangles.length || vertexIndex + regionVerticesLength > vertices.length)
\r
326 int triangleIndex = this.triangleIndex;
\r
327 int vertexIndex = this.vertexIndex;
\r
328 final int startVertex = vertexIndex / VERTEX_SIZE;
\r
330 for (int i = 0; i < regionTrianglesLength; i++)
\r
331 triangles[triangleIndex++] = (short)(regionTriangles[i] + startVertex);
\r
332 this.triangleIndex = triangleIndex;
\r
334 final float[] vertices = this.vertices;
\r
335 final float color = this.color;
\r
336 final float[] textureCoords = region.textureCoords;
\r
338 final float worldOriginX = x + originX;
\r
339 final float worldOriginY = y + originY;
\r
340 final float sX = width / textureRegion.regionWidth;
\r
341 final float sY = height / textureRegion.regionHeight;
\r
342 final float cos = MathUtils.cosDeg(rotation);
\r
343 final float sin = MathUtils.sinDeg(rotation);
\r
346 for (int i = 0; i < regionVerticesLength; i += 2) {
\r
347 fx = (regionVertices[i] * sX - originX) * scaleX;
\r
348 fy = (regionVertices[i + 1] * sY - originY) * scaleY;
\r
349 vertices[vertexIndex++] = cos * fx - sin * fy + worldOriginX;
\r
350 vertices[vertexIndex++] = sin * fx + cos * fy + worldOriginY;
\r
351 vertices[vertexIndex++] = color;
\r
352 vertices[vertexIndex++] = textureCoords[i];
\r
353 vertices[vertexIndex++] = textureCoords[i + 1];
\r
355 this.vertexIndex = vertexIndex;
\r
358 /** Draws the polygon using the given vertices and triangles. Each vertices must be made up of 5 elements in this order: x, y,
\r
360 public void draw (Texture texture, float[] polygonVertices, int verticesOffset, int verticesCount, short[] polygonTriangles,
\r
361 int trianglesOffset, int trianglesCount) {
\r
362 if (!drawing) throw new IllegalStateException("PolygonSpriteBatch.begin must be called before draw.");
\r
364 final short[] triangles = this.triangles;
\r
365 final float[] vertices = this.vertices;
\r
367 if (texture != lastTexture)
\r
368 switchTexture(texture);
\r
369 else if (triangleIndex + trianglesCount > triangles.length || vertexIndex + verticesCount > vertices.length) //
\r
372 int triangleIndex = this.triangleIndex;
\r
373 final int vertexIndex = this.vertexIndex;
\r
374 final int startVertex = vertexIndex / VERTEX_SIZE;
\r
376 for (int i = trianglesOffset, n = i + trianglesCount; i < n; i++)
\r
377 triangles[triangleIndex++] = (short)(polygonTriangles[i] + startVertex);
\r
378 this.triangleIndex = triangleIndex;
\r
380 System.arraycopy(polygonVertices, verticesOffset, vertices, vertexIndex, verticesCount);
\r
381 this.vertexIndex += verticesCount;
\r
384 /** Causes any pending sprites to be rendered, without ending the PolygonSpriteBatch. */
\r
385 public void flush () {
\r
386 if (vertexIndex == 0) return;
\r
389 totalRenderCalls++;
\r
390 int trianglesInBatch = triangleIndex;
\r
391 if (trianglesInBatch > maxTrianglesInBatch) maxTrianglesInBatch = trianglesInBatch;
\r
393 lastTexture.bind();
\r
394 Mesh mesh = this.mesh;
\r
395 mesh.setVertices(vertices, 0, vertexIndex);
\r
396 mesh.setIndices(triangles, 0, triangleIndex);
\r
398 if (blendingDisabled) {
\r
399 Gdx.gl.glDisable(GL20.GL_BLEND);
\r
401 Gdx.gl.glEnable(GL20.GL_BLEND);
\r
402 if (blendSrcFunc != -1) Gdx.gl.glBlendFunc(blendSrcFunc, blendDstFunc);
\r
405 if (Gdx.graphics.isGL20Available())
\r
406 mesh.render(customShader != null ? customShader : shader, GL10.GL_TRIANGLES, 0, trianglesInBatch);
\r
408 mesh.render(GL10.GL_TRIANGLES, 0, trianglesInBatch);
\r
413 if (bufferIndex == buffers.length) bufferIndex = 0;
\r
414 this.mesh = buffers[bufferIndex];
\r
417 /** Disables blending for drawing sprites. Calling this within {@link #begin()}/{@link #end()} will flush the batch. */
\r
418 public void disableBlending () {
\r
420 blendingDisabled = true;
\r
423 /** Enables blending for sprites. Calling this within {@link #begin()}/{@link #end()} will flush the batch. */
\r
424 public void enableBlending () {
\r
426 blendingDisabled = false;
\r
429 /** Sets the blending function to be used when rendering sprites.
\r
430 * @param srcFunc the source function, e.g. GL11.GL_SRC_ALPHA. If set to -1, PolygonSpriteBatch won't change the blending
\r
432 * @param dstFunc the destination function, e.g. GL11.GL_ONE_MINUS_SRC_ALPHA */
\r
433 public void setBlendFunction (int srcFunc, int dstFunc) {
\r
434 if (blendSrcFunc == srcFunc && blendDstFunc == dstFunc) return;
\r
436 blendSrcFunc = srcFunc;
\r
437 blendDstFunc = dstFunc;
\r
440 /** Disposes all resources associated with this PolygonSpriteBatch. */
\r
441 public void dispose () {
\r
442 for (int i = 0; i < buffers.length; i++)
\r
443 buffers[i].dispose();
\r
444 if (ownsShader && shader != null) shader.dispose();
\r
447 /** Returns the current projection matrix. Changing this within {@link #begin()}/{@link #end()} results in undefined behaviour. */
\r
448 public Matrix4 getProjectionMatrix () {
\r
449 return projectionMatrix;
\r
452 /** Returns the current transform matrix. Changing this within {@link #begin()}/{@link #end()} results in undefined behaviour. */
\r
453 public Matrix4 getTransformMatrix () {
\r
454 return transformMatrix;
\r
457 /** Sets the projection matrix to be used by this PolygonSpriteBatch. If this is called inside a {@link #begin()}/{@link #end()}
\r
458 * block, the current batch is flushed to the gpu. */
\r
459 public void setProjectionMatrix (Matrix4 projection) {
\r
460 if (drawing) flush();
\r
461 projectionMatrix.set(projection);
\r
462 if (drawing) setupMatrices();
\r
465 /** Sets the transform matrix to be used by this PolygonSpriteBatch. If this is called inside a {@link #begin()}/{@link #end()}
\r
466 * block, the current batch is flushed to the gpu. */
\r
467 public void setTransformMatrix (Matrix4 transform) {
\r
468 if (drawing) flush();
\r
469 transformMatrix.set(transform);
\r
470 if (drawing) setupMatrices();
\r
473 private void setupMatrices () {
\r
474 if (!Gdx.graphics.isGL20Available()) {
\r
475 GL10 gl = Gdx.gl10;
\r
476 gl.glMatrixMode(GL10.GL_PROJECTION);
\r
477 gl.glLoadMatrixf(projectionMatrix.val, 0);
\r
478 gl.glMatrixMode(GL10.GL_MODELVIEW);
\r
479 gl.glLoadMatrixf(transformMatrix.val, 0);
\r
481 combinedMatrix.set(projectionMatrix).mul(transformMatrix);
\r
482 if (customShader != null) {
\r
483 customShader.setUniformMatrix("u_proj", projectionMatrix);
\r
484 customShader.setUniformMatrix("u_trans", transformMatrix);
\r
485 customShader.setUniformMatrix("u_projTrans", combinedMatrix);
\r
486 customShader.setUniformi("u_texture", 0);
\r
488 shader.setUniformMatrix("u_projTrans", combinedMatrix);
\r
489 shader.setUniformi("u_texture", 0);
\r
494 private void switchTexture (Texture texture) {
\r
496 lastTexture = texture;
\r
499 /** @see SpriteBatch#setShader(ShaderProgram) */
\r
500 public void setShader (ShaderProgram shader) {
\r
503 if (customShader != null)
\r
504 customShader.end();
\r
508 customShader = shader;
\r
510 if (customShader != null)
\r
511 customShader.begin();
\r
513 this.shader.begin();
\r
518 /** @return whether blending for sprites is enabled */
\r
519 public boolean isBlendingEnabled () {
\r
520 return !blendingDisabled;
\r