Line data Source code
1 : /* Copyright (C) 2022 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 "graphics/Texture.h"
22 : #include "lib/file/vfs/vfs.h"
23 : #include "lib/tex/tex.h"
24 : #include "renderer/backend/IDevice.h"
25 : #include "renderer/backend/IDeviceCommandContext.h"
26 : #include "renderer/backend/ITexture.h"
27 :
28 : #include <memory>
29 :
30 : class CTextureProperties;
31 : class CTextureManagerImpl;
32 :
33 : /**
34 : * Texture manager with asynchronous loading and automatic DDS conversion/compression.
35 : *
36 : * Input textures can be any format. They will be converted to DDS using settings defined
37 : * in files named "texture.xml", in the same directory as the texture and in its parent
38 : * directories. See CTextureConverter for the XML syntax. The DDS file will be cached
39 : * for faster loading in the future.
40 : *
41 : * Typically the graphics code will initialise many textures at the start of the game,
42 : * mostly for off-screen objects, by calling CreateTexture().
43 : * Loading texture data may be very slow (especially if it needs to be converted
44 : * to DDS), and we don't want the game to become unresponsive.
45 : * CreateTexture therefore returns an object immediately, without loading the
46 : * texture. If the object is never used then the data will never be loaded.
47 : *
48 : * Typically, the renderer will call CTexture::Bind() when it wants to use the
49 : * texture. This will trigger the loading of the texture data. If it can be loaded
50 : * quickly (i.e. there is already a cached DDS version), then it will be loaded before
51 : * the function returns, and the texture can be rendered as normal.
52 : *
53 : * If loading will take a long time, then Bind() binds a default placeholder texture
54 : * and starts loading the texture in the background. It will use the correct texture
55 : * when the renderer next calls Bind() after the load has finished.
56 : *
57 : * It is also possible to prefetch textures which are not being rendered yet, but
58 : * are expected to be rendered soon (e.g. for off-screen terrain tiles).
59 : * These will be loaded in the background, when there are no higher-priority textures
60 : * to load.
61 : *
62 : * The same texture file can be safely loaded multiple times with different backend parameters
63 : * (but this should be avoided whenever possible, as it wastes VRAM).
64 : *
65 : * For release packages, DDS files can be precached by appending ".dds" to their name,
66 : * which will be used instead of doing runtime conversion. This means most players should
67 : * never experience the slow asynchronous conversion behaviour.
68 : * These cache files will typically be packed into an archive for faster loading;
69 : * if no archive cache is available then the source file will be converted and stored
70 : * as a loose cache file on the user's disk.
71 : */
72 : class CTextureManager
73 : {
74 : NONCOPYABLE(CTextureManager);
75 :
76 : public:
77 : /**
78 : * Construct texture manager. vfs must be the VFS instance used for all textures
79 : * loaded from this object.
80 : * highQuality is slower and intended for batch-conversion modes.
81 : */
82 : CTextureManager(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device);
83 :
84 : ~CTextureManager();
85 :
86 : /**
87 : * Create a texture with the given properties.
88 : * The texture data will not be loaded immediately.
89 : */
90 : CTexturePtr CreateTexture(const CTextureProperties& props);
91 :
92 : /**
93 : * Wraps a backend texture.
94 : */
95 : CTexturePtr WrapBackendTexture(
96 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture);
97 :
98 : /**
99 : * Returns a magenta texture. Use this for highlighting errors
100 : * (e.g. missing terrain textures).
101 : */
102 : const CTexturePtr& GetErrorTexture();
103 :
104 : /**
105 : * Returns a single color RGBA texture with CColor(1.0f, 1.0f, 1.0f, 1.0f).
106 : */
107 : const CTexturePtr& GetWhiteTexture();
108 :
109 : /**
110 : * Returns a single color RGBA texture with CColor(0.0f, 0.0f, 0.0f, 0.0f).
111 : */
112 : const CTexturePtr& GetTransparentTexture();
113 :
114 : /**
115 : * Returns a white RGBA texture with alpha gradient.
116 : */
117 : const CTexturePtr& GetAlphaGradientTexture();
118 :
119 : /**
120 : * Returns a single color RGBA texture cube with CColor(0.0f, 0.0f, 0.0f, 1.0f).
121 : */
122 : const CTexturePtr& GetBlackTextureCube();
123 :
124 : /**
125 : * Work on asynchronous texture loading operations, if any.
126 : * Returns true if it did any work.
127 : * The caller should typically loop this per frame until it returns
128 : * false or exceeds the allocated time for this frame.
129 : */
130 : bool MakeProgress();
131 :
132 : /**
133 : * Work on asynchronous texture uploading operations, if any.
134 : * Returns true if it did any work. Mostly the same as MakeProgress.
135 : */
136 : bool MakeUploadProgress(Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
137 :
138 : /**
139 : * Synchronously converts and compresses and saves the texture,
140 : * and returns the output path (minus a "cache/" prefix). This
141 : * is intended for pre-caching textures in release archives.
142 : * @return true on success
143 : */
144 : bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath);
145 :
146 : /**
147 : * Returns true if the given texture exists.
148 : * This tests both for the original and converted filename.
149 : */
150 : bool TextureExists(const VfsPath& path) const;
151 :
152 : /**
153 : * Returns total number of bytes uploaded for all current texture.
154 : */
155 : size_t GetBytesUploaded() const;
156 :
157 : /**
158 : * Should be called on any quality or anisotropic change.
159 : */
160 : void OnQualityChanged();
161 :
162 : private:
163 : CTextureManagerImpl* m;
164 : };
165 :
166 : /**
167 : * Represents the filename and GL parameters of a texture,
168 : * for passing to CTextureManager::CreateTexture.
169 : */
170 198 : class CTextureProperties
171 : {
172 : friend class CTextureManagerImpl;
173 : friend struct TextureCacheCmp;
174 : friend struct TPequal_to;
175 : friend struct TPhash;
176 :
177 : public:
178 : /**
179 : * Use the given texture name, and default GL parameters.
180 : */
181 61 : explicit CTextureProperties(const VfsPath& path)
182 61 : : m_Path(path)
183 : {
184 61 : }
185 :
186 5 : CTextureProperties(
187 : const VfsPath& path, const Renderer::Backend::Format formatOverride)
188 5 : : m_Path(path), m_FormatOverride(formatOverride)
189 : {
190 5 : }
191 :
192 : /**
193 : * Set sampler address mode.
194 : */
195 0 : void SetAddressMode(const Renderer::Backend::Sampler::AddressMode addressMode)
196 : {
197 0 : m_AddressModeU = m_AddressModeV = addressMode;
198 0 : }
199 :
200 : /**
201 : * Set sampler address mode separately for different coordinates.
202 : */
203 0 : void SetAddressMode(
204 : const Renderer::Backend::Sampler::AddressMode addressModeU,
205 : const Renderer::Backend::Sampler::AddressMode addressModeV)
206 : {
207 0 : m_AddressModeU = addressModeU;
208 0 : m_AddressModeV = addressModeV;
209 0 : }
210 :
211 : /**
212 : * The value of max anisotropy is set by options. Though it might make sense
213 : * to add an override.
214 : */
215 0 : void SetAnisotropicFilter(const bool enabled) { m_AnisotropicFilterEnabled = enabled; }
216 :
217 : // TODO: rather than this static definition of texture properties
218 : // (especially anisotropy), maybe we want something that can be more
219 : // easily tweaked in an Options menu? e.g. the caller just specifies
220 : // "terrain texture mode" and we combine it with the user's options.
221 : // That'd let us dynamically change texture properties easily.
222 : //
223 : // enum EQualityMode
224 : // {
225 : // NONE,
226 : // TERRAIN,
227 : // MODEL,
228 : // GUI
229 : // }
230 : // void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
231 : //
232 : // or something a bit like that.
233 :
234 5 : void SetIgnoreQuality(bool ignore) { m_IgnoreQuality = ignore; }
235 :
236 : private:
237 : // Must update TPhash, TPequal_to when changing these fields
238 : VfsPath m_Path;
239 :
240 : Renderer::Backend::Sampler::AddressMode m_AddressModeU =
241 : Renderer::Backend::Sampler::AddressMode::REPEAT;
242 : Renderer::Backend::Sampler::AddressMode m_AddressModeV =
243 : Renderer::Backend::Sampler::AddressMode::REPEAT;
244 : bool m_AnisotropicFilterEnabled = false;
245 : Renderer::Backend::Format m_FormatOverride =
246 : Renderer::Backend::Format::UNDEFINED;
247 : bool m_IgnoreQuality = false;
248 : };
249 :
250 : /**
251 : * Represents a texture object.
252 : * The texture data may or may not have been loaded yet.
253 : * Before it has been loaded, all operations will act on a default
254 : * 1x1-pixel grey texture instead.
255 : */
256 66 : class CTexture
257 : {
258 : NONCOPYABLE(CTexture);
259 : public:
260 : ~CTexture();
261 :
262 : /**
263 : * Returns the width (in pixels) of the current texture.
264 : */
265 : size_t GetWidth() const;
266 :
267 : /**
268 : * Returns the height (in pixels) of the current texture.
269 : */
270 : size_t GetHeight() const;
271 :
272 : /**
273 : * Returns whether the current texture has an alpha channel.
274 : */
275 : bool HasAlpha() const;
276 :
277 : /**
278 : * Returns the ARGB value of the lowest mipmap level (i.e. the
279 : * average of the whole texture).
280 : * Returns 0 if the texture has no mipmaps.
281 : */
282 : u32 GetBaseColor() const;
283 :
284 : /**
285 : * Returns total number of bytes uploaded for this texture.
286 : */
287 : size_t GetUploadedSize() const;
288 :
289 : /**
290 : * Uploads a texture data to a backend texture if successfully loaded.
291 : * If the texture data hasn't been loaded yet, this may wait a short while to
292 : * load it. If loading takes too long then it will return sooner and the data will
293 : * be loaded in a background thread, so this does not guarantee the texture really
294 : * will be uploaded.
295 : */
296 : void UploadBackendTextureIfNeeded(
297 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
298 :
299 : /**
300 : * Returns a backend texture if successfully uploaded, else fallback.
301 : */
302 : Renderer::Backend::ITexture* GetBackendTexture();
303 : const Renderer::Backend::ITexture* GetBackendTexture() const;
304 :
305 : /**
306 : * Attempt to load the texture data quickly, as with
307 : * GetUploadedBackendTextureIfNeeded(). Returns whether the texture data is
308 : * currently loaded (but not uploaded).
309 : */
310 : bool TryLoad();
311 :
312 : /**
313 : * Returns whether the texture data is currently loaded.
314 : */
315 15 : bool IsLoaded() const { return m_State == LOADED; }
316 :
317 : /**
318 : * Returns whether the texture data is currently uploaded.
319 : */
320 17 : bool IsUploaded() const { return m_State == UPLOADED; }
321 :
322 : /**
323 : * Activate the prefetching optimisation for this texture.
324 : * Use this if it is likely the texture will be needed in the near future.
325 : * It will be loaded in the background so that it is likely to be ready when
326 : * it is used by Bind().
327 : */
328 : void Prefetch();
329 :
330 : private:
331 : friend class CTextureManagerImpl;
332 : friend class CPredefinedTexture;
333 : friend struct TextureCacheCmp;
334 : friend struct TPequal_to;
335 : friend struct TPhash;
336 :
337 : // Only the texture manager can create these
338 : explicit CTexture(
339 : std::unique_ptr<Renderer::Backend::ITexture> texture,
340 : Renderer::Backend::ITexture* fallback,
341 : const CTextureProperties& props, CTextureManagerImpl* textureManager);
342 :
343 : void ResetBackendTexture(
344 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
345 : Renderer::Backend::ITexture* fallbackBackendTexture);
346 :
347 : const CTextureProperties m_Properties;
348 :
349 : std::unique_ptr<Renderer::Backend::ITexture> m_BackendTexture;
350 : // It's possible to m_FallbackBackendTexture references m_BackendTexture.
351 : Renderer::Backend::ITexture* m_FallbackBackendTexture = nullptr;
352 : u32 m_BaseColor;
353 : std::unique_ptr<Tex> m_TextureData;
354 : size_t m_UploadedSize = 0;
355 : uint32_t m_BaseLevelOffset = 0;
356 :
357 : enum
358 : {
359 : UNLOADED, // loading has not started
360 : PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
361 : PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
362 : PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
363 : HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
364 : HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
365 : LOADED, // loading texture data has completed (successfully or not)
366 : UPLOADED // uploading to backend has completed (successfully or not)
367 : } m_State;
368 :
369 : CTextureManagerImpl* m_TextureManager;
370 :
371 : // Self-reference to let us recover the CTexturePtr for this object.
372 : // (weak pointer to avoid cycles)
373 : std::weak_ptr<CTexture> m_Self;
374 : };
375 :
376 : #endif // INCLUDED_TEXTUREMANAGER
|