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
17package com.badlogic.gdx.graphics;
18
19import java.util.HashMap;
20import java.util.Map;
21
22import com.badlogic.gdx.Application;
23import com.badlogic.gdx.Gdx;
24import com.badlogic.gdx.assets.AssetLoaderParameters.LoadedCallback;
25import com.badlogic.gdx.assets.AssetManager;
26import com.badlogic.gdx.assets.loaders.AssetLoader;
27import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter;
28import com.badlogic.gdx.files.FileHandle;
29import com.badlogic.gdx.graphics.Pixmap.Format;
30import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
31import com.badlogic.gdx.utils.Array;
32import 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@gmail.com */
47public 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