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