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