\r
package com.badlogic.gdx.graphics;\r
\r
-import java.nio.FloatBuffer;\r
import java.util.ArrayList;\r
\r
+import com.badlogic.gdx.ApplicationListener;\r
import com.badlogic.gdx.Gdx;\r
import com.badlogic.gdx.graphics.VertexAttributes.Usage;\r
import com.badlogic.gdx.graphics.glutils.ShaderProgram;\r
import com.badlogic.gdx.math.Matrix4;\r
import com.badlogic.gdx.utils.MathUtils;\r
\r
+/**\r
+ * Draws 2D images, optimized for geometry that does not change. Sprites and/or textures are cached and given an ID, which can\r
+ * later be used for drawing. The size, color, and texture region for each cached image cannot be modified. This information is\r
+ * stored in video memory and does not have to be sent to the GPU each time it is drawn.<br>\r
+ * <br>\r
+ * To cache {@link Sprite sprites} or {@link Texture textures}, first call {@link SpriteCache#beginCache()}, then call the\r
+ * appropriate add method to define the images. To complete the cache, call {@link SpriteCache#endCache()} and store the returned\r
+ * cache ID.<br>\r
+ * <br>\r
+ * To draw with SpriteCache, first call {@link #begin()}, then call {@link #draw(int)} with a cache ID. When SpriteCache drawing\r
+ * is complete, call {@link #end()}.<br>\r
+ * <br>\r
+ * By default, SpriteCache draws using screen coordinates and uses an x-axis pointing to the right, an y-axis pointing upwards and\r
+ * the origin is the bottom left corner of the screen. The default transformation and projection matrices can be changed. If the\r
+ * screen is {@link ApplicationListener#resize(int, int) resized}, the SpriteCache's matrices must be updated. For example:<br>\r
+ * <code>cache.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());</code><br>\r
+ * <br>\r
+ * SpriteCache is managed. If the OpenGL context is lost and the restored, all OpenGL resources a SpriteCache uses internally are\r
+ * restored.<br>\r
+ * <br>\r
+ * SpriteCache is a reasonably heavyweight object. Typically only one instance should be used for an entire application.<br>\r
+ * <br>\r
+ * SpriteCache works with OpenGL ES 1.x and 2.0. For 2.0, it uses its own custom shader to draw.<br>\r
+ * <br>\r
+ * SpriteCache must be disposed once it is no longer needed.\r
+ */\r
public class SpriteCache {\r
static private final float[] tempVertices = new float[Sprite.SPRITE_SIZE];\r
\r
- final Mesh mesh;\r
- boolean drawing = false;\r
+ private final Mesh mesh;\r
+ private boolean drawing;\r
private final Matrix4 transformMatrix = new Matrix4();\r
private final Matrix4 projectionMatrix = new Matrix4();\r
+ private ArrayList<Cache> caches = new ArrayList();\r
\r
private final Matrix4 combinedMatrix = new Matrix4();\r
- ShaderProgram shader;\r
+ private ShaderProgram shader;\r
\r
- private CacheBuidler builder;\r
+ private boolean caching;\r
+ private final ArrayList<Texture> textures = new ArrayList(8);\r
+ private final ArrayList<Integer> counts = new ArrayList(8);\r
+ private int offset;\r
\r
+ /**\r
+ * Creates a cache that can contain up to 1000 images.\r
+ */\r
public SpriteCache () {\r
this(1000);\r
}\r
\r
+ /**\r
+ * Creates a cache with the specified size, using a default shader if OpenGL ES 2.0 is being used.\r
+ * @param size The maximum number of images this cache can hold. The memory required to hold the images is allocated up front.\r
+ */\r
public SpriteCache (int size) {\r
+ this(size, createDefaultShader());\r
+ }\r
+\r
+ /**\r
+ * Creates a cache with the specified size and OpenGL ES 2.0 shader.\r
+ * @param size The maximum number of images this cache can hold. The memory required to hold the images is allocated up front.\r
+ */\r
+ public SpriteCache (int size, ShaderProgram shader) {\r
mesh = new Mesh(true, size * 4, size * 6, new VertexAttribute(Usage.Position, 2, "a_position"), new VertexAttribute(\r
Usage.ColorPacked, 4, "a_color"), new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoords"));\r
mesh.setAutoBind(false);\r
\r
- short[] indices = new short[size * 6];\r
- int len = size * 6;\r
+ int length = size * 6;\r
+ short[] indices = new short[length];\r
short j = 0;\r
- for (int i = 0; i < len; i += 6, j += 4) {\r
+ for (int i = 0; i < length; i += 6, j += 4) {\r
indices[i + 0] = (short)(j + 0);\r
indices[i + 1] = (short)(j + 1);\r
indices[i + 2] = (short)(j + 2);\r
mesh.setIndices(indices);\r
\r
projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());\r
-\r
- if (Gdx.graphics.isGL20Available()) createShader();\r
- }\r
-\r
- private void createShader () {\r
- String vertexShader = "attribute vec4 a_position;\n" //\r
- + "attribute vec4 a_color;\n" //\r
- + "attribute vec2 a_texCoords;\n" //\r
- + "uniform mat4 u_projectionViewMatrix;\n" //\r
- + "varying vec4 v_color; \n" //\r
- + "varying vec2 v_texCoords;\n" //\r
- + "void main()\n" //\r
- + "{\n" //\r
- + " v_color = a_color; \n" //\r
- + " v_texCoords = a_texCoords; \n" //\r
- + " gl_Position = u_projectionViewMatrix * a_position;\n" //\r
- + "}";\r
- String fragmentShader = "precision mediump float;\n" //\r
- + "varying vec4 v_color;\n" //\r
- + "varying vec2 v_texCoords;\n" //\r
- + "uniform sampler2D u_texture;\n" //\r
- + "void main()\n" //\r
- + "{\n" //\r
- + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //\r
- + "}";\r
- shader = new ShaderProgram(vertexShader, fragmentShader);\r
- if (shader.isCompiled() == false) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());\r
}\r
\r
+ /**\r
+ * Starts the definition of a new cache, allowing the add and {@link #endCache()} methods to be called.\r
+ */\r
public void beginCache () {\r
- if (builder != null) throw new IllegalStateException("endCache must be called before begin.");\r
- builder = new CacheBuidler(mesh.getNumVertices() / 2 * 6);\r
+ if (caching) throw new IllegalStateException("endCache must be called before begin.");\r
+ caching = true;\r
+\r
+ offset = mesh.getNumVertices() / 2 * 6;\r
mesh.getVerticesBuffer().compact();\r
}\r
\r
- public Cache endCache () {\r
- if (builder == null) throw new IllegalStateException("beginCache mustbe called before endCache.");\r
- Cache cache = builder.finish();\r
- builder = null;\r
+ /**\r
+ * Ends the definition of a cache, returning the cache ID to be used with {@link #draw(int)}.\r
+ */\r
+ public int endCache () {\r
+ if (!caching) throw new IllegalStateException("beginCache must be called before endCache.");\r
+ caching = false;\r
+\r
+ Cache cache = new Cache(offset, textures.toArray(new Texture[textures.size()]), new int[counts.size()]);\r
+ for (int i = 0, n = counts.size(); i < n; i++)\r
+ cache.counts[i] = counts.get(i);\r
+ caches.add(cache);\r
+\r
mesh.getVerticesBuffer().flip();\r
- return cache;\r
+\r
+ textures.clear();\r
+ counts.clear();\r
+\r
+ return caches.size() - 1;\r
}\r
\r
+ /**\r
+ * Adds the specified image to the cache.\r
+ */\r
public void add (Texture texture, float[] vertices, int offset, int length) {\r
- if (builder == null) throw new IllegalStateException("beginCache mustbe called before add.");\r
- builder.add(texture, vertices.length / Sprite.SPRITE_SIZE);\r
+ if (!caching) throw new IllegalStateException("beginCache must be called before add.");\r
+\r
+ int count = vertices.length / Sprite.SPRITE_SIZE * 6;\r
+ int lastIndex = textures.size() - 1;\r
+ if (lastIndex < 0 || textures.get(lastIndex) != texture) {\r
+ textures.add(texture);\r
+ counts.add(count);\r
+ } else\r
+ counts.set(lastIndex, counts.get(lastIndex) + count);\r
\r
mesh.getVerticesBuffer().put(vertices);\r
}\r
\r
+ /**\r
+ * Adds the specified image to the cache.\r
+ */\r
public void add (Texture texture, float x, float y, int srcWidth, int srcHeight, float u, float v, float u2, float v2,\r
float color) {\r
final float fx2 = x + srcWidth;\r
add(texture, tempVertices, 0, 20);\r
}\r
\r
+ /**\r
+ * Adds the specified image to the cache.\r
+ */\r
public void add (Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight, Color tint) {\r
float invTexWidth = 1.0f / texture.getWidth();\r
float invTexHeight = 1.0f / texture.getHeight();\r
add(texture, tempVertices, 0, 20);\r
}\r
\r
+ /**\r
+ * Adds the specified image to the cache.\r
+ */\r
public void add (Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth,\r
int srcHeight, Color tint, boolean flipX, boolean flipY) {\r
\r
add(texture, tempVertices, 0, 20);\r
}\r
\r
+ /**\r
+ * Adds the specified image to the cache.\r
+ */\r
public void add (Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX,\r
float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, Color tint, boolean flipX, boolean flipY) {\r
\r
add(texture, tempVertices, 0, 20);\r
}\r
\r
+ /**\r
+ * Adds the specified sprite to the cache.\r
+ */\r
public void add (Sprite sprite) {\r
add(sprite.getTexture(), sprite.getVertices(), 0, 20);\r
}\r
\r
+ /**\r
+ * Prepares the OpenGL state for SpriteCache rendering.\r
+ */\r
public void begin () {\r
if (drawing) throw new IllegalStateException("end must be called before begin.");\r
\r
gl.glDisable(GL10.GL_DEPTH_TEST);\r
gl.glDisable(GL10.GL_CULL_FACE);\r
gl.glDepthMask(false);\r
-\r
gl.glEnable(GL10.GL_TEXTURE_2D);\r
- // gl.glActiveTexture( GL10.GL_TEXTURE0 );\r
\r
gl.glMatrixMode(GL10.GL_PROJECTION);\r
gl.glLoadMatrixf(projectionMatrix.val, 0);\r
gl.glDisable(GL20.GL_DEPTH_TEST);\r
gl.glDisable(GL20.GL_CULL_FACE);\r
gl.glDepthMask(false);\r
-\r
gl.glEnable(GL20.GL_TEXTURE_2D);\r
- // gl.glActiveTexture( GL20.GL_TEXTURE0 );\r
\r
shader.begin();\r
shader.setUniformMatrix("u_projectionViewMatrix", combinedMatrix);\r
drawing = true;\r
}\r
\r
+ /**\r
+ * Completes rendering for this SpriteCache.f\r
+ */\r
public void end () {\r
if (!drawing) throw new IllegalStateException("begin must be called before end.");\r
drawing = false;\r
mesh.unbind();\r
}\r
\r
- public void draw (Cache cache) {\r
+ /**\r
+ * Draws all the images defined for the specified cache ID.\r
+ */\r
+ public void draw (int cacheID) {\r
if (!drawing) throw new IllegalStateException("SpriteCache.begin must be called before draw.");\r
\r
+ Cache cache = caches.get(cacheID);\r
int offset = cache.offset;\r
Texture[] textures = cache.textures;\r
int[] counts = cache.counts;\r
}\r
}\r
\r
+ /**\r
+ * Releases all resources held by this SpriteCache.\r
+ */\r
public void dispose () {\r
mesh.dispose();\r
if (shader != null) shader.dispose();\r
transformMatrix.set(transform);\r
}\r
\r
- private class CacheBuidler {\r
- private final ArrayList<Texture> textures = new ArrayList(8);\r
- private final ArrayList<Integer> counts = new ArrayList(8);\r
- private final int offset;\r
-\r
- CacheBuidler (int offset) {\r
- this.offset = offset;\r
- }\r
-\r
- void add (Texture texture, int count) {\r
- count *= 6;\r
- int lastIndex = textures.size() - 1;\r
- if (lastIndex < 0 || textures.get(lastIndex) != texture) {\r
- textures.add(texture);\r
- counts.add(count);\r
- } else\r
- counts.set(lastIndex, counts.get(lastIndex) + count);\r
- }\r
-\r
- Cache finish () {\r
- Cache cache = new Cache(offset, textures.toArray(new Texture[textures.size()]), new int[counts.size()]);\r
- for (int i = 0, n = counts.size(); i < n; i++)\r
- cache.counts[i] = counts.get(i);\r
- return cache;\r
- }\r
- }\r
-\r
- public class Cache {\r
+ static private class Cache {\r
final int offset;\r
final Texture[] textures;\r
final int[] counts;\r
this.counts = counts;\r
}\r
}\r
+\r
+ static ShaderProgram createDefaultShader () {\r
+ if (!Gdx.graphics.isGL20Available()) return null;\r
+ String vertexShader = "attribute vec4 a_position;\n" //\r
+ + "attribute vec4 a_color;\n" //\r
+ + "attribute vec2 a_texCoords;\n" //\r
+ + "uniform mat4 u_projectionViewMatrix;\n" //\r
+ + "varying vec4 v_color; \n" //\r
+ + "varying vec2 v_texCoords;\n" //\r
+ + "void main()\n" //\r
+ + "{\n" //\r
+ + " v_color = a_color; \n" //\r
+ + " v_texCoords = a_texCoords; \n" //\r
+ + " gl_Position = u_projectionViewMatrix * a_position;\n" //\r
+ + "}";\r
+ String fragmentShader = "precision mediump float;\n" //\r
+ + "varying vec4 v_color;\n" //\r
+ + "varying vec2 v_texCoords;\n" //\r
+ + "uniform sampler2D u_texture;\n" //\r
+ + "void main()\n" //\r
+ + "{\n" //\r
+ + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //\r
+ + "}";\r
+ ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);\r
+ if (shader.isCompiled() == false) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());\r
+ return shader;\r
+ }\r
}\r