/*
* Aphelion
* Copyright (c) 2013 Joris van der Wel
*
* This file is part of Aphelion
*
* Aphelion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* Aphelion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Aphelion. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, the following supplemental terms apply, based on section 7 of
* the GNU Affero General Public License (version 3):
* a) Preservation of all legal notices and author attributions
* b) Prohibition of misrepresentation of the origin of this material, and
* modified versions are required to be marked in reasonable ways as
* different from the original version (for example by appending a copyright notice).
*
* Linking this library statically or dynamically with other modules is making a
* combined work based on this library. Thus, the terms and conditions of the
* GNU Affero General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent module,
* the terms and conditions of the license of that module. An independent
* module is a module which is not derived from or based on this library.
*/
package aphelion.client.resource;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;
import org.newdawn.slick.Image;
import org.newdawn.slick.opengl.SlickLastBindHack;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/** An asynchronously loaded texture to be bound within JOGL.
* Based on org.newdawn.slick.opengl.TextureImpl, however this
* implementation uses a worker to do as much work as possible during
* loading. Until loading is complete this object will return
* placeholder values. This avoids having to use complex loading
* logic in other parts of the project.
*
* This object is responsible for
* keeping track of a given OpenGL texture and for calculating the
* texturing mapping coordinates of the full image.
*
* Since textures need to be powers of 2 the actual texture may be
* considerably bigger that the source image and hence the texture
* mapping coordinates need to be adjusted to match up drawing the
* sprite against the texture.
*
* @author Kevin Glass
* @author Brian Matzon
* @author Joris
*/
public class AsyncTexture implements Texture
{
private static final Logger log = Logger.getLogger("aphelion.client.graphics");
private static SGL GL = Renderer.get();
public static Texture getLastBind()
{
return TextureImpl.getLastBind();
}
public static void setLastBind(Texture texture)
{
SlickLastBindHack.setLastBind(texture);
}
/** Clear the bind cache. Call me at the start of each render loop.
*/
public static void unbind()
{
TextureImpl.unbind();
}
public static void bindNone()
{
TextureImpl.bindNone();
}
private AsyncTextureLoader loader;
/** The ResourceDB key. */
String resourceKey;
/** The GL texture target type. For example GL11.GL_TEXTURE_2D */
int target;
int textureID;
private boolean loaded = false;
boolean error = false;
boolean released = false;
int imageHeight = 1;
int imageWidth = 1;
int texWidth = 1;
int texHeight = 1;
float widthRatio = 1;
float heightRatio = 1;
boolean alpha = false;
public AsyncTexture(AsyncTextureLoader loader, String resourceKey, int target)
{
this.loader = loader;
this.resourceKey = resourceKey;
this.target = target;
// GL11.GL_TEXTURE_2D
}
@Override
protected void finalize() throws Throwable
{
loader.finalizeTexture(this);
super.finalize();
}
/** Has the actual texture been loaded into the GPU
* @return
*/
public boolean isLoaded()
{
return loaded;
}
/** Was there an error during loading?
* @return If true, this texture will never complete loading
*/
public boolean isError()
{
return error;
}
public boolean isReleased()
{
return released;
}
@Override
public void bind()
{
if (getLastBind() == this)
{
return;
}
if (isLoaded())
{
setLastBind(this);
GL.glEnable(SGL.GL_TEXTURE_2D);
GL.glBindTexture(target, textureID);
}
else
{
GL.glDisable(SGL.GL_TEXTURE_2D);
GL.glColor4f(0, 0, 0, 0);
}
setLastBind(this);
}
@Override
public boolean hasAlpha()
{
return alpha;
}
@Override
public String getTextureRef()
{
return resourceKey;
}
public String getResourceKey()
{
return resourceKey;
}
/**
* Get the height of the original image
*
* @return The height of the original image
*/
@Override
public int getImageHeight()
{
return imageHeight;
}
/**
* Get the width of the original image
*
* @return The height of the original image
*/
@Override
public int getImageWidth()
{
return imageWidth;
}
/**
* Get the ratio between the texture height and the image height.
*
* @return imageHeight / textureHeight (between 0 and 1)
*/
@Override
public float getHeight()
{
return heightRatio;
}
/**
* Get the ratio between the texture width and the image width.
*
* @return imageWidth / textureWidth (between 0 and 1)
*/
@Override
public float getWidth()
{
return widthRatio;
}
/**
* Get the height of the actual texture.
* This height may differ from the image height since texture sizes are usually a power of 2
*
* @return The height of the actual texture
*/
@Override
public int getTextureHeight()
{
return texHeight;
}
/**
* Get the width of the actual texture.
* This height may differ from the image height since texture sizes are usually a power of 2
*
* @return The height of the actual texture
*/
@Override
public int getTextureWidth()
{
return texWidth;
}
/** Explicit release */
@Override
public void release()
{
ByteBuffer temp = ByteBuffer.allocateDirect(4);
temp.order(ByteOrder.nativeOrder());
IntBuffer texBuf = temp.asIntBuffer();
texBuf.put(textureID);
texBuf.flip();
GL.glDeleteTextures(texBuf);
if (getLastBind() == this)
{
GL.glDisable(SGL.GL_TEXTURE_2D);
setLastBind(null);
}
released = true;
loader.removeFromCache(this);
}
@Override
public int getTextureID()
{
return textureID;
}
@Override
public byte[] getTextureData()
{
if (isLoaded())
{
ByteBuffer buffer = BufferUtils.createByteBuffer((hasAlpha() ? 4 : 3) * texWidth * texHeight);
bind();
GL.glGetTexImage(SGL.GL_TEXTURE_2D, 0, hasAlpha() ? SGL.GL_RGBA : SGL.GL_RGB, SGL.GL_UNSIGNED_BYTE, buffer);
byte[] data = new byte[buffer.limit()];
buffer.get(data);
buffer.clear();
return data;
}
else
{
return null;
}
}
@Override
public void setTextureFilter(int textureFilter)
{
bind();
GL.glTexParameteri(target, SGL.GL_TEXTURE_MIN_FILTER, textureFilter);
GL.glTexParameteri(target, SGL.GL_TEXTURE_MAG_FILTER, textureFilter);
}
void loaded()
{
loaded = true;
}
private Image cachedImaged;
/** Return a slick image instance for this texture.
* @return null if this texture is not yet loaded (isLoaded()) otherwise
* the same Image instance every time.
*/
public Image getCachedImage()
{
if (!this.isLoaded())
{
return null;
}
if (cachedImaged == null)
{
cachedImaged = new Image(this);
}
return cachedImaged;
}
}