1 /* Copyright (C) 2018 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #ifndef INCLUDED_TEXTUREMANAGER
19 #define INCLUDED_TEXTUREMANAGER
20 
21 #include "Texture.h"
22 
23 #include <memory>
24 
25 #include "lib/ogl.h"
26 #include "lib/file/vfs/vfs.h"
27 #include "lib/res/handle.h"
28 
29 class CTextureProperties;
30 class CTextureManagerImpl;
31 
32 /**
33  * Texture manager with asynchronous loading and automatic DDS conversion/compression.
34  *
35  * Input textures can be any format. They will be converted to DDS using settings defined
36  * in files named "texture.xml", in the same directory as the texture and in its parent
37  * directories. See CTextureConverter for the XML syntax. The DDS file will be cached
38  * for faster loading in the future.
39  *
40  * Typically the graphics code will initialise many textures at the start of the game,
41  * mostly for off-screen objects, by calling CreateTexture().
42  * Loading texture data may be very slow (especially if it needs to be converted
43  * to DDS), and we don't want the game to become unresponsive.
44  * CreateTexture therefore returns an object immediately, without loading the
45  * texture. If the object is never used then the data will never be loaded.
46  *
47  * Typically, the renderer will call CTexture::Bind() when it wants to use the
48  * texture. This will trigger the loading of the texture data. If it can be loaded
49  * quickly (i.e. there is already a cached DDS version), then it will be loaded before
50  * the function returns, and the texture can be rendered as normal.
51  *
52  * If loading will take a long time, then Bind() binds a default placeholder texture
53  * and starts loading the texture in the background. It will use the correct texture
54  * when the renderer next calls Bind() after the load has finished.
55  *
56  * It is also possible to prefetch textures which are not being rendered yet, but
57  * are expected to be rendered soon (e.g. for off-screen terrain tiles).
58  * These will be loaded in the background, when there are no higher-priority textures
59  * to load.
60  *
61  * The same texture file can be safely loaded multiple times with different GL parameters
62  * (but this should be avoided whenever possible, as it wastes VRAM).
63  *
64  * For release packages, DDS files can be precached by appending ".dds" to their name,
65  * which will be used instead of doing runtime conversion. This means most players should
66  * never experience the slow asynchronous conversion behaviour.
67  * These cache files will typically be packed into an archive for faster loading;
68  * if no archive cache is available then the source file will be converted and stored
69  * as a loose cache file on the user's disk.
70  */
71 class CTextureManager
72 {
73 	NONCOPYABLE(CTextureManager);
74 
75 public:
76 	/**
77 	 * Construct texture manager. vfs must be the VFS instance used for all textures
78 	 * loaded from this object.
79 	 * highQuality is slower and intended for batch-conversion modes.
80 	 * disableGL is intended for tests, and will disable all GL uploads.
81 	 */
82 	CTextureManager(PIVFS vfs, bool highQuality, bool disableGL);
83 
84 	~CTextureManager();
85 
86 	/**
87 	 * Create a texture with the given GL properties.
88 	 * The texture data will not be loaded immediately.
89 	 */
90 	CTexturePtr CreateTexture(const CTextureProperties& props);
91 
92 	/**
93 	 * Returns a magenta texture. Use this for highlighting errors
94 	 * (e.g. missing terrain textures).
95 	 */
96 	CTexturePtr GetErrorTexture();
97 
98 	/**
99 	 * Work on asynchronous texture loading operations, if any.
100 	 * Returns true if it did any work.
101 	 * The caller should typically loop this per frame until it returns
102 	 * false or exceeds the allocated time for this frame.
103 	 */
104 	bool MakeProgress();
105 
106 	/**
107 	 * Synchronously converts and compresses and saves the texture,
108 	 * and returns the output path (minus a "cache/" prefix). This
109 	 * is intended for pre-caching textures in release archives.
110 	 * @return true on success
111 	 */
112 	bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath);
113 
114 	/**
115 	 * Returns true if the given texture exists.
116 	 * This tests both for the original and converted filename.
117 	 */
118 	bool TextureExists(const VfsPath& path) const;
119 
120 	/**
121 	 * Returns total number of bytes uploaded for all current texture.
122 	 */
123 	size_t GetBytesUploaded() const;
124 
125 private:
126 	CTextureManagerImpl* m;
127 };
128 
129 /**
130  * Represents the filename and GL parameters of a texture,
131  * for passing to CTextureManager::CreateTexture.
132  */
133 class CTextureProperties
134 {
135 	friend class CTextureManagerImpl;
136 	friend struct TextureCacheCmp;
137 	friend struct TPequal_to;
138 	friend struct TPhash;
139 
140 public:
141 	/**
142 	 * Use the given texture name, and default GL parameters.
143 	 */
CTextureProperties(const VfsPath & path)144 	explicit CTextureProperties(const VfsPath& path) :
145 		m_Path(path), m_Filter(GL_LINEAR_MIPMAP_LINEAR),
146 		m_WrapS(GL_REPEAT), m_WrapT(GL_REPEAT), m_Aniso(1.0f), m_Format(0)
147 	{
148 	}
149 
150 	/**
151 	 * Set min/mag filter mode (typically GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST, etc).
152 	 */
SetFilter(GLint filter)153 	void SetFilter(GLint filter) { m_Filter = filter; }
154 
155 	/**
156 	 * Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc).
157 	 */
SetWrap(GLint wrap)158 	void SetWrap(GLint wrap) { m_WrapS = wrap; m_WrapT = wrap; }
159 
160 	/**
161 	 * Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc),
162 	 * separately for S and T.
163 	 */
SetWrap(GLint wrap_s,GLint wrap_t)164 	void SetWrap(GLint wrap_s, GLint wrap_t) { m_WrapS = wrap_s; m_WrapT = wrap_t; }
165 
166 	/**
167 	 * Set maximum anisotropy value. Must be >= 1.0. Should be a power of 2.
168 	 */
SetMaxAnisotropy(float aniso)169 	void SetMaxAnisotropy(float aniso) { m_Aniso = aniso; }
170 
171 	/**
172 	 * Set GL texture upload format, to override the default.
173 	 * Typically GL_ALPHA or GL_LUMINANCE for 8-bit textures.
174 	 */
SetFormatOverride(GLenum format)175 	void SetFormatOverride(GLenum format) { m_Format = format; }
176 
177 	// TODO: rather than this static definition of texture properties
178 	// (especially anisotropy), maybe we want something that can be more
179 	// easily tweaked in an Options menu? e.g. the caller just specifies
180 	// "terrain texture mode" and we combine it with the user's options.
181 	// That'd let us dynamically change texture properties easily.
182 	//
183 	// enum EQualityMode
184 	// {
185 	//   NONE,
186 	//   TERRAIN,
187 	//   MODEL,
188 	//   GUI
189 	// }
190 	// void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
191 	//
192 	// or something a bit like that.
193 
194 private:
195 	// Must update TPhash, TPequal_to when changing these fields
196 	VfsPath m_Path;
197 	GLint m_Filter;
198 	GLint m_WrapS;
199 	GLint m_WrapT;
200 	float m_Aniso;
201 	GLenum m_Format;
202 };
203 
204 /**
205  * Represents a texture object.
206  * The texture data may or may not have been loaded yet.
207  * Before it has been loaded, all operations will act on a default
208  * 1x1-pixel grey texture instead.
209  */
210 class CTexture
211 {
212 	friend class CTextureManagerImpl;
213 	friend struct TextureCacheCmp;
214 	friend struct TPequal_to;
215 	friend struct TPhash;
216 
217 	// Only the texture manager can create these
218 	explicit CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager);
219 
220 	NONCOPYABLE(CTexture);
221 
222 public:
223 
224 	~CTexture();
225 
226 	/**
227 	 * Returns the width (in pixels) of the current texture.
228 	 */
229 	size_t GetWidth() const;
230 
231 	/**
232 	 * Returns the height (in pixels) of the current texture.
233 	 */
234 	size_t GetHeight() const;
235 
236 	/**
237 	 * Returns whether the current texture has an alpha channel.
238 	 */
239 	bool HasAlpha() const;
240 
241 	/**
242 	 * Returns the ARGB value of the lowest mipmap level (i.e. the
243 	 * average of the whole texture).
244 	 * Returns 0 if the texture has no mipmaps.
245 	 */
246 	u32 GetBaseColor() const;
247 
248 	/**
249 	 * Returns total number of bytes uploaded for this texture.
250 	 */
251 	size_t GetUploadedSize() const;
252 
253 	/**
254 	 * Bind the texture to the given GL texture unit.
255 	 * If the texture data hasn't been loaded yet, this may wait a short while to
256 	 * load it. If loading takes too long then it will return sooner and the data will
257 	 * be loaded in a background thread, so this does not guarantee the texture really
258 	 * will be loaded.
259 	 */
260 	void Bind(size_t unit = 0);
261 
262 	/**
263 	 * Returns a ogl_tex handle, for later binding. See comments from Bind().
264 	 */
265 	Handle GetHandle();
266 
267 	/**
268 	 * Attempt to load the texture data quickly, as with Bind().
269 	 * Returns whether the texture data is currently loaded.
270 	 */
271 	bool TryLoad();
272 
273 	/**
274 	 * Returns whether the texture data is currently loaded.
275 	 */
276 	bool IsLoaded();
277 
278 	/**
279 	 * Activate the prefetching optimisation for this texture.
280 	 * Use this if it is likely the texture will be needed in the near future.
281 	 * It will be loaded in the background so that it is likely to be ready when
282 	 * it is used by Bind().
283 	 */
284 	void Prefetch();
285 
286 private:
287 	/**
288 	 * Replace the Handle stored by this object.
289 	 * If takeOwnership is true, it will not increment the Handle's reference count.
290 	 */
291 	void SetHandle(Handle handle, bool takeOwnership = false);
292 
293 	const CTextureProperties m_Properties;
294 
295 	Handle m_Handle;
296 	u32 m_BaseColor;
297 
298 	enum {
299 		UNLOADED, // loading has not started
300 		PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
301 		PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
302 		PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
303 		HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
304 		HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
305 		LOADED // loading has completed (successfully or not)
306 	} m_State;
307 
308 	CTextureManagerImpl* m_TextureManager;
309 
310 	// Self-reference to let us recover the CTexturePtr for this object.
311 	// (weak pointer to avoid cycles)
312 	std::weak_ptr<CTexture> m_Self;
313 };
314 
315 std::size_t hash_value(const CTexturePtr& v);
316 std::size_t hash_value(const CTextureProperties& v);
317 
318 #endif // INCLUDED_TEXTUREMANAGER
319