1 /* Copyright (C) 2024 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"
26 #include "lib/file/vfs/vfs_tree.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"
37 #include "renderer/backend/IDevice.h"
38 #include "renderer/Renderer.h"
41 #include <boost/filesystem.hpp>
45 #include <unordered_map>
46 #include <unordered_set>
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
);
73 return Renderer::Backend::Format::BC1_RGBA_UNORM
;
75 return Renderer::Backend::Format::BC1_RGB_UNORM
;
77 return Renderer::Backend::Format::BC2_UNORM
;
79 return Renderer::Backend::Format::BC3_UNORM
;
81 LOGERROR("Unknown DXT compression.");
82 return Renderer::Backend::Format::UNDEFINED
;
86 textureData
.transform_to(textureData
.m_Flags
& ~TEX_DXT
);
89 switch (textureData
.m_Bpp
)
93 return Renderer::Backend::Format::L8_UNORM
;
96 if (device
->IsTextureFormatSupported(Renderer::Backend::Format::R8G8B8_UNORM
))
97 return Renderer::Backend::Format::R8G8B8_UNORM
;
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
;
106 return Renderer::Backend::Format::R8G8B8A8_UNORM
;
108 LOGERROR("Unsupported BPP: %zu", textureData
.m_Bpp
);
111 return Renderer::Backend::Format::UNDEFINED
;
114 } // anonymous namespace
116 class CPredefinedTexture
119 const CTexturePtr
& GetTexture()
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
;
137 CTexturePtr m_Texture
;
140 class CSingleColorTexture final
: public CPredefinedTexture
143 CSingleColorTexture(const CColor
& color
, Renderer::Backend::IDevice
* device
,
144 CTextureManagerImpl
* textureManager
)
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())
171 const SColor4ub color32
= m_Color
.AsSColor4ub();
172 // Construct 1x1 32-bit texture
180 deviceCommandContext
->UploadTexture(GetTexture()->GetBackendTexture(),
181 Renderer::Backend::Format::R8G8B8A8_UNORM
, data
, std::size(data
));
188 class CSingleColorTextureCube final
: public CPredefinedTexture
191 CSingleColorTextureCube(const CColor
& color
, Renderer::Backend::IDevice
* device
,
192 CTextureManagerImpl
* textureManager
)
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())
219 const SColor4ub color32
= m_Color
.AsSColor4ub();
220 // Construct 1x1 32-bit texture
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
);
241 class CGradientTexture final
: public CPredefinedTexture
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
),
274 CreateTexture(std::move(backendTexture
), textureManager
);
277 void Upload(Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
279 if (!GetTexture() || !GetTexture()->GetBackendTexture())
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);
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];
314 CColor m_ColorFrom
, m_ColorTo
;
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
);
331 std::size_t operator()(const CTexturePtr
& texture
) const
333 return this->operator()(texture
->m_Properties
);
337 std::hash
<Path
> m_PathHash
;
342 bool operator()(const CTextureProperties
& lhs
, const CTextureProperties
& rhs
) const
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
;
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);
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())
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
);
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
;
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
;
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();
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();
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();
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
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
));
514 debug_warn("Unsupported format override.");
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();
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
)
557 CFG_GET_VAL("textures.quality", quality
);
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
);
595 // Arbitrary version number - change this if we update the code and
596 // need to invalidate old users' caches
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
)
609 PrepareCacheKey(texture
, hash
, version
);
612 Status ret
= m_CacheLoader
.TryLoadingCached(texture
->m_Properties
.m_Path
, hash
, version
, loadPath
);
616 // Found a cached texture - load it
617 LoadTexture(texture
, loadPath
);
620 else if (ret
== INFO::SKIPPED
)
622 // No cached version was found - we'll need to create it
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());
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());
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
))
678 CTexturePtr textureOut
;
681 if (m_TextureConverter
.Poll(textureOut
, dest
, ok
))
684 std::this_thread::sleep_for(std::chrono::microseconds(1));
688 VfsPath
GetCachedPath(const VfsPath
& path
) const
690 return m_CacheLoader
.ArchiveCachePath(path
);
695 // Process any completed conversion tasks
700 if (m_TextureConverter
.Poll(texture
, dest
, ok
))
704 LoadTexture(texture
, dest
);
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
;
717 // We'll only push new conversion requests if it's not already busy
718 bool converterBusy
= m_TextureConverter
.IsBusy();
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
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
;
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
;
749 (*it
)->m_State
= CTexture::PREFETCH_NEEDS_CONVERTING
;
755 // If we've got nothing better to do, then start converting prefetched textures.
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
;
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;
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
;
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
);
807 p
= p
/ GetWstringFromWpath(*it
);
809 return m_TextureConverter
.ComputeSettings(GetWstringFromWpath(srcPath
.filename()), 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();
830 m_SettingsFiles
.insert(std::make_pair(path
, std::shared_ptr
<CTextureConverter::SettingsFile
>()));
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();
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
879 for (TextureCache::const_iterator it
= m_TextureCache
.begin(); it
!= m_TextureCache
.end(); ++it
)
880 size
+= (*it
)->GetUploadedSize();
884 void OnQualityChanged()
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
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;
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
)
946 else if (!m_TextureData
|| !m_BackendTexture
)
948 ResetBackendTexture(nullptr, m_TextureManager
->GetErrorTexture()->GetBackendTexture());
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();
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
))
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();
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
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()
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();