/*******************************************************************************
* 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.graphics.GL10;
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 batch using the {@link DefaultGroupStrategy} */
public DecalBatch() {
this(DEFAULT_SIZE, new DefaultGroupStrategy());
}
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 = new Mesh(Mesh.VertexDataType.VertexArray, 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) {
DecalMaterial material = decal.getMaterial();
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);
if (shader != null) {
mesh.render(shader, GL10.GL_TRIANGLES, 0, verticesPosition / 4);
} else {
mesh.render(GL10.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();
}
}