• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2017 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // ResourceMap:
7 //   An optimized resource map which packs the first set of allocated objects into a
8 //   flat array, and then falls back to an unordered map for the higher handle values.
9 //
10 
11 #ifndef LIBANGLE_RESOURCE_MAP_H_
12 #define LIBANGLE_RESOURCE_MAP_H_
13 
14 #include <mutex>
15 #include <type_traits>
16 
17 #include "common/SimpleMutex.h"
18 #include "libANGLE/angletypes.h"
19 
20 namespace gl
21 {
22 // The resource map needs to be internally synchronized for maps that are placed in the share group
23 // (as opposed to being private to the context) and that are accessed without holding the share
24 // group lock.
25 #if defined(ANGLE_ENABLE_SHARE_CONTEXT_LOCK)
26 using ResourceMapMutex = angle::SimpleMutex;
27 #else
28 using ResourceMapMutex = angle::NoOpMutex;
29 #endif
30 
31 template <bool NeedsLock = true>
32 struct SelectResourceMapMutex
33 {
34     using type = ResourceMapMutex;
35 };
36 
37 template <>
38 struct SelectResourceMapMutex<false>
39 {
40     using type = angle::NoOpMutex;
41 };
42 
43 // Analysis of ANGLE's traces as well as Chrome usage reveals the following:
44 //
45 // - Buffers: Typical applications use no more than 4000 ids.  Very few use over 6000.
46 // - Textures: Typical applications use no more than 1200 ids.  Very few use over 2000.
47 // - Samplers: Typical applications use no more than 50 ids.  Very few use over 100.
48 // - Shaders and Programs: Typical applications use no more than 500.  Very few use over 700.
49 // - Sync objects: Typical applications use no more than 500.  Very few use over 1500.
50 //
51 // For all the other shared types, the maximum used id is small (under 100).  For
52 // context-private parts (such as vertex arrays and queries), the id count can be in the
53 // thousands.
54 //
55 // The initial size of the flat resource map is based on the above, rounded up to a multiple of
56 // 1536.  Resource maps that need a lock (kNeedsLock == true) have the maximum flat size identical
57 // to initial flat size to avoid reallocation.  For others, the maps start small and can grow.
58 template <typename IDType>
59 struct ResourceMapParams
60 {
61     static constexpr size_t kInitialFlatResourcesSize = 192;
62 
63     // The following are private to the context and don't need a lock:
64     //
65     // - Vertex Array Objects
66     // - Framebuffer Objects
67     // - Transform Feedback Objects
68     // - Query Objects
69     //
70     // The rest of the maps need a lock.  However, only a select few are currently locked as API
71     // relevant to the rest of the types are protected by the share group lock.  As the share group
72     // lock is removed from more types, the resource map lock needs to be enabled for them.
73     static constexpr bool kNeedsLock = false;
74 };
75 template <>
76 struct ResourceMapParams<BufferID>
77 {
78     static constexpr size_t kInitialFlatResourcesSize = 6144;
79     static constexpr bool kNeedsLock                  = true;
80 };
81 template <>
82 struct ResourceMapParams<TextureID>
83 {
84     static constexpr size_t kInitialFlatResourcesSize = 1536;
85     static constexpr bool kNeedsLock                  = false;
86 };
87 template <>
88 struct ResourceMapParams<ShaderProgramID>
89 {
90     static constexpr size_t kInitialFlatResourcesSize = 1536;
91     static constexpr bool kNeedsLock                  = false;
92 };
93 template <>
94 struct ResourceMapParams<SyncID>
95 {
96     static constexpr size_t kInitialFlatResourcesSize = 1536;
97     static constexpr bool kNeedsLock                  = false;
98 };
99 // For the purpose of unit testing, |int| is considered private (not needing lock), and
100 // |unsigned int| is considered shared (needing lock).
101 template <>
102 struct ResourceMapParams<unsigned int>
103 {
104     static constexpr size_t kInitialFlatResourcesSize = 192;
105     static constexpr bool kNeedsLock                  = true;
106 };
107 
108 template <typename ResourceType, typename IDType>
109 class ResourceMap final : angle::NonCopyable
110 {
111   public:
112     ResourceMap();
113     ~ResourceMap();
114 
115     ANGLE_INLINE ResourceType *query(IDType id) const
116     {
117         GLuint handle = GetIDValue(id);
118         // No need for a lock when accessing the flat map.  Either the flat map is static, or
119         // locking is not needed.
120         static_assert(!kNeedsLock || kInitialFlatResourcesSize == kFlatResourcesLimit);
121 
122         if (handle < mFlatResourcesSize)
123         {
124             ResourceType *value = mFlatResources[handle];
125             return (value == InvalidPointer() ? nullptr : value);
126         }
127 
128         std::lock_guard<Mutex> lock(mMutex);
129 
130         auto it = mHashedResources.find(handle);
131         return (it == mHashedResources.end() ? nullptr : it->second);
132     }
133 
134     // Returns true if the handle was reserved. Not necessarily if the resource is created.
135     bool contains(IDType id) const;
136 
137     // Returns the element that was at this location.
138     bool erase(IDType id, ResourceType **resourceOut);
139 
140     void assign(IDType id, ResourceType *resource);
141 
142     // Clears the map.
143     void clear();
144 
145     using IndexAndResource = std::pair<GLuint, ResourceType *>;
146     using HashMap          = angle::HashMap<GLuint, ResourceType *>;
147 
148     class Iterator final
149     {
150       public:
151         bool operator==(const Iterator &other) const;
152         bool operator!=(const Iterator &other) const;
153         Iterator &operator++();
154         const IndexAndResource *operator->() const;
155         const IndexAndResource &operator*() const;
156 
157       private:
158         friend class ResourceMap;
159         Iterator(const ResourceMap &origin,
160                  GLuint flatIndex,
161                  typename HashMap::const_iterator hashIndex,
162                  bool skipNulls);
163         void updateValue();
164 
165         const ResourceMap &mOrigin;
166         GLuint mFlatIndex;
167         typename HashMap::const_iterator mHashIndex;
168         IndexAndResource mValue;
169         bool mSkipNulls;
170     };
171 
172   private:
173     friend class Iterator;
174     template <typename SameResourceType, typename SameIDType>
175     friend class UnsafeResourceMapIter;
176 
177     // null values represent reserved handles.
178     Iterator begin() const;
179     Iterator end() const;
180 
181     Iterator beginWithNull() const;
182     Iterator endWithNull() const;
183 
184     // Used by iterators and related functions only (due to lack of thread safety).
185     GLuint nextResource(size_t flatIndex, bool skipNulls) const;
186 
187     // constexpr methods cannot contain reinterpret_cast, so we need a static method.
188     static ResourceType *InvalidPointer();
189     static constexpr intptr_t kInvalidPointer = static_cast<intptr_t>(-1);
190 
191     static constexpr bool kNeedsLock = ResourceMapParams<IDType>::kNeedsLock;
192     using Mutex                      = typename SelectResourceMapMutex<kNeedsLock>::type;
193 
194     static constexpr size_t kInitialFlatResourcesSize =
195         ResourceMapParams<IDType>::kInitialFlatResourcesSize;
196 
197     // Experimental testing suggests that ~10k is a reasonable upper limit.
198     static constexpr size_t kFlatResourcesLimit = kNeedsLock ? kInitialFlatResourcesSize : 0x3000;
199     // Due to the way assign() is implemented, kFlatResourcesLimit / kInitialFlatResourcesSize must
200     // be a power of 2.
201     static_assert(kFlatResourcesLimit % kInitialFlatResourcesSize == 0);
202     static_assert(((kFlatResourcesLimit / kInitialFlatResourcesSize) &
203                    (kFlatResourcesLimit / kInitialFlatResourcesSize - 1)) == 0);
204 
205     size_t mFlatResourcesSize;
206     ResourceType **mFlatResources;
207 
208     // A map of GL objects indexed by object ID.
209     HashMap mHashedResources;
210 
211     // mFlatResources is allocated at object creation time, with a default size of
212     // |kInitialFlatResourcesSize|.  This is thread safe, because the allocation is done by the
213     // first context in the share group.  The flat map is allowed to grow up to
214     // |kFlatResourcesLimit|, but only for maps that don't need a lock (kNeedsLock == false).
215     //
216     // For maps that don't need a lock, this mutex is a no-op.  For those that do, the mutex is
217     // taken when allocating / deleting objects, as well as when accessing |mHashedResources|.
218     // Otherwise, access to the flat map (which never gets reallocated due to
219     // |kInitialFlatResourcesSize == kFlatResourcesLimit|) is lockless.  This latter is possible
220     // because the application is not allowed to gen/delete and bind the same ID in different
221     // threads at the same time.
222     //
223     // Note that because HandleAllocator is not yet thread-safe, glGen* and glDelete* functions
224     // cannot be free of the share group mutex yet.  To remove the share group mutex from those
225     // functions, likely the HandleAllocator class should be merged with this class, and the
226     // necessary insert/erase operations done under this same lock.
227     mutable Mutex mMutex;
228 };
229 
230 // A helper to retrieve the resource map iterators while being explicit that this is not thread
231 // safe.  Usage of iterators are limited to clean up on destruction and capture/replay, neither of
232 // which can race with other threads in their access to the resource map.
233 template <typename ResourceType, typename IDType>
234 class UnsafeResourceMapIter
235 {
236   public:
237     using ResMap = ResourceMap<ResourceType, IDType>;
238 
239     UnsafeResourceMapIter(const ResMap &resourceMap) : mResourceMap(resourceMap) {}
240 
241     typename ResMap::Iterator begin() const { return mResourceMap.begin(); }
242     typename ResMap::Iterator end() const { return mResourceMap.end(); }
243 
244     typename ResMap::Iterator beginWithNull() const { return mResourceMap.beginWithNull(); }
245     typename ResMap::Iterator endWithNull() const { return mResourceMap.endWithNull(); }
246 
247     // Not a constant-time operation, should only be used for verification.
248     bool empty() const;
249 
250   private:
251     const ResMap &mResourceMap;
252 };
253 
254 template <typename ResourceType, typename IDType>
255 ResourceMap<ResourceType, IDType>::ResourceMap()
256     : mFlatResourcesSize(kInitialFlatResourcesSize),
257       mFlatResources(new ResourceType *[kInitialFlatResourcesSize])
258 {
259     memset(mFlatResources, kInvalidPointer, mFlatResourcesSize * sizeof(mFlatResources[0]));
260 }
261 
262 template <typename ResourceType, typename IDType>
263 ResourceMap<ResourceType, IDType>::~ResourceMap()
264 {
265     ASSERT(begin() == end());
266     delete[] mFlatResources;
267 }
268 
269 template <typename ResourceType, typename IDType>
270 ANGLE_INLINE bool ResourceMap<ResourceType, IDType>::contains(IDType id) const
271 {
272     GLuint handle = GetIDValue(id);
273     if (handle < mFlatResourcesSize)
274     {
275         return mFlatResources[handle] != InvalidPointer();
276     }
277     std::lock_guard<Mutex> lock(mMutex);
278     return mHashedResources.find(handle) != mHashedResources.end();
279 }
280 
281 template <typename ResourceType, typename IDType>
282 bool ResourceMap<ResourceType, IDType>::erase(IDType id, ResourceType **resourceOut)
283 {
284     GLuint handle = GetIDValue(id);
285     if (handle < mFlatResourcesSize)
286     {
287         auto &value = mFlatResources[handle];
288         if (value == InvalidPointer())
289         {
290             return false;
291         }
292         *resourceOut = value;
293         value        = InvalidPointer();
294     }
295     else
296     {
297         std::lock_guard<Mutex> lock(mMutex);
298 
299         auto it = mHashedResources.find(handle);
300         if (it == mHashedResources.end())
301         {
302             return false;
303         }
304         *resourceOut = it->second;
305         mHashedResources.erase(it);
306     }
307     return true;
308 }
309 
310 template <typename ResourceType, typename IDType>
311 void ResourceMap<ResourceType, IDType>::assign(IDType id, ResourceType *resource)
312 {
313     GLuint handle = GetIDValue(id);
314     if (handle < kFlatResourcesLimit)
315     {
316         if (handle >= mFlatResourcesSize)
317         {
318             // No need for a lock as the flat map never grows when locking is needed.
319             static_assert(!kNeedsLock || kInitialFlatResourcesSize == kFlatResourcesLimit);
320 
321             // Use power-of-two.
322             size_t newSize = mFlatResourcesSize;
323             while (newSize <= handle)
324             {
325                 newSize *= 2;
326             }
327 
328             ResourceType **oldResources = mFlatResources;
329 
330             mFlatResources = new ResourceType *[newSize];
331             memset(&mFlatResources[mFlatResourcesSize], kInvalidPointer,
332                    (newSize - mFlatResourcesSize) * sizeof(mFlatResources[0]));
333             memcpy(mFlatResources, oldResources, mFlatResourcesSize * sizeof(mFlatResources[0]));
334             mFlatResourcesSize = newSize;
335             delete[] oldResources;
336         }
337         ASSERT(mFlatResourcesSize > handle);
338         mFlatResources[handle] = resource;
339     }
340     else
341     {
342         std::lock_guard<Mutex> lock(mMutex);
343         mHashedResources[handle] = resource;
344     }
345 }
346 
347 template <typename ResourceType, typename IDType>
348 typename ResourceMap<ResourceType, IDType>::Iterator ResourceMap<ResourceType, IDType>::begin()
349     const
350 {
351     return Iterator(*this, nextResource(0, true), mHashedResources.begin(), true);
352 }
353 
354 template <typename ResourceType, typename IDType>
355 typename ResourceMap<ResourceType, IDType>::Iterator ResourceMap<ResourceType, IDType>::end() const
356 {
357     return Iterator(*this, static_cast<GLuint>(mFlatResourcesSize), mHashedResources.end(), true);
358 }
359 
360 template <typename ResourceType, typename IDType>
361 typename ResourceMap<ResourceType, IDType>::Iterator
362 ResourceMap<ResourceType, IDType>::beginWithNull() const
363 {
364     return Iterator(*this, nextResource(0, false), mHashedResources.begin(), false);
365 }
366 
367 template <typename ResourceType, typename IDType>
368 typename ResourceMap<ResourceType, IDType>::Iterator
369 ResourceMap<ResourceType, IDType>::endWithNull() const
370 {
371     return Iterator(*this, static_cast<GLuint>(mFlatResourcesSize), mHashedResources.end(), false);
372 }
373 
374 template <typename ResourceType, typename IDType>
375 bool UnsafeResourceMapIter<ResourceType, IDType>::empty() const
376 {
377     return begin() == end();
378 }
379 
380 template <typename ResourceType, typename IDType>
381 void ResourceMap<ResourceType, IDType>::clear()
382 {
383     // No need for a lock as this is only called on destruction.
384     memset(mFlatResources, kInvalidPointer, kInitialFlatResourcesSize * sizeof(mFlatResources[0]));
385     mFlatResourcesSize = kInitialFlatResourcesSize;
386     mHashedResources.clear();
387 }
388 
389 template <typename ResourceType, typename IDType>
390 GLuint ResourceMap<ResourceType, IDType>::nextResource(size_t flatIndex, bool skipNulls) const
391 {
392     // This function is only used by the iterators, access to which is marked by
393     // UnsafeResourceMapIter.  Locking is the responsibility of the caller.
394     for (size_t index = flatIndex; index < mFlatResourcesSize; index++)
395     {
396         if ((mFlatResources[index] != nullptr || !skipNulls) &&
397             mFlatResources[index] != InvalidPointer())
398         {
399             return static_cast<GLuint>(index);
400         }
401     }
402     return static_cast<GLuint>(mFlatResourcesSize);
403 }
404 
405 template <typename ResourceType, typename IDType>
406 // static
407 ResourceType *ResourceMap<ResourceType, IDType>::InvalidPointer()
408 {
409     return reinterpret_cast<ResourceType *>(kInvalidPointer);
410 }
411 
412 template <typename ResourceType, typename IDType>
413 ResourceMap<ResourceType, IDType>::Iterator::Iterator(
414     const ResourceMap &origin,
415     GLuint flatIndex,
416     typename ResourceMap<ResourceType, IDType>::HashMap::const_iterator hashIndex,
417     bool skipNulls)
418     : mOrigin(origin), mFlatIndex(flatIndex), mHashIndex(hashIndex), mSkipNulls(skipNulls)
419 {
420     updateValue();
421 }
422 
423 template <typename ResourceType, typename IDType>
424 bool ResourceMap<ResourceType, IDType>::Iterator::operator==(const Iterator &other) const
425 {
426     return (mFlatIndex == other.mFlatIndex && mHashIndex == other.mHashIndex);
427 }
428 
429 template <typename ResourceType, typename IDType>
430 bool ResourceMap<ResourceType, IDType>::Iterator::operator!=(const Iterator &other) const
431 {
432     return !(*this == other);
433 }
434 
435 template <typename ResourceType, typename IDType>
436 typename ResourceMap<ResourceType, IDType>::Iterator &
437 ResourceMap<ResourceType, IDType>::Iterator::operator++()
438 {
439     if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResourcesSize))
440     {
441         mFlatIndex = mOrigin.nextResource(mFlatIndex + 1, mSkipNulls);
442     }
443     else
444     {
445         mHashIndex++;
446     }
447     updateValue();
448     return *this;
449 }
450 
451 template <typename ResourceType, typename IDType>
452 const typename ResourceMap<ResourceType, IDType>::IndexAndResource *
453 ResourceMap<ResourceType, IDType>::Iterator::operator->() const
454 {
455     return &mValue;
456 }
457 
458 template <typename ResourceType, typename IDType>
459 const typename ResourceMap<ResourceType, IDType>::IndexAndResource &
460 ResourceMap<ResourceType, IDType>::Iterator::operator*() const
461 {
462     return mValue;
463 }
464 
465 template <typename ResourceType, typename IDType>
466 void ResourceMap<ResourceType, IDType>::Iterator::updateValue()
467 {
468     if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResourcesSize))
469     {
470         mValue.first  = mFlatIndex;
471         mValue.second = mOrigin.mFlatResources[mFlatIndex];
472     }
473     else if (mHashIndex != mOrigin.mHashedResources.end())
474     {
475         mValue.first  = mHashIndex->first;
476         mValue.second = mHashIndex->second;
477     }
478 }
479 
480 }  // namespace gl
481 
482 #endif  // LIBANGLE_RESOURCE_MAP_H_
483