[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / TextureManager.cpp
blobb46731924bd8975dbaf9fd01fc8db178e7ef0bb3
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "TextureManager.h"
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"
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>
48 namespace
51 Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(
52 Renderer::Backend::IDevice* device, Tex& textureData, const bool hasS3TC)
54 const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0;
55 const bool grey = (textureData.m_Flags & TEX_GREY) != 0;
56 const size_t dxt = textureData.m_Flags & TEX_DXT;
58 // Some backends don't support BGR as an internal format (like GLES).
59 // TODO: add a check that the format is internally supported.
60 if ((textureData.m_Flags & TEX_BGR) != 0)
62 LOGWARNING("Using slow path to convert BGR texture.");
63 textureData.transform_to(textureData.m_Flags & ~TEX_BGR);
66 if (dxt)
68 if (hasS3TC)
70 switch (dxt)
72 case DXT1A:
73 return Renderer::Backend::Format::BC1_RGBA_UNORM;
74 case 1:
75 return Renderer::Backend::Format::BC1_RGB_UNORM;
76 case 3:
77 return Renderer::Backend::Format::BC2_UNORM;
78 case 5:
79 return Renderer::Backend::Format::BC3_UNORM;
80 default:
81 LOGERROR("Unknown DXT compression.");
82 return Renderer::Backend::Format::UNDEFINED;
85 else
86 textureData.transform_to(textureData.m_Flags & ~TEX_DXT);
89 switch (textureData.m_Bpp)
91 case 8:
92 ENSURE(grey);
93 return Renderer::Backend::Format::L8_UNORM;
94 case 24:
95 ENSURE(!alpha);
96 if (device->IsTextureFormatSupported(Renderer::Backend::Format::R8G8B8_UNORM))
97 return Renderer::Backend::Format::R8G8B8_UNORM;
98 else
100 LOGWARNING("Using slow path to convert unsupported RGB texture to RGBA.");
101 textureData.transform_to(textureData.m_Flags & TEX_ALPHA);
102 return Renderer::Backend::Format::R8G8B8A8_UNORM;
104 case 32:
105 ENSURE(alpha);
106 return Renderer::Backend::Format::R8G8B8A8_UNORM;
107 default:
108 LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp);
111 return Renderer::Backend::Format::UNDEFINED;
114 } // anonymous namespace
116 class CPredefinedTexture
118 public:
119 const CTexturePtr& GetTexture()
121 return m_Texture;
124 void CreateTexture(
125 std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
126 CTextureManagerImpl* textureManager)
128 Renderer::Backend::ITexture* fallback = backendTexture.get();
129 CTextureProperties props(VfsPath{});
130 m_Texture = CTexturePtr(new CTexture(
131 std::move(backendTexture), fallback, props, textureManager));
132 m_Texture->m_State = CTexture::UPLOADED;
133 m_Texture->m_Self = m_Texture;
136 private:
137 CTexturePtr m_Texture;
140 class CSingleColorTexture final : public CPredefinedTexture
142 public:
143 CSingleColorTexture(const CColor& color, Renderer::Backend::IDevice* device,
144 CTextureManagerImpl* textureManager)
145 : m_Color(color)
147 std::stringstream textureName;
148 textureName << "SingleColorTexture (";
149 textureName << "R:" << m_Color.r << ", ";
150 textureName << "G:" << m_Color.g << ", ";
151 textureName << "B:" << m_Color.b << ", ";
152 textureName << "A:" << m_Color.a << ")";
154 std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
155 device->CreateTexture2D(
156 textureName.str().c_str(),
157 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
158 Renderer::Backend::ITexture::Usage::SAMPLED,
159 Renderer::Backend::Format::R8G8B8A8_UNORM,
160 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
161 Renderer::Backend::Sampler::Filter::LINEAR,
162 Renderer::Backend::Sampler::AddressMode::REPEAT));
163 CreateTexture(std::move(backendTexture), textureManager);
166 void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
168 if (!GetTexture() || !GetTexture()->GetBackendTexture())
169 return;
171 const SColor4ub color32 = m_Color.AsSColor4ub();
172 // Construct 1x1 32-bit texture
173 const u8 data[4] =
175 color32.R,
176 color32.G,
177 color32.B,
178 color32.A
180 deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
181 Renderer::Backend::Format::R8G8B8A8_UNORM, data, std::size(data));
184 private:
185 CColor m_Color;
188 class CSingleColorTextureCube final : public CPredefinedTexture
190 public:
191 CSingleColorTextureCube(const CColor& color, Renderer::Backend::IDevice* device,
192 CTextureManagerImpl* textureManager)
193 : m_Color(color)
195 std::stringstream textureName;
196 textureName << "SingleColorTextureCube (";
197 textureName << "R:" << m_Color.r << ", ";
198 textureName << "G:" << m_Color.g << ", ";
199 textureName << "B:" << m_Color.b << ", ";
200 textureName << "A:" << m_Color.a << ")";
202 std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
203 device->CreateTexture(
204 textureName.str().c_str(), Renderer::Backend::ITexture::Type::TEXTURE_CUBE,
205 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
206 Renderer::Backend::ITexture::Usage::SAMPLED,
207 Renderer::Backend::Format::R8G8B8A8_UNORM,
208 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
209 Renderer::Backend::Sampler::Filter::LINEAR,
210 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
211 CreateTexture(std::move(backendTexture), textureManager);
214 void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
216 if (!GetTexture() || !GetTexture()->GetBackendTexture())
217 return;
219 const SColor4ub color32 = m_Color.AsSColor4ub();
220 // Construct 1x1 32-bit texture
221 const u8 data[4] =
223 color32.R,
224 color32.G,
225 color32.B,
226 color32.A
229 for (size_t face = 0; face < 6; ++face)
231 deviceCommandContext->UploadTexture(
232 GetTexture()->GetBackendTexture(), Renderer::Backend::Format::R8G8B8A8_UNORM,
233 data, std::size(data), 0, face);
237 private:
238 CColor m_Color;
241 class CGradientTexture final : public CPredefinedTexture
243 public:
244 static const uint32_t WIDTH = 256;
245 static const uint32_t NUMBER_OF_LEVELS = 9;
247 CGradientTexture(const CColor& colorFrom, const CColor& colorTo,
248 Renderer::Backend::IDevice* device, CTextureManagerImpl* textureManager)
249 : m_ColorFrom(colorFrom), m_ColorTo(colorTo)
251 std::stringstream textureName;
252 textureName << "GradientTexture";
253 textureName << " From (";
254 textureName << "R:" << m_ColorFrom.r << ", ";
255 textureName << "G:" << m_ColorFrom.g << ", ";
256 textureName << "B:" << m_ColorFrom.b << ", ";
257 textureName << "A:" << m_ColorFrom.a << ")";
258 textureName << " To (";
259 textureName << "R:" << m_ColorTo.r << ", ";
260 textureName << "G:" << m_ColorTo.g << ", ";
261 textureName << "B:" << m_ColorTo.b << ", ";
262 textureName << "A:" << m_ColorTo.a << ")";
264 std::unique_ptr<Renderer::Backend::ITexture> backendTexture =
265 device->CreateTexture2D(
266 textureName.str().c_str(),
267 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
268 Renderer::Backend::ITexture::Usage::SAMPLED,
269 Renderer::Backend::Format::R8G8B8A8_UNORM,
270 WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
271 Renderer::Backend::Sampler::Filter::LINEAR,
272 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE),
273 NUMBER_OF_LEVELS);
274 CreateTexture(std::move(backendTexture), textureManager);
277 void Upload(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
279 if (!GetTexture() || !GetTexture()->GetBackendTexture())
280 return;
282 std::array<std::array<u8, 4>, WIDTH> data;
283 for (uint32_t x = 0; x < WIDTH; ++x)
285 const float t = static_cast<float>(x) / (WIDTH - 1);
286 const CColor color(
287 Interpolate(m_ColorFrom.r, m_ColorTo.r, t),
288 Interpolate(m_ColorFrom.g, m_ColorTo.g, t),
289 Interpolate(m_ColorFrom.b, m_ColorTo.b, t),
290 Interpolate(m_ColorFrom.a, m_ColorTo.a, t));
291 const SColor4ub color32 = color.AsSColor4ub();
292 data[x][0] = color32.R;
293 data[x][1] = color32.G;
294 data[x][2] = color32.B;
295 data[x][3] = color32.A;
297 for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level)
299 deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
300 Renderer::Backend::Format::R8G8B8A8_UNORM, data.data(), (WIDTH >> level) * data[0].size(), level);
301 // Prepare data for the next level.
302 const uint32_t nextLevelWidth = (WIDTH >> (level + 1));
303 if (nextLevelWidth > 0)
305 for (uint32_t x = 0; x < nextLevelWidth; ++x)
306 data[x] = data[(x << 1)];
307 // Border values should be the same.
308 data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1];
313 private:
314 CColor m_ColorFrom, m_ColorTo;
317 struct TPhash
319 std::size_t operator()(const CTextureProperties& textureProperties) const
321 std::size_t seed = 0;
322 hash_combine(seed, m_PathHash(textureProperties.m_Path));
323 hash_combine(seed, textureProperties.m_AddressModeU);
324 hash_combine(seed, textureProperties.m_AddressModeV);
325 hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled);
326 hash_combine(seed, textureProperties.m_FormatOverride);
327 hash_combine(seed, textureProperties.m_IgnoreQuality);
328 return seed;
331 std::size_t operator()(const CTexturePtr& texture) const
333 return this->operator()(texture->m_Properties);
336 private:
337 std::hash<Path> m_PathHash;
340 struct TPequal_to
342 bool operator()(const CTextureProperties& lhs, const CTextureProperties& rhs) const
344 return
345 lhs.m_Path == rhs.m_Path &&
346 lhs.m_AddressModeU == rhs.m_AddressModeU &&
347 lhs.m_AddressModeV == rhs.m_AddressModeV &&
348 lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled &&
349 lhs.m_FormatOverride == rhs.m_FormatOverride &&
350 lhs.m_IgnoreQuality == rhs.m_IgnoreQuality;
353 bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const
355 return this->operator()(lhs->m_Properties, rhs->m_Properties);
359 class CTextureManagerImpl
361 friend class CTexture;
362 public:
363 CTextureManagerImpl(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device) :
364 m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_Device(device),
365 m_TextureConverter(vfs, highQuality),
366 m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), device, this),
367 m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), device, this),
368 m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), device, this),
369 m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), device, this),
370 m_AlphaGradientTexture(
371 CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), device, this),
372 m_BlackTextureCube(CColor(0.0f, 0.0f, 0.0f, 1.0f), device, this)
374 // Allow hotloading of textures
375 RegisterFileReloadFunc(ReloadChangedFileCB, this);
377 m_HasS3TC =
378 m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGB_UNORM) &&
379 m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGBA_UNORM) &&
380 m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC2_UNORM) &&
381 m_Device->IsTextureFormatSupported(Renderer::Backend::Format::BC3_UNORM);
384 ~CTextureManagerImpl()
386 UnregisterFileReloadFunc(ReloadChangedFileCB, this);
389 const CTexturePtr& GetErrorTexture()
391 return m_ErrorTexture.GetTexture();
394 const CTexturePtr& GetWhiteTexture()
396 return m_WhiteTexture.GetTexture();
399 const CTexturePtr& GetTransparentTexture()
401 return m_TransparentTexture.GetTexture();
404 const CTexturePtr& GetAlphaGradientTexture()
406 return m_AlphaGradientTexture.GetTexture();
409 const CTexturePtr& GetBlackTextureCube()
411 return m_BlackTextureCube.GetTexture();
415 * See CTextureManager::CreateTexture
417 CTexturePtr CreateTexture(const CTextureProperties& props)
419 // Construct a new default texture with the given properties to use as the search key
420 CTexturePtr texture(new CTexture(
421 nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture(), props, this));
423 // Try to find an existing texture with the given properties
424 TextureCache::iterator it = m_TextureCache.find(texture);
425 if (it != m_TextureCache.end())
426 return *it;
428 // Can't find an existing texture - finish setting up this new texture
429 texture->m_Self = texture;
430 m_TextureCache.insert(texture);
431 m_HotloadFiles[props.m_Path].insert(texture);
433 return texture;
436 CTexturePtr WrapBackendTexture(
437 std::unique_ptr<Renderer::Backend::ITexture> backendTexture)
439 ENSURE(backendTexture);
440 Renderer::Backend::ITexture* fallback = backendTexture.get();
442 CTextureProperties props(VfsPath{});
443 CTexturePtr texture(new CTexture(
444 std::move(backendTexture), fallback, props, this));
445 texture->m_State = CTexture::UPLOADED;
446 texture->m_Self = texture;
447 return texture;
451 * Load the given file into the texture object and upload it to OpenGL.
452 * Assumes the file already exists.
454 void LoadTexture(const CTexturePtr& texture, const VfsPath& path)
456 PROFILE2("load texture");
457 PROFILE2_ATTR("name: %ls", path.string().c_str());
459 std::shared_ptr<u8> fileData;
460 size_t fileSize;
461 const Status loadStatus = m_VFS->LoadFile(path, fileData, fileSize);
462 if (loadStatus != INFO::OK)
464 LOGERROR("Texture failed to load; \"%s\" %s",
465 texture->m_Properties.m_Path.string8(), GetStatusAsString(loadStatus).c_str());
466 texture->ResetBackendTexture(
467 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
468 texture->m_TextureData.reset();
469 return;
472 texture->m_TextureData = std::make_unique<Tex>();
473 Tex& textureData = *texture->m_TextureData;
474 const Status decodeStatus = textureData.decode(fileData, fileSize);
475 if (decodeStatus != INFO::OK)
477 LOGERROR("Texture failed to decode; \"%s\" %s",
478 texture->m_Properties.m_Path.string8(), GetStatusAsString(decodeStatus).c_str());
479 texture->ResetBackendTexture(
480 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
481 texture->m_TextureData.reset();
482 return;
485 if (!is_pow2(textureData.m_Width) || !is_pow2(textureData.m_Height))
487 LOGERROR("Texture should have width and height be power of two; \"%s\" %zux%zu",
488 texture->m_Properties.m_Path.string8(), textureData.m_Width, textureData.m_Height);
489 texture->ResetBackendTexture(
490 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
491 texture->m_TextureData.reset();
492 return;
495 // Initialise base color from the texture
496 texture->m_BaseColor = textureData.get_average_color();
498 Renderer::Backend::Format format = Renderer::Backend::Format::UNDEFINED;
499 if (texture->m_Properties.m_FormatOverride != Renderer::Backend::Format::UNDEFINED)
501 format = texture->m_Properties.m_FormatOverride;
502 // TODO: it'd be good to remove the override hack and provide information
503 // via XML.
504 ENSURE((textureData.m_Flags & TEX_DXT) == 0);
505 if (format == Renderer::Backend::Format::A8_UNORM)
507 ENSURE(textureData.m_Bpp == 8 && (textureData.m_Flags & TEX_GREY));
509 else if (format == Renderer::Backend::Format::R8G8B8A8_UNORM)
511 ENSURE(textureData.m_Bpp == 32 && (textureData.m_Flags & TEX_ALPHA));
513 else
514 debug_warn("Unsupported format override.");
516 else
518 format = ChooseFormatAndTransformTextureDataIfNeeded(m_Device, textureData, m_HasS3TC);
521 if (format == Renderer::Backend::Format::UNDEFINED)
523 LOGERROR("Texture failed to choose format; \"%s\"", texture->m_Properties.m_Path.string8());
524 texture->ResetBackendTexture(
525 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
526 texture->m_TextureData.reset();
527 return;
530 const uint32_t width = texture->m_TextureData->m_Width;
531 const uint32_t height = texture->m_TextureData->m_Height ;
532 const uint32_t MIPLevelCount = texture->m_TextureData->GetMIPLevels().size();
533 texture->m_BaseLevelOffset = 0;
535 Renderer::Backend::Sampler::Desc defaultSamplerDesc =
536 Renderer::Backend::Sampler::MakeDefaultSampler(
537 Renderer::Backend::Sampler::Filter::LINEAR,
538 Renderer::Backend::Sampler::AddressMode::REPEAT);
540 defaultSamplerDesc.addressModeU = texture->m_Properties.m_AddressModeU;
541 defaultSamplerDesc.addressModeV = texture->m_Properties.m_AddressModeV;
542 if (texture->m_Properties.m_AnisotropicFilterEnabled && m_Device->GetCapabilities().anisotropicFiltering)
544 int maxAnisotropy = 1;
545 CFG_GET_VAL("textures.maxanisotropy", maxAnisotropy);
546 const int allowedValues[] = {2, 4, 8, 16};
547 if (std::find(std::begin(allowedValues), std::end(allowedValues), maxAnisotropy) != std::end(allowedValues))
549 defaultSamplerDesc.anisotropyEnabled = true;
550 defaultSamplerDesc.maxAnisotropy = maxAnisotropy;
554 if (!texture->m_Properties.m_IgnoreQuality)
556 int quality = 2;
557 CFG_GET_VAL("textures.quality", quality);
558 if (quality == 1)
560 if (MIPLevelCount > 1 && std::min(width, height) > 8)
561 texture->m_BaseLevelOffset += 1;
563 else if (quality == 0)
565 if (MIPLevelCount > 2 && std::min(width, height) > 16)
566 texture->m_BaseLevelOffset += 2;
567 while (std::min(width >> texture->m_BaseLevelOffset, height >> texture->m_BaseLevelOffset) > 256 &&
568 MIPLevelCount > texture->m_BaseLevelOffset + 1)
570 texture->m_BaseLevelOffset += 1;
572 defaultSamplerDesc.mipFilter = Renderer::Backend::Sampler::Filter::NEAREST;
573 defaultSamplerDesc.anisotropyEnabled = false;
577 texture->m_BackendTexture = m_Device->CreateTexture2D(
578 texture->m_Properties.m_Path.string8().c_str(),
579 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
580 Renderer::Backend::ITexture::Usage::SAMPLED,
581 format, (width >> texture->m_BaseLevelOffset), (height >> texture->m_BaseLevelOffset),
582 defaultSamplerDesc, MIPLevelCount - texture->m_BaseLevelOffset);
586 * Set up some parameters for the loose cache filename code.
588 void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version)
590 // Hash the settings, so we won't use an old loose cache file if the
591 // settings have changed
592 CTextureConverter::Settings settings = GetConverterSettings(texture);
593 settings.Hash(hash);
595 // Arbitrary version number - change this if we update the code and
596 // need to invalidate old users' caches
597 version = 1;
601 * Attempts to load a cached version of a texture.
602 * If the texture is loaded (or there was an error), returns true.
603 * Otherwise, returns false to indicate the caller should generate the cached version.
605 bool TryLoadingCached(const CTexturePtr& texture)
607 MD5 hash;
608 u32 version;
609 PrepareCacheKey(texture, hash, version);
611 VfsPath loadPath;
612 Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath);
614 if (ret == INFO::OK)
616 // Found a cached texture - load it
617 LoadTexture(texture, loadPath);
618 return true;
620 else if (ret == INFO::SKIPPED)
622 // No cached version was found - we'll need to create it
623 return false;
625 else
627 ENSURE(ret < 0);
629 // No source file or archive cache was found, so we can't load the
630 // real texture at all - return the error texture instead
631 LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8());
632 texture->ResetBackendTexture(
633 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
634 return true;
639 * Initiates an asynchronous conversion process, from the texture's
640 * source file to the corresponding loose cache file.
642 void ConvertTexture(const CTexturePtr& texture)
644 const VfsPath sourcePath = texture->m_Properties.m_Path;
646 PROFILE2("convert texture");
647 PROFILE2_ATTR("name: %ls", sourcePath.string().c_str());
649 MD5 hash;
650 u32 version;
651 PrepareCacheKey(texture, hash, version);
652 const VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version);
654 CTextureConverter::Settings settings = GetConverterSettings(texture);
656 m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings);
659 bool TextureExists(const VfsPath& path) const
661 return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK ||
662 m_VFS->GetFileInfo(path, 0) == INFO::OK;
665 bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath)
667 archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath);
669 CTextureProperties textureProps(sourcePath);
670 CTexturePtr texture = CreateTexture(textureProps);
671 CTextureConverter::Settings settings = GetConverterSettings(texture);
673 if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings))
674 return false;
676 while (true)
678 CTexturePtr textureOut;
679 VfsPath dest;
680 bool ok;
681 if (m_TextureConverter.Poll(textureOut, dest, ok))
682 return ok;
684 std::this_thread::sleep_for(std::chrono::microseconds(1));
688 VfsPath GetCachedPath(const VfsPath& path) const
690 return m_CacheLoader.ArchiveCachePath(path);
693 bool MakeProgress()
695 // Process any completed conversion tasks
697 CTexturePtr texture;
698 VfsPath dest;
699 bool ok;
700 if (m_TextureConverter.Poll(texture, dest, ok))
702 if (ok)
704 LoadTexture(texture, dest);
706 else
708 LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8());
709 texture->ResetBackendTexture(
710 nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
712 texture->m_State = CTexture::LOADED;
713 return true;
717 // We'll only push new conversion requests if it's not already busy
718 bool converterBusy = m_TextureConverter.IsBusy();
720 if (!converterBusy)
722 // Look for all high-priority textures needing conversion.
723 // (Iterating over all textures isn't optimally efficient, but it
724 // doesn't seem to be a problem yet and it's simpler than maintaining
725 // multiple queues.)
726 for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
728 if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING)
730 // Start converting this texture
731 (*it)->m_State = CTexture::HIGH_IS_CONVERTING;
732 ConvertTexture(*it);
733 return true;
738 // Try loading prefetched textures from their cache
739 for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
741 if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING)
743 if (TryLoadingCached(*it))
745 (*it)->m_State = CTexture::LOADED;
747 else
749 (*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING;
751 return true;
755 // If we've got nothing better to do, then start converting prefetched textures.
756 if (!converterBusy)
758 for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
760 if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING)
762 (*it)->m_State = CTexture::PREFETCH_IS_CONVERTING;
763 ConvertTexture(*it);
764 return true;
769 return false;
772 bool MakeUploadProgress(
773 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
775 if (!m_PredefinedTexturesUploaded)
777 m_DefaultTexture.Upload(deviceCommandContext);
778 m_ErrorTexture.Upload(deviceCommandContext);
779 m_WhiteTexture.Upload(deviceCommandContext);
780 m_TransparentTexture.Upload(deviceCommandContext);
781 m_AlphaGradientTexture.Upload(deviceCommandContext);
782 m_BlackTextureCube.Upload(deviceCommandContext);
783 m_PredefinedTexturesUploaded = true;
784 return true;
786 return false;
790 * Compute the conversion settings that apply to a given texture, by combining
791 * the textures.xml files from its directory and all parent directories
792 * (up to the VFS root).
794 CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture)
796 fs::wpath srcPath = texture->m_Properties.m_Path.string();
798 std::vector<CTextureConverter::SettingsFile*> files;
799 VfsPath p;
800 for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it)
802 VfsPath settingsPath = p / "textures.xml";
803 m_HotloadFiles[settingsPath].insert(texture);
804 CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath);
805 if (f)
806 files.push_back(f);
807 p = p / GetWstringFromWpath(*it);
809 return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files);
813 * Return the (cached) settings file with the given filename,
814 * or NULL if it doesn't exist.
816 CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path)
818 SettingsFilesMap::iterator it = m_SettingsFiles.find(path);
819 if (it != m_SettingsFiles.end())
820 return it->second.get();
822 if (m_VFS->GetFileInfo(path, NULL) >= 0)
824 std::shared_ptr<CTextureConverter::SettingsFile> settings(m_TextureConverter.LoadSettings(path));
825 m_SettingsFiles.insert(std::make_pair(path, settings));
826 return settings.get();
828 else
830 m_SettingsFiles.insert(std::make_pair(path, std::shared_ptr<CTextureConverter::SettingsFile>()));
831 return NULL;
835 static Status ReloadChangedFileCB(void* param, const VfsPath& path)
837 return static_cast<CTextureManagerImpl*>(param)->ReloadChangedFile(path);
840 Status ReloadChangedFile(const VfsPath& path)
842 // Uncache settings file, if this is one
843 m_SettingsFiles.erase(path);
845 // Find all textures using this file
846 HotloadFilesMap::iterator files = m_HotloadFiles.find(path);
847 if (files != m_HotloadFiles.end())
849 // Flag all textures using this file as needing reloading
850 for (std::set<std::weak_ptr<CTexture>>::iterator it = files->second.begin(); it != files->second.end(); ++it)
852 if (std::shared_ptr<CTexture> texture = it->lock())
854 texture->m_State = CTexture::UNLOADED;
855 texture->ResetBackendTexture(
856 nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
857 texture->m_TextureData.reset();
862 return INFO::OK;
865 void ReloadAllTextures()
867 for (const CTexturePtr& texture : m_TextureCache)
869 texture->m_State = CTexture::UNLOADED;
870 texture->ResetBackendTexture(
871 nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
872 texture->m_TextureData.reset();
876 size_t GetBytesUploaded() const
878 size_t size = 0;
879 for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
880 size += (*it)->GetUploadedSize();
881 return size;
884 void OnQualityChanged()
886 ReloadAllTextures();
889 private:
890 PIVFS m_VFS;
891 CCacheLoader m_CacheLoader;
892 Renderer::Backend::IDevice* m_Device = nullptr;
893 CTextureConverter m_TextureConverter;
895 CSingleColorTexture m_DefaultTexture;
896 CSingleColorTexture m_ErrorTexture;
897 CSingleColorTexture m_WhiteTexture;
898 CSingleColorTexture m_TransparentTexture;
899 CGradientTexture m_AlphaGradientTexture;
900 CSingleColorTextureCube m_BlackTextureCube;
901 bool m_PredefinedTexturesUploaded = false;
903 // Cache of all loaded textures
904 using TextureCache =
905 std::unordered_set<CTexturePtr, TPhash, TPequal_to>;
906 TextureCache m_TextureCache;
907 // TODO: we ought to expire unused textures from the cache eventually
909 // Store the set of textures that need to be reloaded when the given file
910 // (a source file or settings.xml) is modified
911 using HotloadFilesMap =
912 std::unordered_map<VfsPath, std::set<std::weak_ptr<CTexture>, std::owner_less<std::weak_ptr<CTexture>>>>;
913 HotloadFilesMap m_HotloadFiles;
915 // Cache for the conversion settings files
916 using SettingsFilesMap =
917 std::unordered_map<VfsPath, std::shared_ptr<CTextureConverter::SettingsFile>>;
918 SettingsFilesMap m_SettingsFiles;
920 bool m_HasS3TC = false;
923 CTexture::CTexture(
924 std::unique_ptr<Renderer::Backend::ITexture> texture,
925 Renderer::Backend::ITexture* fallback,
926 const CTextureProperties& props, CTextureManagerImpl* textureManager) :
927 m_BackendTexture(std::move(texture)), m_FallbackBackendTexture(fallback),
928 m_BaseColor(0), m_State(UNLOADED), m_Properties(props),
929 m_TextureManager(textureManager)
933 CTexture::~CTexture() = default;
935 void CTexture::UploadBackendTextureIfNeeded(
936 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
938 if (IsUploaded())
939 return;
941 if (!IsLoaded())
942 TryLoad();
944 if (!IsLoaded())
945 return;
946 else if (!m_TextureData || !m_BackendTexture)
948 ResetBackendTexture(nullptr, m_TextureManager->GetErrorTexture()->GetBackendTexture());
949 m_State = UPLOADED;
950 return;
953 m_UploadedSize = 0;
954 for (uint32_t textureDataLevel = m_BaseLevelOffset, level = 0; textureDataLevel < m_TextureData->GetMIPLevels().size(); ++textureDataLevel)
956 const Tex::MIPLevel& levelData = m_TextureData->GetMIPLevels()[textureDataLevel];
957 deviceCommandContext->UploadTexture(m_BackendTexture.get(), m_BackendTexture->GetFormat(),
958 levelData.data, levelData.dataSize, level++);
959 m_UploadedSize += levelData.dataSize;
961 m_TextureData.reset();
963 m_State = UPLOADED;
966 Renderer::Backend::ITexture* CTexture::GetBackendTexture()
968 return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
971 const Renderer::Backend::ITexture* CTexture::GetBackendTexture() const
973 return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
976 bool CTexture::TryLoad()
978 // If we haven't started loading, then try loading, and if that fails then request conversion.
979 // If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority.
980 if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING)
982 if (std::shared_ptr<CTexture> self = m_Self.lock())
984 if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self))
985 m_State = LOADED;
986 else
987 m_State = HIGH_NEEDS_CONVERTING;
991 return IsLoaded() || IsUploaded();
994 void CTexture::Prefetch()
996 if (m_State == UNLOADED)
998 if (std::shared_ptr<CTexture> self = m_Self.lock())
1000 m_State = PREFETCH_NEEDS_LOADING;
1005 void CTexture::ResetBackendTexture(
1006 std::unique_ptr<Renderer::Backend::ITexture> backendTexture,
1007 Renderer::Backend::ITexture* fallbackBackendTexture)
1009 m_BackendTexture = std::move(backendTexture);
1010 m_FallbackBackendTexture = fallbackBackendTexture;
1013 size_t CTexture::GetWidth() const
1015 return GetBackendTexture()->GetWidth();
1018 size_t CTexture::GetHeight() const
1020 return GetBackendTexture()->GetHeight();
1023 bool CTexture::HasAlpha() const
1025 const Renderer::Backend::Format format = GetBackendTexture()->GetFormat();
1026 return
1027 format == Renderer::Backend::Format::A8_UNORM ||
1028 format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
1029 format == Renderer::Backend::Format::BC1_RGBA_UNORM ||
1030 format == Renderer::Backend::Format::BC2_UNORM ||
1031 format == Renderer::Backend::Format::BC3_UNORM;
1034 u32 CTexture::GetBaseColor() const
1036 return m_BaseColor;
1039 size_t CTexture::GetUploadedSize() const
1041 return m_UploadedSize;
1044 // CTextureManager: forward all calls to impl:
1046 CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, Renderer::Backend::IDevice* device) :
1047 m(new CTextureManagerImpl(vfs, highQuality, device))
1051 CTextureManager::~CTextureManager()
1053 delete m;
1056 CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props)
1058 return m->CreateTexture(props);
1061 CTexturePtr CTextureManager::WrapBackendTexture(
1062 std::unique_ptr<Renderer::Backend::ITexture> backendTexture)
1064 return m->WrapBackendTexture(std::move(backendTexture));
1067 bool CTextureManager::TextureExists(const VfsPath& path) const
1069 return m->TextureExists(path);
1072 const CTexturePtr& CTextureManager::GetErrorTexture()
1074 return m->GetErrorTexture();
1077 const CTexturePtr& CTextureManager::GetWhiteTexture()
1079 return m->GetWhiteTexture();
1082 const CTexturePtr& CTextureManager::GetTransparentTexture()
1084 return m->GetTransparentTexture();
1087 const CTexturePtr& CTextureManager::GetAlphaGradientTexture()
1089 return m->GetAlphaGradientTexture();
1092 const CTexturePtr& CTextureManager::GetBlackTextureCube()
1094 return m->GetBlackTextureCube();
1097 bool CTextureManager::MakeProgress()
1099 return m->MakeProgress();
1102 bool CTextureManager::MakeUploadProgress(
1103 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
1105 return m->MakeUploadProgress(deviceCommandContext);
1108 bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath)
1110 return m->GenerateCachedTexture(path, outputPath);
1113 VfsPath CTextureManager::GetCachedPath(const VfsPath& path) const
1115 return m->GetCachedPath(path);
1118 size_t CTextureManager::GetBytesUploaded() const
1120 return m->GetBytesUploaded();
1123 void CTextureManager::OnQualityChanged()
1125 m->OnQualityChanged();