Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Spritebatch

mattdesl edited this page · 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++;
    }


}
Something went wrong with that request. Please try again.