Skip to content

Commit 62bfc32

Browse files
GLTFLoader: use shared mutex in texture cache for better concurrency
1 parent be9bdb4 commit 62bfc32

File tree

2 files changed

+94
-37
lines changed

2 files changed

+94
-37
lines changed

AssetLoader/interface/GLTFLoader.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
#include <array>
3535
#include <cfloat>
3636
#include <unordered_map>
37-
#include <mutex>
37+
#include <shared_mutex>
3838
#include <atomic>
3939
#include <functional>
4040
#include <string>
@@ -723,7 +723,7 @@ InputLayoutDescX VertexAttributesToInputLayout(const VertexAttributeDesc* pAttri
723723

724724
struct TextureCacheType
725725
{
726-
std::mutex TexturesMtx;
726+
std::shared_mutex TexturesMtx;
727727

728728
std::unordered_map<std::string, RefCntWeakPtr<ITexture>> Textures;
729729
};

AssetLoader/src/GLTFLoader.cpp

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -749,25 +749,57 @@ Uint32 Model::AddTexture(IRenderDevice* pDevice,
749749
}
750750
else if (pTextureCache != nullptr)
751751
{
752-
std::lock_guard<std::mutex> Lock{pTextureCache->TexturesMtx};
752+
bool TextureExpired = false;
753753

754-
auto it = pTextureCache->Textures.find(CacheId);
755-
if (it != pTextureCache->Textures.end())
754+
// First try with shared lock
756755
{
757-
TexInfo.pTexture = it->second.Lock();
758-
if (!TexInfo.pTexture)
756+
std::shared_lock<std::shared_mutex> SharedLock{pTextureCache->TexturesMtx};
757+
758+
auto it = pTextureCache->Textures.find(CacheId);
759+
if (it != pTextureCache->Textures.end())
759760
{
760-
// Image width and height (or pixel_type for dds/ktx) are initialized by LoadImageData()
761-
// if the texture is found in the cache.
762-
if ((Image.Width > 0 && Image.Height > 0) ||
763-
(Image.FileFormat == IMAGE_FILE_FORMAT_DDS || Image.FileFormat == IMAGE_FILE_FORMAT_KTX))
761+
TexInfo.pTexture = it->second.Lock();
762+
if (!TexInfo.pTexture)
764763
{
765-
UNEXPECTED("Stale textures should not be found in the texture cache because we hold strong references. "
766-
"This must be an unexpected effect of loading resources from multiple threads or a bug.");
764+
// Image width and height (or pixel_type for dds/ktx) are initialized by LoadImageData()
765+
// if the texture is found in the cache.
766+
if ((Image.Width > 0 && Image.Height > 0) ||
767+
(Image.FileFormat == IMAGE_FILE_FORMAT_DDS || Image.FileFormat == IMAGE_FILE_FORMAT_KTX))
768+
{
769+
UNEXPECTED("Stale textures should not be found in the texture cache because we hold strong references. "
770+
"This must be an unexpected effect of loading resources from multiple threads or a bug.");
771+
}
772+
else
773+
{
774+
TextureExpired = true;
775+
}
767776
}
768-
else
777+
}
778+
}
779+
780+
if (TextureExpired)
781+
{
782+
// Upgrade to exclusive lock to remove the expried reference
783+
std::unique_lock<std::shared_mutex> UniqueLock{pTextureCache->TexturesMtx};
784+
785+
auto it = pTextureCache->Textures.find(CacheId);
786+
if (it != pTextureCache->Textures.end())
787+
{
788+
TexInfo.pTexture = it->second.Lock();
789+
if (!TexInfo.pTexture)
769790
{
770-
pTextureCache->Textures.erase(it);
791+
// Image width and height (or pixel_type for dds/ktx) are initialized by LoadImageData()
792+
// if the texture is found in the cache.
793+
if ((Image.Width > 0 && Image.Height > 0) ||
794+
(Image.FileFormat == IMAGE_FILE_FORMAT_DDS || Image.FileFormat == IMAGE_FILE_FORMAT_KTX))
795+
{
796+
UNEXPECTED("Stale textures should not be found in the texture cache because we hold strong references. "
797+
"This must be an unexpected effect of loading resources from multiple threads or a bug.");
798+
}
799+
else
800+
{
801+
pTextureCache->Textures.erase(it);
802+
}
771803
}
772804
}
773805
}
@@ -914,7 +946,7 @@ Uint32 Model::AddTexture(IRenderDevice* pDevice,
914946

915947
if (TexInfo.pTexture && pTextureCache != nullptr)
916948
{
917-
std::lock_guard<std::mutex> Lock{pTextureCache->TexturesMtx};
949+
std::unique_lock<std::shared_mutex> UniqueLock{pTextureCache->TexturesMtx};
918950
pTextureCache->Textures.emplace(CacheId, TexInfo.pTexture);
919951
}
920952
}
@@ -1672,33 +1704,58 @@ bool LoadImageData(tinygltf::Image* gltf_image,
16721704
{
16731705
TextureCacheType& TexCache = *pLoaderData->pTextureCache;
16741706

1675-
std::lock_guard<std::mutex> Lock{TexCache.TexturesMtx};
1707+
RefCntAutoPtr<ITexture> pTexture;
1708+
bool TextureExpired = false;
16761709

1677-
auto it = TexCache.Textures.find(CacheId);
1678-
if (it != TexCache.Textures.end())
1710+
// Try with shared lock first
16791711
{
1680-
if (RefCntAutoPtr<ITexture> pTexture = it->second.Lock())
1681-
{
1682-
const TextureDesc& TexDesc = pTexture->GetDesc();
1683-
const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);
1712+
std::shared_lock<std::shared_mutex> SharedLock{TexCache.TexturesMtx};
16841713

1685-
gltf_image->width = TexDesc.Width;
1686-
gltf_image->height = TexDesc.Height;
1687-
gltf_image->component = FmtAttribs.NumComponents;
1688-
gltf_image->bits = FmtAttribs.ComponentSize * 8;
1689-
gltf_image->pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
1714+
auto it = TexCache.Textures.find(CacheId);
1715+
if (it != TexCache.Textures.end())
1716+
{
1717+
pTexture = it->second.Lock();
1718+
if (!pTexture)
1719+
{
1720+
// Texture is stale
1721+
TextureExpired = true;
1722+
}
1723+
}
1724+
}
16901725

1691-
// Keep strong reference to ensure the texture is alive (second time, but that's fine).
1692-
pLoaderData->TexturesHold.emplace_back(std::move(pTexture));
1726+
if (TextureExpired)
1727+
{
1728+
// Upgrade to exclusive lock to remove stale texture
1729+
std::unique_lock<std::shared_mutex> UniqueLock{TexCache.TexturesMtx};
16931730

1694-
return true;
1695-
}
1696-
else
1731+
auto it = TexCache.Textures.find(CacheId);
1732+
if (it != TexCache.Textures.end())
16971733
{
1698-
// Texture is stale - remove it from the cache
1699-
TexCache.Textures.erase(it);
1734+
pTexture = it->second.Lock();
1735+
if (!pTexture)
1736+
{
1737+
// Remove stale texture from the cache
1738+
TexCache.Textures.erase(it);
1739+
}
17001740
}
17011741
}
1742+
1743+
if (pTexture)
1744+
{
1745+
const TextureDesc& TexDesc = pTexture->GetDesc();
1746+
const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);
1747+
1748+
gltf_image->width = TexDesc.Width;
1749+
gltf_image->height = TexDesc.Height;
1750+
gltf_image->component = FmtAttribs.NumComponents;
1751+
gltf_image->bits = FmtAttribs.ComponentSize * 8;
1752+
gltf_image->pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
1753+
1754+
// Keep strong reference to ensure the texture is alive (second time, but that's fine).
1755+
pLoaderData->TexturesHold.emplace_back(std::move(pTexture));
1756+
1757+
return true;
1758+
}
17021759
}
17031760
}
17041761

@@ -1829,7 +1886,7 @@ bool FileExists(const std::string& abs_filename, void* user_data)
18291886
}
18301887
else if (pLoaderData->pTextureCache != nullptr)
18311888
{
1832-
std::lock_guard<std::mutex> Lock{pLoaderData->pTextureCache->TexturesMtx};
1889+
std::shared_lock<std::shared_mutex> SharedLock{pLoaderData->pTextureCache->TexturesMtx};
18331890

18341891
auto it = pLoaderData->pTextureCache->Textures.find(CacheId.c_str());
18351892
if (it != pLoaderData->pTextureCache->Textures.end())
@@ -1868,7 +1925,7 @@ bool ReadWholeFile(std::vector<unsigned char>* out,
18681925
}
18691926
else if (pLoaderData->pTextureCache != nullptr)
18701927
{
1871-
std::lock_guard<std::mutex> Lock{pLoaderData->pTextureCache->TexturesMtx};
1928+
std::shared_lock<std::shared_mutex> SharedLock{pLoaderData->pTextureCache->TexturesMtx};
18721929

18731930
auto it = pLoaderData->pTextureCache->Textures.find(CacheId.c_str());
18741931
if (it != pLoaderData->pTextureCache->Textures.end())

0 commit comments

Comments
 (0)