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 : #include "precompiled.h"
19 :
20 : #include "TextureManager.h"
21 :
22 : #include "graphics/Color.h"
23 : #include "graphics/TextureConverter.h"
24 : #include "lib/allocators/shared_ptr.h"
25 : #include "lib/bits.h"
26 : #include "lib/file/vfs/vfs_tree.h"
27 : #include "lib/hash.h"
28 : #include "lib/timer.h"
29 : #include "maths/MathUtil.h"
30 : #include "maths/MD5.h"
31 : #include "ps/CacheLoader.h"
32 : #include "ps/CLogger.h"
33 : #include "ps/ConfigDB.h"
34 : #include "ps/Filesystem.h"
35 : #include "ps/Profile.h"
36 : #include "ps/Util.h"
37 : #include "renderer/backend/IDevice.h"
38 : #include "renderer/Renderer.h"
39 :
40 : #include <algorithm>
41 : #include <boost/filesystem.hpp>
42 : #include <iomanip>
43 : #include <set>
44 : #include <sstream>
45 : #include <unordered_map>
46 : #include <unordered_set>
47 :
48 : namespace
49 : {
50 :
51 6 : Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(Tex& textureData, const bool hasS3TC)
52 : {
53 6 : const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0;
54 6 : const bool grey = (textureData.m_Flags & TEX_GREY) != 0;
55 6 : const size_t dxt = textureData.m_Flags & TEX_DXT;
56 :
57 : // Some backends don't support BGR as an internal format (like GLES).
58 : // TODO: add a check that the format is internally supported.
59 6 : if ((textureData.m_Flags & TEX_BGR) != 0)
60 : {
61 0 : LOGWARNING("Using slow path to convert BGR texture.");
62 0 : textureData.transform_to(textureData.m_Flags & ~TEX_BGR);
63 : }
64 :
65 6 : if (dxt)
66 : {
67 6 : if (hasS3TC)
68 : {
69 6 : switch (dxt)
70 : {
71 0 : case DXT1A:
72 0 : return Renderer::Backend::Format::BC1_RGBA_UNORM;
73 6 : case 1:
74 6 : return Renderer::Backend::Format::BC1_RGB_UNORM;
75 0 : case 3:
76 0 : return Renderer::Backend::Format::BC2_UNORM;
77 0 : case 5:
78 0 : return Renderer::Backend::Format::BC3_UNORM;
79 0 : default:
80 0 : LOGERROR("Unknown DXT compression.");
81 0 : return Renderer::Backend::Format::UNDEFINED;
82 : }
83 : }
84 : else
85 0 : textureData.transform_to(textureData.m_Flags & ~TEX_DXT);
86 : }
87 :
88 0 : switch (textureData.m_Bpp)
89 : {
90 0 : case 8:
91 0 : ENSURE(grey);
92 0 : return Renderer::Backend::Format::L8_UNORM;
93 0 : case 24:
94 0 : ENSURE(!alpha);
95 0 : return Renderer::Backend::Format::R8G8B8_UNORM;
96 0 : case 32:
97 0 : ENSURE(alpha);
98 0 : return Renderer::Backend::Format::R8G8B8A8_UNORM;
99 0 : default:
100 0 : LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp);
101 : }
102 :
103 0 : return Renderer::Backend::Format::UNDEFINED;
104 : }
105 :
106 : } // anonymous namespace
107 :
108 108 : class CPredefinedTexture
109 : {
110 : public:
111 12 : const CTexturePtr& GetTexture()
112 : {
113 12 : return m_Texture;
114 : }
115 :
116 54 : void CreateTexture(
117 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
118 : CTextureManagerImpl* textureManager)
119 : {
120 54 : Renderer::Backend::ITexture* fallback = backendTexture.get();
121 108 : CTextureProperties props(VfsPath{});
122 162 : m_Texture = CTexturePtr(new CTexture(
123 162 : std::move(backendTexture), fallback, props, textureManager));
124 54 : m_Texture->m_State = CTexture::UPLOADED;
125 54 : m_Texture->m_Self = m_Texture;
126 54 : }
127 :
128 : private:
129 : CTexturePtr m_Texture;
130 : };
131 :
132 36 : class CSingleColorTexture final : public CPredefinedTexture
133 : {
134 : public:
135 36 : CSingleColorTexture(const CColor& color, Renderer::Backend::IDevice* device,
136 : CTextureManagerImpl* textureManager)
137 36 : : m_Color(color)
138 : {
139 72 : std::stringstream textureName;
140 36 : textureName << "SingleColorTexture (";
141 36 : textureName << "R:" << m_Color.r << ", ";
142 36 : textureName << "G:" << m_Color.g << ", ";
143 36 : textureName << "B:" << m_Color.b << ", ";
144 36 : textureName << "A:" << m_Color.a << ")";
145 :
146 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
147 : device->CreateTexture2D(
148 72 : textureName.str().c_str(),
149 : Renderer::Backend::ITexture::Usage::TRANSFER_DST |
150 : Renderer::Backend::ITexture::Usage::SAMPLED,
151 : Renderer::Backend::Format::R8G8B8A8_UNORM,
152 72 : 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
153 : Renderer::Backend::Sampler::Filter::LINEAR,
154 108 : Renderer::Backend::Sampler::AddressMode::REPEAT));
155 36 : CreateTexture(std::move(backendTexture), textureManager);
156 36 : }
157 :
158 0 : void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
159 : {
160 0 : if (!GetTexture() || !GetTexture()->GetBackendTexture())
161 0 : return;
162 :
163 0 : const SColor4ub color32 = m_Color.AsSColor4ub();
164 : // Construct 1x1 32-bit texture
165 : const u8 data[4] =
166 : {
167 0 : color32.R,
168 0 : color32.G,
169 0 : color32.B,
170 0 : color32.A
171 0 : };
172 0 : deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
173 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, data, std::size(data));
174 : }
175 :
176 : private:
177 : CColor m_Color;
178 : };
179 :
180 9 : class CSingleColorTextureCube final : public CPredefinedTexture
181 : {
182 : public:
183 9 : CSingleColorTextureCube(const CColor& color, Renderer::Backend::IDevice* device,
184 : CTextureManagerImpl* textureManager)
185 9 : : m_Color(color)
186 : {
187 18 : std::stringstream textureName;
188 9 : textureName << "SingleColorTextureCube (";
189 9 : textureName << "R:" << m_Color.r << ", ";
190 9 : textureName << "G:" << m_Color.g << ", ";
191 9 : textureName << "B:" << m_Color.b << ", ";
192 9 : textureName << "A:" << m_Color.a << ")";
193 :
194 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
195 : device->CreateTexture(
196 18 : textureName.str().c_str(), Renderer::Backend::ITexture::Type::TEXTURE_CUBE,
197 : Renderer::Backend::ITexture::Usage::TRANSFER_DST |
198 : Renderer::Backend::ITexture::Usage::SAMPLED,
199 : Renderer::Backend::Format::R8G8B8A8_UNORM,
200 18 : 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
201 : Renderer::Backend::Sampler::Filter::LINEAR,
202 27 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
203 9 : CreateTexture(std::move(backendTexture), textureManager);
204 9 : }
205 :
206 0 : void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
207 : {
208 0 : if (!GetTexture() || !GetTexture()->GetBackendTexture())
209 0 : return;
210 :
211 0 : const SColor4ub color32 = m_Color.AsSColor4ub();
212 : // Construct 1x1 32-bit texture
213 : const u8 data[4] =
214 : {
215 0 : color32.R,
216 0 : color32.G,
217 0 : color32.B,
218 0 : color32.A
219 0 : };
220 :
221 0 : for (size_t face = 0; face < 6; ++face)
222 : {
223 0 : deviceCommandContext->UploadTexture(
224 0 : GetTexture()->GetBackendTexture(), Renderer::Backend::Format::R8G8B8A8_UNORM,
225 0 : data, std::size(data), 0, face);
226 : }
227 : }
228 :
229 : private:
230 : CColor m_Color;
231 : };
232 :
233 9 : class CGradientTexture final : public CPredefinedTexture
234 : {
235 : public:
236 : static const uint32_t WIDTH = 256;
237 : static const uint32_t NUMBER_OF_LEVELS = 9;
238 :
239 9 : CGradientTexture(const CColor& colorFrom, const CColor& colorTo,
240 : Renderer::Backend::IDevice* device, CTextureManagerImpl* textureManager)
241 9 : : m_ColorFrom(colorFrom), m_ColorTo(colorTo)
242 : {
243 18 : std::stringstream textureName;
244 9 : textureName << "GradientTexture";
245 9 : textureName << " From (";
246 9 : textureName << "R:" << m_ColorFrom.r << ", ";
247 9 : textureName << "G:" << m_ColorFrom.g << ", ";
248 9 : textureName << "B:" << m_ColorFrom.b << ", ";
249 9 : textureName << "A:" << m_ColorFrom.a << ")";
250 9 : textureName << " To (";
251 9 : textureName << "R:" << m_ColorTo.r << ", ";
252 9 : textureName << "G:" << m_ColorTo.g << ", ";
253 9 : textureName << "B:" << m_ColorTo.b << ", ";
254 9 : textureName << "A:" << m_ColorTo.a << ")";
255 :
256 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
257 : device->CreateTexture2D(
258 18 : textureName.str().c_str(),
259 : Renderer::Backend::ITexture::Usage::TRANSFER_DST |
260 : Renderer::Backend::ITexture::Usage::SAMPLED,
261 : Renderer::Backend::Format::R8G8B8A8_UNORM,
262 18 : WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
263 : Renderer::Backend::Sampler::Filter::LINEAR,
264 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE),
265 27 : NUMBER_OF_LEVELS);
266 9 : CreateTexture(std::move(backendTexture), textureManager);
267 9 : }
268 :
269 0 : void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
270 : {
271 0 : if (!GetTexture() || !GetTexture()->GetBackendTexture())
272 0 : return;
273 :
274 : std::array<std::array<u8, 4>, WIDTH> data;
275 0 : for (uint32_t x = 0; x < WIDTH; ++x)
276 : {
277 0 : const float t = static_cast<float>(x) / (WIDTH - 1);
278 : const CColor color(
279 : Interpolate(m_ColorFrom.r, m_ColorTo.r, t),
280 : Interpolate(m_ColorFrom.g, m_ColorTo.g, t),
281 : Interpolate(m_ColorFrom.b, m_ColorTo.b, t),
282 0 : Interpolate(m_ColorFrom.a, m_ColorTo.a, t));
283 0 : const SColor4ub color32 = color.AsSColor4ub();
284 0 : data[x][0] = color32.R;
285 0 : data[x][1] = color32.G;
286 0 : data[x][2] = color32.B;
287 0 : data[x][3] = color32.A;
288 : }
289 0 : for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level)
290 : {
291 0 : deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
292 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, data.data(), (WIDTH >> level) * data[0].size(), level);
293 : // Prepare data for the next level.
294 0 : const uint32_t nextLevelWidth = (WIDTH >> (level + 1));
295 0 : if (nextLevelWidth > 0)
296 : {
297 0 : for (uint32_t x = 0; x < nextLevelWidth; ++x)
298 0 : data[x] = data[(x << 1)];
299 : // Border values should be the same.
300 0 : data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1];
301 : }
302 : }
303 : }
304 :
305 : private:
306 : CColor m_ColorFrom, m_ColorTo;
307 : };
308 :
309 : struct TPhash
310 : {
311 23 : std::size_t operator()(const CTextureProperties& textureProperties) const
312 : {
313 23 : std::size_t seed = 0;
314 23 : hash_combine(seed, m_PathHash(textureProperties.m_Path));
315 23 : hash_combine(seed, textureProperties.m_AddressModeU);
316 23 : hash_combine(seed, textureProperties.m_AddressModeV);
317 23 : hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled);
318 23 : hash_combine(seed, textureProperties.m_FormatOverride);
319 23 : hash_combine(seed, textureProperties.m_IgnoreQuality);
320 23 : return seed;
321 : }
322 :
323 23 : std::size_t operator()(const CTexturePtr& texture) const
324 : {
325 23 : return this->operator()(texture->m_Properties);
326 : }
327 :
328 : private:
329 : std::hash<Path> m_PathHash;
330 : };
331 :
332 : struct TPequal_to
333 : {
334 1 : bool operator()(const CTextureProperties& lhs, const CTextureProperties& rhs) const
335 : {
336 : return
337 2 : lhs.m_Path == rhs.m_Path &&
338 2 : lhs.m_AddressModeU == rhs.m_AddressModeU &&
339 2 : lhs.m_AddressModeV == rhs.m_AddressModeV &&
340 2 : lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled &&
341 3 : lhs.m_FormatOverride == rhs.m_FormatOverride &&
342 2 : lhs.m_IgnoreQuality == rhs.m_IgnoreQuality;
343 : }
344 :
345 1 : bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const
346 : {
347 1 : return this->operator()(lhs->m_Properties, rhs->m_Properties);
348 : }
349 : };
350 :
351 : class CTextureManagerImpl
352 : {
353 : friend class CTexture;
354 : public:
355 9 : CTextureManagerImpl(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device) :
356 : m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_Device(device),
357 : m_TextureConverter(vfs, highQuality),
358 18 : m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), device, this),
359 18 : m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), device, this),
360 18 : m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), device, this),
361 18 : m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), device, this),
362 : m_AlphaGradientTexture(
363 18 : CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), device, this),
364 99 : m_BlackTextureCube(CColor(0.0f, 0.0f, 0.0f, 1.0f), device, this)
365 : {
366 : // Allow hotloading of textures
367 9 : RegisterFileReloadFunc(ReloadChangedFileCB, this);
368 :
369 9 : m_HasS3TC =
370 18 : m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGB_UNORM) &&
371 18 : m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGBA_UNORM) &&
372 27 : m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC2_UNORM) &&
373 9 : m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC3_UNORM);
374 9 : }
375 :
376 9 : ~CTextureManagerImpl()
377 9 : {
378 9 : UnregisterFileReloadFunc(ReloadChangedFileCB, this);
379 9 : }
380 :
381 0 : const CTexturePtr& GetErrorTexture()
382 : {
383 0 : return m_ErrorTexture.GetTexture();
384 : }
385 :
386 0 : const CTexturePtr& GetWhiteTexture()
387 : {
388 0 : return m_WhiteTexture.GetTexture();
389 : }
390 :
391 0 : const CTexturePtr& GetTransparentTexture()
392 : {
393 0 : return m_TransparentTexture.GetTexture();
394 : }
395 :
396 0 : const CTexturePtr& GetAlphaGradientTexture()
397 : {
398 0 : return m_AlphaGradientTexture.GetTexture();
399 : }
400 :
401 0 : const CTexturePtr& GetBlackTextureCube()
402 : {
403 0 : return m_BlackTextureCube.GetTexture();
404 : }
405 :
406 : /**
407 : * See CTextureManager::CreateTexture
408 : */
409 12 : CTexturePtr CreateTexture(const CTextureProperties& props)
410 : {
411 : // Construct a new default texture with the given properties to use as the search key
412 : CTexturePtr texture(new CTexture(
413 24 : nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture(), props, this));
414 :
415 : // Try to find an existing texture with the given properties
416 12 : TextureCache::iterator it = m_TextureCache.find(texture);
417 12 : if (it != m_TextureCache.end())
418 1 : return *it;
419 :
420 : // Can't find an existing texture - finish setting up this new texture
421 11 : texture->m_Self = texture;
422 11 : m_TextureCache.insert(texture);
423 11 : m_HotloadFiles[props.m_Path].insert(texture);
424 :
425 11 : return texture;
426 : }
427 :
428 0 : CTexturePtr WrapBackendTexture(
429 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture)
430 : {
431 0 : ENSURE(backendTexture);
432 0 : Renderer::Backend::ITexture* fallback = backendTexture.get();
433 :
434 0 : CTextureProperties props(VfsPath{});
435 : CTexturePtr texture(new CTexture(
436 0 : std::move(backendTexture), fallback, props, this));
437 0 : texture->m_State = CTexture::UPLOADED;
438 0 : texture->m_Self = texture;
439 0 : return texture;
440 : }
441 :
442 : /**
443 : * Load the given file into the texture object and upload it to OpenGL.
444 : * Assumes the file already exists.
445 : */
446 6 : void LoadTexture(const CTexturePtr& texture, const VfsPath& path)
447 : {
448 12 : PROFILE2("load texture");
449 6 : PROFILE2_ATTR("name: %ls", path.string().c_str());
450 :
451 12 : std::shared_ptr<u8> fileData;
452 : size_t fileSize;
453 6 : const Status loadStatus = m_VFS->LoadFile(path, fileData, fileSize);
454 6 : if (loadStatus != INFO::OK)
455 : {
456 0 : LOGERROR("Texture failed to load; \"%s\" %s",
457 : texture->m_Properties.m_Path.string8(), GetStatusAsString(loadStatus).c_str());
458 0 : texture->ResetBackendTexture(
459 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
460 0 : texture->m_TextureData.reset();
461 0 : return;
462 : }
463 :
464 6 : texture->m_TextureData = std::make_unique<Tex>();
465 6 : Tex& textureData = *texture->m_TextureData;
466 6 : const Status decodeStatus = textureData.decode(fileData, fileSize);
467 6 : if (decodeStatus != INFO::OK)
468 : {
469 0 : LOGERROR("Texture failed to decode; \"%s\" %s",
470 : texture->m_Properties.m_Path.string8(), GetStatusAsString(decodeStatus).c_str());
471 0 : texture->ResetBackendTexture(
472 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
473 0 : texture->m_TextureData.reset();
474 0 : return;
475 : }
476 :
477 6 : if (!is_pow2(textureData.m_Width) || !is_pow2(textureData.m_Height))
478 : {
479 0 : LOGERROR("Texture should have width and height be power of two; \"%s\" %zux%zu",
480 : texture->m_Properties.m_Path.string8(), textureData.m_Width, textureData.m_Height);
481 0 : texture->ResetBackendTexture(
482 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
483 0 : texture->m_TextureData.reset();
484 0 : return;
485 : }
486 :
487 : // Initialise base color from the texture
488 6 : texture->m_BaseColor = textureData.get_average_color();
489 :
490 6 : Renderer::Backend::Format format = Renderer::Backend::Format::UNDEFINED;
491 6 : if (texture->m_Properties.m_FormatOverride != Renderer::Backend::Format::UNDEFINED)
492 : {
493 0 : format = texture->m_Properties.m_FormatOverride;
494 : // TODO: it'd be good to remove the override hack and provide information
495 : // via XML.
496 0 : ENSURE((textureData.m_Flags & TEX_DXT) == 0);
497 0 : if (format == Renderer::Backend::Format::A8_UNORM)
498 : {
499 0 : ENSURE(textureData.m_Bpp == 8 && (textureData.m_Flags & TEX_GREY));
500 : }
501 0 : else if (format == Renderer::Backend::Format::R8G8B8A8_UNORM)
502 : {
503 0 : ENSURE(textureData.m_Bpp == 32 && (textureData.m_Flags & TEX_ALPHA));
504 : }
505 : else
506 0 : debug_warn("Unsupported format override.");
507 : }
508 : else
509 : {
510 6 : format = ChooseFormatAndTransformTextureDataIfNeeded(textureData, m_HasS3TC);
511 : }
512 :
513 6 : if (format == Renderer::Backend::Format::UNDEFINED)
514 : {
515 0 : LOGERROR("Texture failed to choose format; \"%s\"", texture->m_Properties.m_Path.string8());
516 0 : texture->ResetBackendTexture(
517 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
518 0 : texture->m_TextureData.reset();
519 0 : return;
520 : }
521 :
522 6 : const uint32_t width = texture->m_TextureData->m_Width;
523 6 : const uint32_t height = texture->m_TextureData->m_Height ;
524 6 : const uint32_t MIPLevelCount = texture->m_TextureData->GetMIPLevels().size();
525 6 : texture->m_BaseLevelOffset = 0;
526 :
527 : Renderer::Backend::Sampler::Desc defaultSamplerDesc =
528 : Renderer::Backend::Sampler::MakeDefaultSampler(
529 : Renderer::Backend::Sampler::Filter::LINEAR,
530 6 : Renderer::Backend::Sampler::AddressMode::REPEAT);
531 :
532 6 : defaultSamplerDesc.addressModeU = texture->m_Properties.m_AddressModeU;
533 6 : defaultSamplerDesc.addressModeV = texture->m_Properties.m_AddressModeV;
534 6 : if (texture->m_Properties.m_AnisotropicFilterEnabled && m_Device->GetCapabilities().anisotropicFiltering)
535 : {
536 0 : int maxAnisotropy = 1;
537 0 : CFG_GET_VAL("textures.maxanisotropy", maxAnisotropy);
538 0 : const int allowedValues[] = {2, 4, 8, 16};
539 0 : if (std::find(std::begin(allowedValues), std::end(allowedValues), maxAnisotropy) != std::end(allowedValues))
540 : {
541 0 : defaultSamplerDesc.anisotropyEnabled = true;
542 0 : defaultSamplerDesc.maxAnisotropy = maxAnisotropy;
543 : }
544 : }
545 :
546 6 : if (!texture->m_Properties.m_IgnoreQuality)
547 : {
548 6 : int quality = 2;
549 6 : CFG_GET_VAL("textures.quality", quality);
550 6 : if (quality == 1)
551 : {
552 0 : if (MIPLevelCount > 1 && std::min(width, height) > 8)
553 0 : texture->m_BaseLevelOffset += 1;
554 : }
555 6 : else if (quality == 0)
556 : {
557 0 : if (MIPLevelCount > 2 && std::min(width, height) > 16)
558 0 : texture->m_BaseLevelOffset += 2;
559 0 : while (std::min(width >> texture->m_BaseLevelOffset, height >> texture->m_BaseLevelOffset) > 256 &&
560 0 : MIPLevelCount > texture->m_BaseLevelOffset + 1)
561 : {
562 0 : texture->m_BaseLevelOffset += 1;
563 : }
564 0 : defaultSamplerDesc.mipFilter = Renderer::Backend::Sampler::Filter::NEAREST;
565 0 : defaultSamplerDesc.anisotropyEnabled = false;
566 : }
567 : }
568 :
569 36 : texture->m_BackendTexture = m_Device->CreateTexture2D(
570 12 : texture->m_Properties.m_Path.string8().c_str(),
571 : Renderer::Backend::ITexture::Usage::TRANSFER_DST |
572 : Renderer::Backend::ITexture::Usage::SAMPLED,
573 12 : format, (width >> texture->m_BaseLevelOffset), (height >> texture->m_BaseLevelOffset),
574 12 : defaultSamplerDesc, MIPLevelCount - texture->m_BaseLevelOffset);
575 : }
576 :
577 : /**
578 : * Set up some parameters for the loose cache filename code.
579 : */
580 11 : void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version)
581 : {
582 : // Hash the settings, so we won't use an old loose cache file if the
583 : // settings have changed
584 11 : CTextureConverter::Settings settings = GetConverterSettings(texture);
585 11 : settings.Hash(hash);
586 :
587 : // Arbitrary version number - change this if we update the code and
588 : // need to invalidate old users' caches
589 11 : version = 1;
590 11 : }
591 :
592 : /**
593 : * Attempts to load a cached version of a texture.
594 : * If the texture is loaded (or there was an error), returns true.
595 : * Otherwise, returns false to indicate the caller should generate the cached version.
596 : */
597 6 : bool TryLoadingCached(const CTexturePtr& texture)
598 : {
599 6 : MD5 hash;
600 : u32 version;
601 6 : PrepareCacheKey(texture, hash, version);
602 :
603 12 : VfsPath loadPath;
604 6 : Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath);
605 :
606 6 : if (ret == INFO::OK)
607 : {
608 : // Found a cached texture - load it
609 1 : LoadTexture(texture, loadPath);
610 1 : return true;
611 : }
612 5 : else if (ret == INFO::SKIPPED)
613 : {
614 : // No cached version was found - we'll need to create it
615 5 : return false;
616 : }
617 : else
618 : {
619 0 : ENSURE(ret < 0);
620 :
621 : // No source file or archive cache was found, so we can't load the
622 : // real texture at all - return the error texture instead
623 0 : LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8());
624 0 : texture->ResetBackendTexture(
625 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
626 0 : return true;
627 : }
628 : }
629 :
630 : /**
631 : * Initiates an asynchronous conversion process, from the texture's
632 : * source file to the corresponding loose cache file.
633 : */
634 5 : void ConvertTexture(const CTexturePtr& texture)
635 : {
636 10 : const VfsPath sourcePath = texture->m_Properties.m_Path;
637 :
638 10 : PROFILE2("convert texture");
639 5 : PROFILE2_ATTR("name: %ls", sourcePath.string().c_str());
640 :
641 5 : MD5 hash;
642 : u32 version;
643 5 : PrepareCacheKey(texture, hash, version);
644 10 : const VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version);
645 :
646 5 : CTextureConverter::Settings settings = GetConverterSettings(texture);
647 :
648 5 : m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings);
649 5 : }
650 :
651 0 : bool TextureExists(const VfsPath& path) const
652 : {
653 0 : return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK ||
654 0 : m_VFS->GetFileInfo(path, 0) == INFO::OK;
655 : }
656 :
657 0 : bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath)
658 : {
659 0 : archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath);
660 :
661 0 : CTextureProperties textureProps(sourcePath);
662 0 : CTexturePtr texture = CreateTexture(textureProps);
663 0 : CTextureConverter::Settings settings = GetConverterSettings(texture);
664 :
665 0 : if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings))
666 0 : return false;
667 :
668 : while (true)
669 : {
670 0 : CTexturePtr textureOut;
671 0 : VfsPath dest;
672 : bool ok;
673 0 : if (m_TextureConverter.Poll(textureOut, dest, ok))
674 0 : return ok;
675 :
676 0 : std::this_thread::sleep_for(std::chrono::microseconds(1));
677 0 : }
678 : }
679 :
680 11 : bool MakeProgress()
681 : {
682 : // Process any completed conversion tasks
683 : {
684 17 : CTexturePtr texture;
685 17 : VfsPath dest;
686 : bool ok;
687 11 : if (m_TextureConverter.Poll(texture, dest, ok))
688 : {
689 5 : if (ok)
690 : {
691 5 : LoadTexture(texture, dest);
692 : }
693 : else
694 : {
695 0 : LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8());
696 0 : texture->ResetBackendTexture(
697 0 : nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
698 : }
699 5 : texture->m_State = CTexture::LOADED;
700 5 : return true;
701 : }
702 : }
703 :
704 : // We'll only push new conversion requests if it's not already busy
705 6 : bool converterBusy = m_TextureConverter.IsBusy();
706 :
707 6 : if (!converterBusy)
708 : {
709 : // Look for all high-priority textures needing conversion.
710 : // (Iterating over all textures isn't optimally efficient, but it
711 : // doesn't seem to be a problem yet and it's simpler than maintaining
712 : // multiple queues.)
713 11 : for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
714 : {
715 11 : if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING)
716 : {
717 : // Start converting this texture
718 5 : (*it)->m_State = CTexture::HIGH_IS_CONVERTING;
719 5 : ConvertTexture(*it);
720 5 : return true;
721 : }
722 : }
723 : }
724 :
725 : // Try loading prefetched textures from their cache
726 2 : for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
727 : {
728 1 : if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING)
729 : {
730 0 : if (TryLoadingCached(*it))
731 : {
732 0 : (*it)->m_State = CTexture::LOADED;
733 : }
734 : else
735 : {
736 0 : (*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING;
737 : }
738 0 : return true;
739 : }
740 : }
741 :
742 : // If we've got nothing better to do, then start converting prefetched textures.
743 1 : if (!converterBusy)
744 : {
745 0 : for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
746 : {
747 0 : if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING)
748 : {
749 0 : (*it)->m_State = CTexture::PREFETCH_IS_CONVERTING;
750 0 : ConvertTexture(*it);
751 0 : return true;
752 : }
753 : }
754 : }
755 :
756 1 : return false;
757 : }
758 :
759 0 : bool MakeUploadProgress(
760 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
761 : {
762 0 : if (!m_PredefinedTexturesUploaded)
763 : {
764 0 : m_DefaultTexture.Upload(deviceCommandContext);
765 0 : m_ErrorTexture.Upload(deviceCommandContext);
766 0 : m_WhiteTexture.Upload(deviceCommandContext);
767 0 : m_TransparentTexture.Upload(deviceCommandContext);
768 0 : m_AlphaGradientTexture.Upload(deviceCommandContext);
769 0 : m_BlackTextureCube.Upload(deviceCommandContext);
770 0 : m_PredefinedTexturesUploaded = true;
771 0 : return true;
772 : }
773 0 : return false;
774 : }
775 :
776 : /**
777 : * Compute the conversion settings that apply to a given texture, by combining
778 : * the textures.xml files from its directory and all parent directories
779 : * (up to the VFS root).
780 : */
781 16 : CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture)
782 : {
783 32 : fs::wpath srcPath = texture->m_Properties.m_Path.string();
784 :
785 32 : std::vector<CTextureConverter::SettingsFile*> files;
786 32 : VfsPath p;
787 80 : for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it)
788 : {
789 128 : VfsPath settingsPath = p / "textures.xml";
790 64 : m_HotloadFiles[settingsPath].insert(texture);
791 64 : CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath);
792 64 : if (f)
793 0 : files.push_back(f);
794 64 : p = p / GetWstringFromWpath(*it);
795 : }
796 32 : return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files);
797 : }
798 :
799 : /**
800 : * Return the (cached) settings file with the given filename,
801 : * or NULL if it doesn't exist.
802 : */
803 64 : CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path)
804 : {
805 64 : SettingsFilesMap::iterator it = m_SettingsFiles.find(path);
806 64 : if (it != m_SettingsFiles.end())
807 52 : return it->second.get();
808 :
809 12 : if (m_VFS->GetFileInfo(path, NULL) >= 0)
810 : {
811 0 : std::shared_ptr<CTextureConverter::SettingsFile> settings(m_TextureConverter.LoadSettings(path));
812 0 : m_SettingsFiles.insert(std::make_pair(path, settings));
813 0 : return settings.get();
814 : }
815 : else
816 : {
817 12 : m_SettingsFiles.insert(std::make_pair(path, std::shared_ptr<CTextureConverter::SettingsFile>()));
818 12 : return NULL;
819 : }
820 : }
821 :
822 0 : static Status ReloadChangedFileCB(void* param, const VfsPath& path)
823 : {
824 0 : return static_cast<CTextureManagerImpl*>(param)->ReloadChangedFile(path);
825 : }
826 :
827 0 : Status ReloadChangedFile(const VfsPath& path)
828 : {
829 : // Uncache settings file, if this is one
830 0 : m_SettingsFiles.erase(path);
831 :
832 : // Find all textures using this file
833 0 : HotloadFilesMap::iterator files = m_HotloadFiles.find(path);
834 0 : if (files != m_HotloadFiles.end())
835 : {
836 : // Flag all textures using this file as needing reloading
837 0 : for (std::set<std::weak_ptr<CTexture>>::iterator it = files->second.begin(); it != files->second.end(); ++it)
838 : {
839 0 : if (std::shared_ptr<CTexture> texture = it->lock())
840 : {
841 0 : texture->m_State = CTexture::UNLOADED;
842 0 : texture->ResetBackendTexture(
843 0 : nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
844 0 : texture->m_TextureData.reset();
845 : }
846 : }
847 : }
848 :
849 0 : return INFO::OK;
850 : }
851 :
852 0 : void ReloadAllTextures()
853 : {
854 0 : for (const CTexturePtr& texture : m_TextureCache)
855 : {
856 0 : texture->m_State = CTexture::UNLOADED;
857 0 : texture->ResetBackendTexture(
858 0 : nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
859 0 : texture->m_TextureData.reset();
860 : }
861 0 : }
862 :
863 0 : size_t GetBytesUploaded() const
864 : {
865 0 : size_t size = 0;
866 0 : for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
867 0 : size += (*it)->GetUploadedSize();
868 0 : return size;
869 : }
870 :
871 0 : void OnQualityChanged()
872 : {
873 0 : ReloadAllTextures();
874 0 : }
875 :
876 : private:
877 : PIVFS m_VFS;
878 : CCacheLoader m_CacheLoader;
879 : Renderer::Backend::IDevice* m_Device = nullptr;
880 : CTextureConverter m_TextureConverter;
881 :
882 : CSingleColorTexture m_DefaultTexture;
883 : CSingleColorTexture m_ErrorTexture;
884 : CSingleColorTexture m_WhiteTexture;
885 : CSingleColorTexture m_TransparentTexture;
886 : CGradientTexture m_AlphaGradientTexture;
887 : CSingleColorTextureCube m_BlackTextureCube;
888 : bool m_PredefinedTexturesUploaded = false;
889 :
890 : // Cache of all loaded textures
891 : using TextureCache =
892 : std::unordered_set<CTexturePtr, TPhash, TPequal_to>;
893 : TextureCache m_TextureCache;
894 : // TODO: we ought to expire unused textures from the cache eventually
895 :
896 : // Store the set of textures that need to be reloaded when the given file
897 : // (a source file or settings.xml) is modified
898 : using HotloadFilesMap =
899 : std::unordered_map<VfsPath, std::set<std::weak_ptr<CTexture>, std::owner_less<std::weak_ptr<CTexture>>>>;
900 : HotloadFilesMap m_HotloadFiles;
901 :
902 : // Cache for the conversion settings files
903 : using SettingsFilesMap =
904 : std::unordered_map<VfsPath, std::shared_ptr<CTextureConverter::SettingsFile>>;
905 : SettingsFilesMap m_SettingsFiles;
906 :
907 : bool m_HasS3TC = false;
908 : };
909 :
910 66 : CTexture::CTexture(
911 : std::unique_ptr<Renderer::Backend::ITexture> texture,
912 : Renderer::Backend::ITexture* fallback,
913 66 : const CTextureProperties& props, CTextureManagerImpl* textureManager) :
914 66 : m_BackendTexture(std::move(texture)), m_FallbackBackendTexture(fallback),
915 : m_BaseColor(0), m_State(UNLOADED), m_Properties(props),
916 132 : m_TextureManager(textureManager)
917 : {
918 66 : }
919 :
920 : CTexture::~CTexture() = default;
921 :
922 0 : void CTexture::UploadBackendTextureIfNeeded(
923 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
924 : {
925 0 : if (IsUploaded())
926 0 : return;
927 :
928 0 : if (!IsLoaded())
929 0 : TryLoad();
930 :
931 0 : if (!IsLoaded())
932 0 : return;
933 0 : else if (!m_TextureData || !m_BackendTexture)
934 : {
935 0 : ResetBackendTexture(nullptr, m_TextureManager->GetErrorTexture()->GetBackendTexture());
936 0 : m_State = UPLOADED;
937 0 : return;
938 : }
939 :
940 0 : m_UploadedSize = 0;
941 0 : for (uint32_t textureDataLevel = m_BaseLevelOffset, level = 0; textureDataLevel < m_TextureData->GetMIPLevels().size(); ++textureDataLevel)
942 : {
943 0 : const Tex::MIPLevel& levelData = m_TextureData->GetMIPLevels()[textureDataLevel];
944 0 : deviceCommandContext->UploadTexture(m_BackendTexture.get(), m_BackendTexture->GetFormat(),
945 0 : levelData.data, levelData.dataSize, level++);
946 0 : m_UploadedSize += levelData.dataSize;
947 : }
948 0 : m_TextureData.reset();
949 :
950 0 : m_State = UPLOADED;
951 : }
952 :
953 12 : Renderer::Backend::ITexture* CTexture::GetBackendTexture()
954 : {
955 12 : return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
956 : }
957 :
958 0 : const Renderer::Backend::ITexture* CTexture::GetBackendTexture() const
959 : {
960 0 : return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
961 : }
962 :
963 6 : bool CTexture::TryLoad()
964 : {
965 : // If we haven't started loading, then try loading, and if that fails then request conversion.
966 : // If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority.
967 6 : if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING)
968 : {
969 12 : if (std::shared_ptr<CTexture> self = m_Self.lock())
970 : {
971 6 : if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self))
972 1 : m_State = LOADED;
973 : else
974 5 : m_State = HIGH_NEEDS_CONVERTING;
975 : }
976 : }
977 :
978 6 : return IsLoaded() || IsUploaded();
979 : }
980 :
981 0 : void CTexture::Prefetch()
982 : {
983 0 : if (m_State == UNLOADED)
984 : {
985 0 : if (std::shared_ptr<CTexture> self = m_Self.lock())
986 : {
987 0 : m_State = PREFETCH_NEEDS_LOADING;
988 : }
989 : }
990 0 : }
991 :
992 0 : void CTexture::ResetBackendTexture(
993 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
994 : Renderer::Backend::ITexture* fallbackBackendTexture)
995 : {
996 0 : m_BackendTexture = std::move(backendTexture);
997 0 : m_FallbackBackendTexture = fallbackBackendTexture;
998 0 : }
999 :
1000 0 : size_t CTexture::GetWidth() const
1001 : {
1002 0 : return GetBackendTexture()->GetWidth();
1003 : }
1004 :
1005 0 : size_t CTexture::GetHeight() const
1006 : {
1007 0 : return GetBackendTexture()->GetHeight();
1008 : }
1009 :
1010 0 : bool CTexture::HasAlpha() const
1011 : {
1012 0 : const Renderer::Backend::Format format = GetBackendTexture()->GetFormat();
1013 : return
1014 0 : format == Renderer::Backend::Format::A8_UNORM ||
1015 0 : format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
1016 0 : format == Renderer::Backend::Format::BC1_RGBA_UNORM ||
1017 0 : format == Renderer::Backend::Format::BC2_UNORM ||
1018 0 : format == Renderer::Backend::Format::BC3_UNORM;
1019 : }
1020 :
1021 0 : u32 CTexture::GetBaseColor() const
1022 : {
1023 0 : return m_BaseColor;
1024 : }
1025 :
1026 0 : size_t CTexture::GetUploadedSize() const
1027 : {
1028 0 : return m_UploadedSize;
1029 : }
1030 :
1031 : // CTextureManager: forward all calls to impl:
1032 :
1033 9 : CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device) :
1034 9 : m(new CTextureManagerImpl(vfs, highQuality, device))
1035 : {
1036 9 : }
1037 :
1038 18 : CTextureManager::~CTextureManager()
1039 : {
1040 9 : delete m;
1041 9 : }
1042 :
1043 12 : CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props)
1044 : {
1045 12 : return m->CreateTexture(props);
1046 : }
1047 :
1048 0 : CTexturePtr CTextureManager::WrapBackendTexture(
1049 : std::unique_ptr<Renderer::Backend::ITexture> backendTexture)
1050 : {
1051 0 : return m->WrapBackendTexture(std::move(backendTexture));
1052 : }
1053 :
1054 0 : bool CTextureManager::TextureExists(const VfsPath& path) const
1055 : {
1056 0 : return m->TextureExists(path);
1057 : }
1058 :
1059 0 : const CTexturePtr& CTextureManager::GetErrorTexture()
1060 : {
1061 0 : return m->GetErrorTexture();
1062 : }
1063 :
1064 0 : const CTexturePtr& CTextureManager::GetWhiteTexture()
1065 : {
1066 0 : return m->GetWhiteTexture();
1067 : }
1068 :
1069 0 : const CTexturePtr& CTextureManager::GetTransparentTexture()
1070 : {
1071 0 : return m->GetTransparentTexture();
1072 : }
1073 :
1074 0 : const CTexturePtr& CTextureManager::GetAlphaGradientTexture()
1075 : {
1076 0 : return m->GetAlphaGradientTexture();
1077 : }
1078 :
1079 0 : const CTexturePtr& CTextureManager::GetBlackTextureCube()
1080 : {
1081 0 : return m->GetBlackTextureCube();
1082 : }
1083 :
1084 11 : bool CTextureManager::MakeProgress()
1085 : {
1086 11 : return m->MakeProgress();
1087 : }
1088 :
1089 0 : bool CTextureManager::MakeUploadProgress(
1090 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
1091 : {
1092 0 : return m->MakeUploadProgress(deviceCommandContext);
1093 : }
1094 :
1095 0 : bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath)
1096 : {
1097 0 : return m->GenerateCachedTexture(path, outputPath);
1098 : }
1099 :
1100 0 : size_t CTextureManager::GetBytesUploaded() const
1101 : {
1102 0 : return m->GetBytesUploaded();
1103 : }
1104 :
1105 0 : void CTextureManager::OnQualityChanged()
1106 : {
1107 0 : m->OnQualityChanged();
1108 3 : }
|