Home | History | Annotate | Download | only in graphics
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.graphics;
     18 
     19 import java.util.HashMap;
     20 import java.util.Map;
     21 
     22 import com.badlogic.gdx.Application;
     23 import com.badlogic.gdx.Gdx;
     24 import com.badlogic.gdx.assets.AssetLoaderParameters.LoadedCallback;
     25 import com.badlogic.gdx.assets.AssetManager;
     26 import com.badlogic.gdx.assets.loaders.AssetLoader;
     27 import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter;
     28 import com.badlogic.gdx.files.FileHandle;
     29 import com.badlogic.gdx.graphics.Pixmap.Format;
     30 import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
     31 import com.badlogic.gdx.utils.Array;
     32 import com.badlogic.gdx.utils.GdxRuntimeException;
     33 
     34 /** A Texture wraps a standard OpenGL ES texture.
     35  * <p>
     36  * A Texture can be managed. If the OpenGL context is lost all managed textures get invalidated. This happens when a user switches
     37  * to another application or receives an incoming call. Managed textures get reloaded automatically.
     38  * <p>
     39  * A Texture has to be bound via the {@link Texture#bind()} method in order for it to be applied to geometry. The texture will be
     40  * bound to the currently active texture unit specified via {@link GL20#glActiveTexture(int)}.
     41  * <p>
     42  * You can draw {@link Pixmap}s to a texture at any time. The changes will be automatically uploaded to texture memory. This is of
     43  * course not extremely fast so use it with care. It also only works with unmanaged textures.
     44  * <p>
     45  * A Texture must be disposed when it is no longer used
     46  * @author badlogicgames (at) gmail.com */
     47 public class Texture extends GLTexture {
     48 	private static AssetManager assetManager;
     49 	final static Map<Application, Array<Texture>> managedTextures = new HashMap<Application, Array<Texture>>();
     50 
     51 	public enum TextureFilter {
     52 		Nearest(GL20.GL_NEAREST), Linear(GL20.GL_LINEAR), MipMap(GL20.GL_LINEAR_MIPMAP_LINEAR), MipMapNearestNearest(
     53 			GL20.GL_NEAREST_MIPMAP_NEAREST), MipMapLinearNearest(GL20.GL_LINEAR_MIPMAP_NEAREST), MipMapNearestLinear(
     54 			GL20.GL_NEAREST_MIPMAP_LINEAR), MipMapLinearLinear(GL20.GL_LINEAR_MIPMAP_LINEAR);
     55 
     56 		final int glEnum;
     57 
     58 		TextureFilter (int glEnum) {
     59 			this.glEnum = glEnum;
     60 		}
     61 
     62 		public boolean isMipMap () {
     63 			return glEnum != GL20.GL_NEAREST && glEnum != GL20.GL_LINEAR;
     64 		}
     65 
     66 		public int getGLEnum () {
     67 			return glEnum;
     68 		}
     69 	}
     70 
     71 	public enum TextureWrap {
     72 		MirroredRepeat(GL20.GL_MIRRORED_REPEAT), ClampToEdge(GL20.GL_CLAMP_TO_EDGE), Repeat(GL20.GL_REPEAT);
     73 
     74 		final int glEnum;
     75 
     76 		TextureWrap (int glEnum) {
     77 			this.glEnum = glEnum;
     78 		}
     79 
     80 		public int getGLEnum () {
     81 			return glEnum;
     82 		}
     83 	}
     84 
     85 	TextureData data;
     86 
     87 	public Texture (String internalPath) {
     88 		this(Gdx.files.internal(internalPath));
     89 	}
     90 
     91 	public Texture (FileHandle file) {
     92 		this(file, null, false);
     93 	}
     94 
     95 	public Texture (FileHandle file, boolean useMipMaps) {
     96 		this(file, null, useMipMaps);
     97 	}
     98 
     99 	public Texture (FileHandle file, Format format, boolean useMipMaps) {
    100 		this(TextureData.Factory.loadFromFile(file, format, useMipMaps));
    101 	}
    102 
    103 	public Texture (Pixmap pixmap) {
    104 		this(new PixmapTextureData(pixmap, null, false, false));
    105 	}
    106 
    107 	public Texture (Pixmap pixmap, boolean useMipMaps) {
    108 		this(new PixmapTextureData(pixmap, null, useMipMaps, false));
    109 	}
    110 
    111 	public Texture (Pixmap pixmap, Format format, boolean useMipMaps) {
    112 		this(new PixmapTextureData(pixmap, format, useMipMaps, false));
    113 	}
    114 
    115 	public Texture (int width, int height, Format format) {
    116 		this(new PixmapTextureData(new Pixmap(width, height, format), null, false, true));
    117 	}
    118 
    119 	public Texture (TextureData data) {
    120 		this(GL20.GL_TEXTURE_2D, Gdx.gl.glGenTexture(), data);
    121 	}
    122 
    123 	protected Texture (int glTarget, int glHandle, TextureData data) {
    124 		super(glTarget, glHandle);
    125 		load(data);
    126 		if (data.isManaged()) addManagedTexture(Gdx.app, this);
    127 	}
    128 
    129 	public void load (TextureData data) {
    130 		if (this.data != null && data.isManaged() != this.data.isManaged())
    131 			throw new GdxRuntimeException("New data must have the same managed status as the old data");
    132 		this.data = data;
    133 
    134 		if (!data.isPrepared()) data.prepare();
    135 
    136 		bind();
    137 		uploadImageData(GL20.GL_TEXTURE_2D, data);
    138 
    139 		setFilter(minFilter, magFilter);
    140 		setWrap(uWrap, vWrap);
    141 		Gdx.gl.glBindTexture(glTarget, 0);
    142 	}
    143 
    144 	/** Used internally to reload after context loss. Creates a new GL handle then calls {@link #load(TextureData)}. Use this only
    145 	 * if you know what you do! */
    146 	@Override
    147 	protected void reload () {
    148 		if (!isManaged()) throw new GdxRuntimeException("Tried to reload unmanaged Texture");
    149 		glHandle = Gdx.gl.glGenTexture();
    150 		load(data);
    151 	}
    152 
    153 	/** Draws the given {@link Pixmap} to the texture at position x, y. No clipping is performed so you have to make sure that you
    154 	 * draw only inside the texture region. Note that this will only draw to mipmap level 0!
    155 	 *
    156 	 * @param pixmap The Pixmap
    157 	 * @param x The x coordinate in pixels
    158 	 * @param y The y coordinate in pixels */
    159 	public void draw (Pixmap pixmap, int x, int y) {
    160 		if (data.isManaged()) throw new GdxRuntimeException("can't draw to a managed texture");
    161 
    162 		bind();
    163 		Gdx.gl.glTexSubImage2D(glTarget, 0, x, y, pixmap.getWidth(), pixmap.getHeight(), pixmap.getGLFormat(), pixmap.getGLType(),
    164 			pixmap.getPixels());
    165 	}
    166 
    167 	@Override
    168 	public int getWidth () {
    169 		return data.getWidth();
    170 	}
    171 
    172 	@Override
    173 	public int getHeight () {
    174 		return data.getHeight();
    175 	}
    176 
    177 	@Override
    178 	public int getDepth () {
    179 		return 0;
    180 	}
    181 
    182 	public TextureData getTextureData () {
    183 		return data;
    184 	}
    185 
    186 	/** @return whether this texture is managed or not. */
    187 	public boolean isManaged () {
    188 		return data.isManaged();
    189 	}
    190 
    191 	/** Disposes all resources associated with the texture */
    192 	public void dispose () {
    193 		// this is a hack. reason: we have to set the glHandle to 0 for textures that are
    194 		// reloaded through the asset manager as we first remove (and thus dispose) the texture
    195 		// and then reload it. the glHandle is set to 0 in invalidateAllTextures prior to
    196 		// removal from the asset manager.
    197 		if (glHandle == 0) return;
    198 		delete();
    199 		if (data.isManaged()) if (managedTextures.get(Gdx.app) != null) managedTextures.get(Gdx.app).removeValue(this, true);
    200 	}
    201 
    202 	private static void addManagedTexture (Application app, Texture texture) {
    203 		Array<Texture> managedTextureArray = managedTextures.get(app);
    204 		if (managedTextureArray == null) managedTextureArray = new Array<Texture>();
    205 		managedTextureArray.add(texture);
    206 		managedTextures.put(app, managedTextureArray);
    207 	}
    208 
    209 	/** Clears all managed textures. This is an internal method. Do not use it! */
    210 	public static void clearAllTextures (Application app) {
    211 		managedTextures.remove(app);
    212 	}
    213 
    214 	/** Invalidate all managed textures. This is an internal method. Do not use it! */
    215 	public static void invalidateAllTextures (Application app) {
    216 		Array<Texture> managedTextureArray = managedTextures.get(app);
    217 		if (managedTextureArray == null) return;
    218 
    219 		if (assetManager == null) {
    220 			for (int i = 0; i < managedTextureArray.size; i++) {
    221 				Texture texture = managedTextureArray.get(i);
    222 				texture.reload();
    223 			}
    224 		} else {
    225 			// first we have to make sure the AssetManager isn't loading anything anymore,
    226 			// otherwise the ref counting trick below wouldn't work (when a texture is
    227 			// currently on the task stack of the manager.)
    228 			assetManager.finishLoading();
    229 
    230 			// next we go through each texture and reload either directly or via the
    231 			// asset manager.
    232 			Array<Texture> textures = new Array<Texture>(managedTextureArray);
    233 			for (Texture texture : textures) {
    234 				String fileName = assetManager.getAssetFileName(texture);
    235 				if (fileName == null) {
    236 					texture.reload();
    237 				} else {
    238 					// get the ref count of the texture, then set it to 0 so we
    239 					// can actually remove it from the assetmanager. Also set the
    240 					// handle to zero, otherwise we might accidentially dispose
    241 					// already reloaded textures.
    242 					final int refCount = assetManager.getReferenceCount(fileName);
    243 					assetManager.setReferenceCount(fileName, 0);
    244 					texture.glHandle = 0;
    245 
    246 					// create the parameters, passing the reference to the texture as
    247 					// well as a callback that sets the ref count.
    248 					TextureParameter params = new TextureParameter();
    249 					params.textureData = texture.getTextureData();
    250 					params.minFilter = texture.getMinFilter();
    251 					params.magFilter = texture.getMagFilter();
    252 					params.wrapU = texture.getUWrap();
    253 					params.wrapV = texture.getVWrap();
    254 					params.genMipMaps = texture.data.useMipMaps(); // not sure about this?
    255 					params.texture = texture; // special parameter which will ensure that the references stay the same.
    256 					params.loadedCallback = new LoadedCallback() {
    257 						@Override
    258 						public void finishedLoading (AssetManager assetManager, String fileName, Class type) {
    259 							assetManager.setReferenceCount(fileName, refCount);
    260 						}
    261 					};
    262 
    263 					// unload the texture, create a new gl handle then reload it.
    264 					assetManager.unload(fileName);
    265 					texture.glHandle = Gdx.gl.glGenTexture();
    266 					assetManager.load(fileName, Texture.class, params);
    267 				}
    268 			}
    269 			managedTextureArray.clear();
    270 			managedTextureArray.addAll(textures);
    271 		}
    272 	}
    273 
    274 	/** Sets the {@link AssetManager}. When the context is lost, textures managed by the asset manager are reloaded by the manager
    275 	 * on a separate thread (provided that a suitable {@link AssetLoader} is registered with the manager). Textures not managed by
    276 	 * the AssetManager are reloaded via the usual means on the rendering thread.
    277 	 * @param manager the asset manager. */
    278 	public static void setAssetManager (AssetManager manager) {
    279 		Texture.assetManager = manager;
    280 	}
    281 
    282 	public static String getManagedStatus () {
    283 		StringBuilder builder = new StringBuilder();
    284 		builder.append("Managed textures/app: { ");
    285 		for (Application app : managedTextures.keySet()) {
    286 			builder.append(managedTextures.get(app).size);
    287 			builder.append(" ");
    288 		}
    289 		builder.append("}");
    290 		return builder.toString();
    291 	}
    292 
    293 	/** @return the number of managed textures currently loaded */
    294 	public static int getNumManagedTextures () {
    295 		return managedTextures.get(Gdx.app).size;
    296 	}
    297 }
    298