Skip to content

Commit 8d29aa5

Browse files
GLTFResourceManager: shard texture allocations to reduce contention
1 parent 1ef6d21 commit 8d29aa5

File tree

2 files changed

+82
-46
lines changed

2 files changed

+82
-46
lines changed

AssetLoader/interface/GLTFResourceManager.hpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2025 Diligent Graphics LLC
2+
* Copyright 2019-2026 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -146,6 +146,11 @@ class ResourceManager final : public ObjectBase<IObject>
146146
/// The number of elements in pTexAtlasCIs array.
147147
Uint32 NumTexAtlases = 0;
148148

149+
/// The number of shards in texture allocation map.
150+
/// Shards are used to reduce contention when allocating texture space and finding texture allocations by cache ID
151+
/// from multiple threads simultaneously. The more shards, the less contention, but the more memory is used by the map.
152+
Uint32 NumTextureAllocationShards = 1;
153+
149154
/// Default texture atlas description that is used to create texture
150155
/// atlas not explicitly specified in pTexAtlasCIs.
151156
/// If DefaultAtlasDesc.Desc.Type is Diligent::RESOURCE_DIM_UNDEFINED,
@@ -490,9 +495,22 @@ class ResourceManager final : public ObjectBase<IObject>
490495
std::vector<IVertexPool*> m_VertexPoolSnapshot;
491496
std::vector<IBufferSuballocator*> m_IndexAllocatorSnapshot;
492497

493-
using TexAllocationsHashMapType = std::unordered_map<std::string, RefCntWeakPtr<ITextureAtlasSuballocation>>;
494-
std::shared_mutex m_TexAllocationsMtx;
495-
TexAllocationsHashMapType m_TexAllocations;
498+
class TexAllocations
499+
{
500+
public:
501+
RefCntAutoPtr<ITextureAtlasSuballocation> Find(const char* CacheId);
502+
503+
void Add(const char* CacheId, RefCntAutoPtr<ITextureAtlasSuballocation> pSuballocation);
504+
505+
static size_t GetShardIndex(const char* CacheId, size_t ShardCount);
506+
507+
private:
508+
using TexAllocationsHashMapType = std::unordered_map<std::string, RefCntWeakPtr<ITextureAtlasSuballocation>>;
509+
510+
std::shared_mutex m_Mtx;
511+
TexAllocationsHashMapType m_Map;
512+
};
513+
std::vector<TexAllocations> m_TexAllocations;
496514

497515
std::vector<StateTransitionDesc> m_Barriers;
498516
};

AssetLoader/src/GLTFResourceManager.cpp

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2025 Diligent Graphics LLC
2+
* Copyright 2019-2026 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -62,7 +62,8 @@ ResourceManager::ResourceManager(IReferenceCounters* pRefCounters,
6262
m_DefaultVertPoolDesc{CI.DefaultPoolDesc},
6363
m_DefaultAtlasName{CI.DefaultAtlasDesc.Desc.Name != nullptr ? CI.DefaultAtlasDesc.Desc.Name : "GLTF texture atlas"},
6464
m_DefaultAtlasDesc{CI.DefaultAtlasDesc},
65-
m_IndexAllocatorCI{CI.IndexAllocatorCI}
65+
m_IndexAllocatorCI{CI.IndexAllocatorCI},
66+
m_TexAllocations{CI.NumTextureAllocationShards != 0 ? CI.NumTextureAllocationShards : 1}
6667
{
6768
m_DefaultVertPoolDesc.Name = m_DefaultVertPoolName.c_str();
6869
m_DefaultAtlasDesc.Desc.Name = m_DefaultAtlasName.c_str();
@@ -122,45 +123,75 @@ ResourceManager::ResourceManager(IReferenceCounters* pRefCounters,
122123
}
123124

124125

125-
RefCntAutoPtr<ITextureAtlasSuballocation> ResourceManager::FindTextureAllocation(const char* CacheId)
126+
RefCntAutoPtr<ITextureAtlasSuballocation> ResourceManager::TexAllocations::Find(const char* CacheId)
126127
{
127-
if (CacheId != nullptr && *CacheId != 0)
128+
if (CacheId == nullptr || *CacheId == 0)
129+
return {};
130+
131+
bool AllocationExpired = false;
132+
133+
// First, try to find the allocation with a shared lock
128134
{
129-
bool AllocationExpired = false;
135+
std::shared_lock<std::shared_mutex> SharedLock{m_Mtx};
130136

131-
// First, try to find the allocation with a shared lock
137+
auto it = m_Map.find(CacheId);
138+
if (it != m_Map.end())
132139
{
133-
std::shared_lock<std::shared_mutex> SharedLock{m_TexAllocationsMtx};
134-
135-
auto it = m_TexAllocations.find(CacheId);
136-
if (it != m_TexAllocations.end())
137-
{
138-
if (RefCntAutoPtr<ITextureAtlasSuballocation> pAllocation = it->second.Lock())
139-
return pAllocation;
140-
else
141-
AllocationExpired = true;
142-
}
140+
if (RefCntAutoPtr<ITextureAtlasSuballocation> pAllocation = it->second.Lock())
141+
return pAllocation;
142+
else
143+
AllocationExpired = true;
143144
}
145+
}
144146

145-
// If the allocation was found but has expired, acquire a unique lock to erase it
146-
if (AllocationExpired)
147-
{
148-
std::unique_lock<std::shared_mutex> UniqueLock{m_TexAllocationsMtx};
147+
// If the allocation was found but has expired, acquire a unique lock to erase it
148+
if (AllocationExpired)
149+
{
150+
std::unique_lock<std::shared_mutex> UniqueLock{m_Mtx};
149151

150-
auto it = m_TexAllocations.find(CacheId);
151-
if (it != m_TexAllocations.end())
152-
{
153-
if (RefCntAutoPtr<ITextureAtlasSuballocation> pAllocation = it->second.Lock())
154-
return pAllocation;
155-
else
156-
m_TexAllocations.erase(it);
157-
}
152+
auto it = m_Map.find(CacheId);
153+
if (it != m_Map.end())
154+
{
155+
if (RefCntAutoPtr<ITextureAtlasSuballocation> pAllocation = it->second.Lock())
156+
return pAllocation;
157+
else
158+
m_Map.erase(it);
158159
}
159160
}
160161

161162
return {};
162163
}
163164

165+
void ResourceManager::TexAllocations::Add(const char* CacheId, RefCntAutoPtr<ITextureAtlasSuballocation> pSuballocation)
166+
{
167+
if (CacheId == nullptr || *CacheId == 0)
168+
return;
169+
170+
std::unique_lock<std::shared_mutex> UniqueLock{m_Mtx};
171+
// Note that the same allocation may potentially be created by more
172+
// than one thread if it has not been found in the cache originally
173+
auto [it, inserted] = m_Map.emplace(CacheId, pSuballocation);
174+
if (!inserted)
175+
{
176+
if (auto pExistingAllocation = it->second.Lock())
177+
pSuballocation = pExistingAllocation;
178+
else
179+
it->second = pSuballocation;
180+
}
181+
}
182+
183+
size_t ResourceManager::TexAllocations::GetShardIndex(const char* CacheId, size_t ShardCount)
184+
{
185+
if (CacheId == nullptr || *CacheId == 0)
186+
return 0;
187+
return ShardCount > 1 ? CStringHash<Char>{}(CacheId) % ShardCount : 0;
188+
}
189+
190+
RefCntAutoPtr<ITextureAtlasSuballocation> ResourceManager::FindTextureAllocation(const char* CacheId)
191+
{
192+
return m_TexAllocations[TexAllocations::GetShardIndex(CacheId, m_TexAllocations.size())].Find(CacheId);
193+
}
194+
164195
RefCntAutoPtr<ITextureAtlasSuballocation> ResourceManager::AllocateTextureSpace(
165196
TEXTURE_FORMAT Fmt,
166197
Uint32 Width,
@@ -232,20 +263,7 @@ RefCntAutoPtr<ITextureAtlasSuballocation> ResourceManager::AllocateTextureSpace(
232263
pAllocation->SetUserData(pUserData);
233264
}
234265

235-
if (CacheId != nullptr && *CacheId != 0)
236-
{
237-
std::unique_lock<std::shared_mutex> UniqueLock{m_TexAllocationsMtx};
238-
// Note that the same allocation may potentially be created by more
239-
// than one thread if it has not been found in the cache originally
240-
auto [it, inserted] = m_TexAllocations.emplace(CacheId, pAllocation);
241-
if (!inserted)
242-
{
243-
if (auto pExistingAllocation = it->second.Lock())
244-
pAllocation = pExistingAllocation;
245-
else
246-
it->second = pAllocation;
247-
}
248-
}
266+
m_TexAllocations[TexAllocations::GetShardIndex(CacheId, m_TexAllocations.size())].Add(CacheId, pAllocation);
249267

250268
return pAllocation;
251269
}

0 commit comments

Comments
 (0)