/*******************************************************************************
* 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.utils;
import java.nio.IntBuffer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GLTexture;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;
/** Class that you assign a range of texture units and binds textures for you within that range. It does some basic usage tracking
* to avoid unnecessary bind calls.
* @author xoppa */
public final class DefaultTextureBinder implements TextureBinder {
public final static int ROUNDROBIN = 0;
public final static int WEIGHTED = 1;
/** GLES only supports up to 32 textures */
public final static int MAX_GLES_UNITS = 32;
/** The index of the first exclusive texture unit */
private final int offset;
/** The amount of exclusive textures that may be used */
private final int count;
/** The weight added to a texture when its reused */
private final int reuseWeight;
/** The textures currently exclusive bound */
private final GLTexture[] textures;
/** The weight (reuseWeight * reused - discarded) of the textures */
private final int[] weights;
/** The method of binding to use */
private final int method;
/** Flag to indicate the current texture is reused */
private boolean reused;
private int reuseCount = 0; // TODO remove debug code
private int bindCount = 0; // TODO remove debug code
/** Uses all available texture units and reuse weight of 3 */
public DefaultTextureBinder (final int method) {
this(method, 0);
}
/** Uses all remaining texture units and reuse weight of 3 */
public DefaultTextureBinder (final int method, final int offset) {
this(method, offset, -1);
}
/** Uses reuse weight of 10 */
public DefaultTextureBinder (final int method, final int offset, final int count) {
this(method, offset, count, 10);
}
public DefaultTextureBinder (final int method, final int offset, int count, final int reuseWeight) {
final int max = Math.min(getMaxTextureUnits(), MAX_GLES_UNITS);
if (count < 0) count = max - offset;
if (offset < 0 || count < 0 || (offset + count) > max || reuseWeight < 1)
throw new GdxRuntimeException("Illegal arguments");
this.method = method;
this.offset = offset;
this.count = count;
this.textures = new GLTexture[count];
this.reuseWeight = reuseWeight;
this.weights = (method == WEIGHTED) ? new int[count] : null;
}
private static int getMaxTextureUnits () {
IntBuffer buffer = BufferUtils.newIntBuffer(16);
Gdx.gl.glGetIntegerv(GL20.GL_MAX_TEXTURE_IMAGE_UNITS, buffer);
return buffer.get(0);
}
@Override
public void begin () {
for (int i = 0; i < count; i++) {
textures[i] = null;
if (weights != null) weights[i] = 0;
}
}
@Override
public void end () {
/*
* No need to unbind and textures are set to null in begin() for(int i = 0; i < count; i++) { if (textures[i] != null) {
* Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0 + offset + i); Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, 0); textures[i] = null; }
* }
*/
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
}
@Override
public final int bind (final TextureDescriptor textureDesc) {
return bindTexture(textureDesc, false);
}
private final TextureDescriptor tempDesc = new TextureDescriptor();
@Override
public final int bind (final GLTexture texture) {
tempDesc.set(texture, null, null, null, null);
return bindTexture(tempDesc, false);
}
private final int bindTexture (final TextureDescriptor textureDesc, final boolean rebind) {
final int idx, result;
final GLTexture texture = textureDesc.texture;
reused = false;
switch (method) {
case ROUNDROBIN:
result = offset + (idx = bindTextureRoundRobin(texture));
break;
case WEIGHTED:
result = offset + (idx = bindTextureWeighted(texture));
break;
default:
return -1;
}
if (reused) {
reuseCount++;
if (rebind)
texture.bind(result);
else
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0 + result);
} else
bindCount++;
texture.unsafeSetWrap(textureDesc.uWrap, textureDesc.vWrap);
texture.unsafeSetFilter(textureDesc.minFilter, textureDesc.magFilter);
return result;
}
private int currentTexture = 0;
private final int bindTextureRoundRobin (final GLTexture texture) {
for (int i = 0; i < count; i++) {
final int idx = (currentTexture + i) % count;
if (textures[idx] == texture) {
reused = true;
return idx;
}
}
currentTexture = (currentTexture + 1) % count;
textures[currentTexture] = texture;
texture.bind(offset + currentTexture);
return currentTexture;
}
private final int bindTextureWeighted (final GLTexture texture) {
int result = -1;
int weight = weights[0];
int windex = 0;
for (int i = 0; i < count; i++) {
if (textures[i] == texture) {
result = i;
weights[i] += reuseWeight;
} else if (weights[i] < 0 || --weights[i] < weight) {
weight = weights[i];
windex = i;
}
}
if (result < 0) {
textures[windex] = texture;
weights[windex] = 100;
texture.bind(offset + (result = windex));
} else
reused = true;
return result;
}
@Override
public final int getBindCount () {
return bindCount;
}
@Override
public final int getReuseCount () {
return reuseCount;
}
@Override
public final void resetCounts () {
bindCount = reuseCount = 0;
}
}