/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.decals; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.SortedIntList; /** <p> * Renderer for {@link Decal} objects. * </p> * <p> * New objects are added using {@link DecalBatch#add(Decal)}, there is no limit on how many decals can be added.<br/> * Once all the decals have been submitted a call to {@link DecalBatch#flush()} will batch them together and send big chunks of * geometry to the GL. * </p> * <p> * The size of the batch specifies the maximum number of decals that can be batched together before they have to be submitted to * the graphics pipeline. The default size is {@link DecalBatch#DEFAULT_SIZE}. If it is known before hand that not as many will be * needed on average the batch can be downsized to save memory. If the game is basically 3d based and decals will only be needed * for an orthogonal HUD it makes sense to tune the size down. * </p> * <p> * The way the batch handles things depends on the {@link GroupStrategy}. Different strategies can be used to customize shaders, * states, culling etc. for more details see the {@link GroupStrategy} java doc.<br/> * While it shouldn't be necessary to change strategies, if you have to do so, do it before calling {@link #add(Decal)}, and if * you already did, call {@link #flush()} first. * </p> */ public class DecalBatch implements Disposable { private static final int DEFAULT_SIZE = 1000; private float[] vertices; private Mesh mesh; private final SortedIntList<Array<Decal>> groupList = new SortedIntList<Array<Decal>>(); private GroupStrategy groupStrategy; private final Pool<Array<Decal>> groupPool = new Pool<Array<Decal>>(16) { @Override protected Array<Decal> newObject () { return new Array<Decal>(false, 100); } }; private final Array<Array<Decal>> usedGroups = new Array<Array<Decal>>(16); /** * Creates a new DecalBatch using the given {@link GroupStrategy}. The most * commong strategy to use is a {@link CameraGroupStrategy} * @param groupStrategy */ public DecalBatch (GroupStrategy groupStrategy) { this(DEFAULT_SIZE, groupStrategy); } public DecalBatch (int size, GroupStrategy groupStrategy) { initialize(size); setGroupStrategy(groupStrategy); } /** Sets the {@link GroupStrategy} used * @param groupStrategy Group strategy to use */ public void setGroupStrategy (GroupStrategy groupStrategy) { this.groupStrategy = groupStrategy; } /** Initializes the batch with the given amount of decal objects the buffer is able to hold when full. * * @param size Maximum size of decal objects to hold in memory */ public void initialize (int size) { vertices = new float[size * Decal.SIZE]; Mesh.VertexDataType vertexDataType = Mesh.VertexDataType.VertexArray; if(Gdx.gl30 != null) { vertexDataType = Mesh.VertexDataType.VertexBufferObjectWithVAO; } mesh = new Mesh(vertexDataType, false, size * 4, size * 6, new VertexAttribute( VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute( VertexAttributes.Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE), new VertexAttribute( VertexAttributes.Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0")); short[] indices = new short[size * 6]; int v = 0; for (int i = 0; i < indices.length; i += 6, v += 4) { indices[i] = (short)(v); indices[i + 1] = (short)(v + 2); indices[i + 2] = (short)(v + 1); indices[i + 3] = (short)(v + 1); indices[i + 4] = (short)(v + 2); indices[i + 5] = (short)(v + 3); } mesh.setIndices(indices); } /** @return maximum amount of decal objects this buffer can hold in memory */ public int getSize () { return vertices.length / Decal.SIZE; } /** Add a decal to the batch, marking it for later rendering * * @param decal Decal to add for rendering */ public void add (Decal decal) { int groupIndex = groupStrategy.decideGroup(decal); Array<Decal> targetGroup = groupList.get(groupIndex); if (targetGroup == null) { targetGroup = groupPool.obtain(); targetGroup.clear(); usedGroups.add(targetGroup); groupList.insert(groupIndex, targetGroup); } targetGroup.add(decal); } /** Flush this batch sending all contained decals to GL. After flushing the batch is empty once again. */ public void flush () { render(); clear(); } /** Renders all decals to the buffer and flushes the buffer to the GL when full/done */ protected void render () { groupStrategy.beforeGroups(); for (SortedIntList.Node<Array<Decal>> group : groupList) { groupStrategy.beforeGroup(group.index, group.value); ShaderProgram shader = groupStrategy.getGroupShader(group.index); render(shader, group.value); groupStrategy.afterGroup(group.index); } groupStrategy.afterGroups(); } /** Renders a group of vertices to the buffer, flushing them to GL when done/full * * @param decals Decals to render */ private void render (ShaderProgram shader, Array<Decal> decals) { // batch vertices DecalMaterial lastMaterial = null; int idx = 0; for (Decal decal : decals) { if (lastMaterial == null || !lastMaterial.equals(decal.getMaterial())) { if (idx > 0) { flush(shader, idx); idx = 0; } decal.material.set(); lastMaterial = decal.material; } decal.update(); System.arraycopy(decal.vertices, 0, vertices, idx, decal.vertices.length); idx += decal.vertices.length; // if our batch is full we have to flush it if (idx == vertices.length) { flush(shader, idx); idx = 0; } } // at the end if there is stuff left in the batch we render that if (idx > 0) { flush(shader, idx); } } /** Flushes vertices[0,verticesPosition[ to GL verticesPosition % Decal.SIZE must equal 0 * * @param verticesPosition Amount of elements from the vertices array to flush */ protected void flush (ShaderProgram shader, int verticesPosition) { mesh.setVertices(vertices, 0, verticesPosition); mesh.render(shader, GL20.GL_TRIANGLES, 0, verticesPosition / 4); } /** Remove all decals from batch */ protected void clear () { groupList.clear(); groupPool.freeAll(usedGroups); usedGroups.clear(); } /** Frees up memory by dropping the buffer and underlying resources. If the batch is needed again after disposing it can be * {@link #initialize(int) initialized} again. */ public void dispose () { clear(); vertices = null; mesh.dispose(); } }