Skip to content

Spritebatch

mattdesl edited this page Dec 16, 2012 · 5 revisions

Below is the full source of our simple sprite batcher. See the repo for a more complete implementation, including better documentation.

This page is a work in progress.

/**  * Copyright (c) 2012, Matt DesLauriers All rights reserved.  *  *	Redistribution and use in source and binary forms, with or without  *	modification, are permitted provided that the following conditions are met:  *  *	* Redistributions of source code must retain the above copyright notice, this  * list of conditions and the following disclaimer.  *  *	* Redistributions in binary  * form must reproduce the above copyright notice, this list of conditions and  * the following disclaimer in the documentation and/or other materials provided  * with the distribution.  *  *	* Neither the name of the Matt DesLauriers nor the names  * of his contributors may be used to endorse or promote products derived from  * this software without specific prior written permission.  *  *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE  *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE  *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE  *	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR  *	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF  *	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  *	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN  *	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  *	POSSIBILITY OF SUCH DAMAGE.  */ package mdesl.graphics; import static org.lwjgl.opengl.GL11.GL_TRIANGLES; import static org.lwjgl.opengl.GL20.glUniform1i; import java.nio.FloatBuffer; import java.util.Arrays; import java.util.List; import mdesl.graphics.glutils.ShaderProgram; import mdesl.graphics.glutils.VertexArray; import mdesl.graphics.glutils.VertexAttrib; import mdesl.graphics.glutils.VertexData; import mdesl.util.MathUtil; import org.lwjgl.opengl.Display; import org.lwjgl.util.vector.Matrix4f; /**  * @author Matt (mdesl) DesLauriers  * @author matheusdev  */ public class SpriteBatch { public static final String U_TEXTURE = "u_texture"; public static final String U_PROJ_VIEW = "u_projView"; public static final String ATTR_COLOR = "Color"; public static final String ATTR_POSITION = "Position"; public static final String ATTR_TEXCOORD = "TexCoord"; public static final String DEFAULT_VERT_SHADER = "uniform mat4 "+U_PROJ_VIEW+";\n" + "attribute vec4 "+ATTR_COLOR+";\n" + "attribute vec2 "+ATTR_TEXCOORD+";\n" + "attribute vec2 "+ATTR_POSITION+";\n" + "varying vec4 vColor;\n" + "varying vec2 vTexCoord; \n" + "void main() {\n" + "	vColor = "+ATTR_COLOR+";\n" + "	vTexCoord = "+ATTR_TEXCOORD+";\n" + "	gl_Position = "+U_PROJ_VIEW+" * vec4("+ATTR_POSITION+".xy, 0, 1);\n" + "}"; public static final String DEFAULT_FRAG_SHADER = "uniform sampler2D "+U_TEXTURE+";\n" + "varying vec4 vColor;\n" + "varying vec2 vTexCoord;\n" + "void main(void) {\n" + "	vec4 texColor = texture2D("+U_TEXTURE+", vTexCoord);\n" + "	gl_FragColor = vColor * texColor;\n" + "}"; public static final List<VertexAttrib> ATTRIBUTES = Arrays.asList( new VertexAttrib(0, ATTR_POSITION, 2), new VertexAttrib(1, ATTR_COLOR, 4), new VertexAttrib(2, ATTR_TEXCOORD, 2)); static ShaderProgram defaultShader; public static int renderCalls = 0; protected FloatBuffer buf16; protected Matrix4f projMatrix; protected Matrix4f viewMatrix; protected Matrix4f projViewMatrix; protected Matrix4f transpositionPool; protected Texture texture; protected ShaderProgram program; protected VertexData data; private int idx; private int maxIndex; private float r=1f, g=1f, b=1f, a=1f; private boolean drawing = false; static ShaderProgram getDefaultShader() { return defaultShader==null	? new ShaderProgram(DEFAULT_VERT_SHADER, DEFAULT_FRAG_SHADER, ATTRIBUTES)	: defaultShader;	} public SpriteBatch(ShaderProgram program, int size) { this.program = program; //later we can do some abstraction to replace this with VBOs... this.data = new VertexArray(size * 6, ATTRIBUTES); //max indices before we need to flush the renderer maxIndex = size * 6; updateMatrices();	} public SpriteBatch(int size) { this(getDefaultShader(), size);	} public SpriteBatch() { this(1000);	} public Matrix4f getViewMatrix() { return viewMatrix;	} public void setColor(float r, float g, float b, float a) { this.r = r; this.g = g; this.b = b; this.a = a;	} /**  * Call to multiply the the projection with the view matrix and save  * the result in the uniform mat4 {@value #U_PROJ_VIEW}.  */ public void updateMatrices() { // Create projection matrix: projMatrix = MathUtil.toOrtho2D(projMatrix, 0, 0, Display.getWidth(), Display.getHeight()); // Create view Matrix, if not present: if (viewMatrix == null) { viewMatrix = new Matrix4f();	} // Multiply the transposed projection matrix with the view matrix: projViewMatrix = Matrix4f.mul( Matrix4f.transpose(projMatrix, transpositionPool), viewMatrix, projViewMatrix); program.use(); // Store the the multiplied matrix in the "projViewMatrix"-uniform: program.storeUniformMat4(U_PROJ_VIEW, projViewMatrix, false); //upload texcoord 0 int tex0 = program.getUniformLocation(U_TEXTURE); if (tex0!=-1) glUniform1i(tex0, 0);	} public void begin() { if (drawing) throw new IllegalStateException("must not be drawing before calling begin()"); drawing = true; program.use(); idx = 0; renderCalls = 0; texture = null;	} public void end() { if (!drawing) throw new IllegalStateException("must be drawing before calling end()"); drawing = false; flush();	} public void flush() { if (idx>0) { data.flip(); render(); idx = 0; data.clear();	}	} public void drawRegion(Texture tex, float srcX, float srcY, float srcWidth, float srcHeight, float dstX, float dstY) { drawRegion(tex, srcX, srcY, srcWidth, srcHeight, dstX, dstY, srcWidth, srcHeight);	} public void drawRegion(Texture tex, float srcX, float srcY, float srcWidth, float srcHeight, float dstX, float dstY, float dstWidth, float dstHeight) { float u = srcX / tex.width; float v = srcY / tex.height; float u2 = (srcX+srcWidth) / tex.width; float v2 = (srcY+srcHeight) / tex.height; draw(tex, dstX, dstY, dstWidth, dstHeight, u, v, u2, v2);	} public void draw(Texture tex, float x, float y) { draw(tex, x, y, tex.width, tex.height);	} public void draw(Texture tex, float x, float y, float width, float height) { draw(tex, x, y, width, height, 0, 0, 1, 1);	} public void draw(Texture tex, float x, float y, float width, float height, float u, float v, float u2, float v2) { checkFlush(tex); //top left, top right, bottom left vertex(x, y, r, g, b, a, u, v); vertex(x+width, y, r, g, b, a, u2, v); vertex(x, y+height, r, g, b, a, u, v2); //top right, bottom right, bottom left vertex(x+width, y, r, g, b, a, u2, v); vertex(x+width, y+height, r, g, b, a, u2, v2); vertex(x, y+height, r, g, b, a, u, v2);	} /**  * Renders a texture using custom vertex attributes; e.g. for different vertex colours.  * This will ignore the current batch color.  *  * @param tex the texture to use  * @param vertices an array of 6 vertices, each holding 8 attributes (total = 48 elements)  * @param offset the offset from the vertices array to start from  */ public void draw(Texture tex, float[] vertices, int offset) { checkFlush(tex); data.put(vertices, offset, data.getTotalNumComponents() * 6); idx += 6;	} VertexData vertex(float x, float y, float r, float g, float b, float a, float u, float v) { data.put(x).put(y).put(r).put(g).put(b).put(a).put(u).put(v); idx++; return data;	} protected void checkFlush(Texture texture) { if (texture==null) throw new NullPointerException("null texture"); //we need to bind a different texture/type. this is //for convenience; ideally the user should order //their rendering wisely to minimize texture binds if (texture!=this.texture || idx >= maxIndex) { //apply the last texture flush(); this.texture = texture;	}	} private void render() { if (texture!=null) texture.bind(); data.bind(); data.draw(GL_TRIANGLES, 0, idx); data.unbind(); renderCalls++;	} }
Clone this wiki locally