| // | |
| // Copyright 2017 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // ResourceMap: | |
| // An optimized resource map which packs the first set of allocated objects into a | |
| // flat array, and then falls back to an unordered map for the higher handle values. | |
| // | |
| #ifndef LIBANGLE_RESOURCE_MAP_H_ | |
| #define LIBANGLE_RESOURCE_MAP_H_ | |
| #include <atomic> | |
| #include <mutex> | |
| #include <type_traits> | |
| #include "common/unsafe_buffers.h" | |
| #include "common/SimpleMutex.h" | |
| #include "common/hash_containers.h" | |
| #include "libANGLE/angletypes.h" | |
| namespace gl | |
| { | |
| // The resource map needs to be internally synchronized for maps that are placed in the share group | |
| // (as opposed to being private to the context) and that are accessed without holding the share | |
| // group lock. | |
| #if defined(ANGLE_ENABLE_SHARE_CONTEXT_LOCK) | |
| using ResourceMapMutex = angle::SimpleMutex; | |
| #else | |
| using ResourceMapMutex = angle::NoOpMutex; | |
| #endif | |
| template <bool NeedsLock = true> | |
| struct SelectResourceMapMutex | |
| { | |
| using type = ResourceMapMutex; | |
| }; | |
| template <> | |
| struct SelectResourceMapMutex<false> | |
| { | |
| using type = angle::NoOpMutex; | |
| }; | |
| // Analysis of ANGLE's traces as well as Chrome usage reveals the following: | |
| // | |
| // - Buffers: Typical applications use no more than 4000 ids. Very few use over 6000. | |
| // - Textures: Typical applications use no more than 1200 ids. Very few use over 2000. | |
| // - Samplers: Typical applications use no more than 50 ids. Very few use over 100. | |
| // - Shaders and Programs: Typical applications use no more than 500. Very few use over 700. | |
| // - Sync objects: Typical applications use no more than 500. Very few use over 1500. | |
| // | |
| // For all the other shared types, the maximum used id is small (under 100). For | |
| // context-private parts (such as vertex arrays and queries), the id count can be in the | |
| // thousands. | |
| // | |
| // The initial size of the flat resource map is based on the above, rounded up to a multiple of | |
| // 1536. Resource maps that need a lock (kNeedsLock == true) have an initial flat size and the | |
| // maximum flat size can be extended when an additional flat size is specified. For others, the | |
| // maps start small and can grow. | |
| // | |
| // kAdditionalFlatResourcesSize defines the size of the additional flat resource map. | |
| // Handles in [kInitialFlatResourcesSize, kInitialFlatResourcesSize + kAdditionalFlatResourcesSize) | |
| // are stored in mAdditionalFlatResources. 0 means the additional flat map is disabled. | |
| template <typename IDType> | |
| struct ResourceMapParams | |
| { | |
| static constexpr size_t kInitialFlatResourcesSize = 0xC0; | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0; | |
| // The following are private to the context and don't need a lock: | |
| // | |
| // - Vertex Array Objects | |
| // - Framebuffer Objects | |
| // - Transform Feedback Objects | |
| // - Query Objects | |
| // | |
| // The rest of the maps need a lock. However, only a select few are currently locked as API | |
| // relevant to the rest of the types are protected by the share group lock. As the share group | |
| // lock is removed from more types, the resource map lock needs to be enabled for them. | |
| static constexpr bool kNeedsLock = false; | |
| }; | |
| template <> | |
| struct ResourceMapParams<BufferID> | |
| { | |
| static constexpr size_t kInitialFlatResourcesSize = 0x1800; | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0x7800 - kInitialFlatResourcesSize; | |
| static constexpr bool kNeedsLock = true; | |
| }; | |
| template <> | |
| struct ResourceMapParams<TextureID> | |
| { | |
| static constexpr size_t kInitialFlatResourcesSize = 0x600; | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0; | |
| static constexpr bool kNeedsLock = false; | |
| }; | |
| template <> | |
| struct ResourceMapParams<ShaderProgramID> | |
| { | |
| static constexpr size_t kInitialFlatResourcesSize = 0x600; | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0; | |
| static constexpr bool kNeedsLock = false; | |
| }; | |
| template <> | |
| struct ResourceMapParams<SyncID> | |
| { | |
| static constexpr size_t kInitialFlatResourcesSize = 0x600; | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0; | |
| static constexpr bool kNeedsLock = false; | |
| }; | |
| template <typename ResourceType, typename IDType> | |
| class ResourceMap final : angle::NonCopyable | |
| { | |
| public: | |
| ResourceMap(); | |
| ~ResourceMap(); | |
| ANGLE_INLINE ResourceType *query(IDType id) const | |
| { | |
| GLuint handle = GetIDValue(id); | |
| // No need for a lock when accessing the flat map. Either the flat map is static, or | |
| // locking is not needed. | |
| static_assert(!kNeedsLock || kInitialFlatResourcesSize == kFlatResourcesLimit); | |
| if (ANGLE_LIKELY(handle < mFlatResourcesSize)) | |
| { | |
| ResourceType *value = ANGLE_UNSAFE_TODO(mFlatResources[handle]); | |
| return (value == InvalidPointer() ? nullptr : value); | |
| } | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| if (handle < kLocklessFlatResourcesLimit) | |
| { | |
| ASSERT(handle >= kInitialFlatResourcesSize); | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (additionalResources != nullptr) | |
| { | |
| ResourceType *value = | |
| ANGLE_UNSAFE_TODO(additionalResources[handle - kInitialFlatResourcesSize]); | |
| return (value == InvalidPointer() ? nullptr : value); | |
| } | |
| return nullptr; | |
| } | |
| } | |
| return findInHashedResources(handle); | |
| } | |
| // Returns true if the handle was reserved. Not necessarily if the resource is created. | |
| bool contains(IDType id) const; | |
| // Returns the element that was at this location. | |
| bool erase(IDType id, ResourceType **resourceOut); | |
| void assign(IDType id, ResourceType *resource); | |
| // Clears the map. | |
| void clear(); | |
| using IndexAndResource = std::pair<GLuint, ResourceType *>; | |
| using HashMap = angle::HashMap<GLuint, ResourceType *>; | |
| class Iterator final | |
| { | |
| public: | |
| bool operator==(const Iterator &other) const; | |
| bool operator!=(const Iterator &other) const; | |
| Iterator &operator++(); | |
| const IndexAndResource *operator->() const; | |
| const IndexAndResource &operator*() const; | |
| private: | |
| friend class ResourceMap; | |
| Iterator(const ResourceMap &origin, | |
| GLuint flatIndex, | |
| typename HashMap::const_iterator hashIndex, | |
| bool skipNulls); | |
| void updateValue(); | |
| const ResourceMap &mOrigin; | |
| GLuint mFlatIndex; | |
| typename HashMap::const_iterator mHashIndex; | |
| IndexAndResource mValue; | |
| bool mSkipNulls; | |
| }; | |
| private: | |
| friend class Iterator; | |
| template <typename SameResourceType, typename SameIDType> | |
| friend class UnsafeResourceMapIter; | |
| // null values represent reserved handles. | |
| Iterator begin() const; | |
| Iterator end() const; | |
| Iterator beginWithNull() const; | |
| Iterator endWithNull() const; | |
| // Used by iterators and related functions only (due to lack of thread safety). | |
| GLuint nextResource(size_t flatIndex, bool skipNulls) const; | |
| GLuint getFlatEndIndex() const; | |
| // constexpr methods cannot contain reinterpret_cast, so we need a static method. | |
| static ResourceType *InvalidPointer(); | |
| static constexpr intptr_t kInvalidPointer = static_cast<intptr_t>(-1); | |
| #if defined(ANGLE_ENABLE_SHARE_CONTEXT_LOCK) | |
| static constexpr bool kNeedsLock = ResourceMapParams<IDType>::kNeedsLock; | |
| static constexpr size_t kInitialFlatResourcesSize = | |
| ResourceMapParams<IDType>::kInitialFlatResourcesSize; | |
| static constexpr size_t kAdditionalFlatResourcesSize = | |
| ResourceMapParams<IDType>::kAdditionalFlatResourcesSize; | |
| #else | |
| // When share group locks are disabled, we are already in an thread-unsafe state so disable | |
| // locking in the ResourceManager too. | |
| static constexpr bool kNeedsLock = false; | |
| // Always grow from a small initial allocation when locks are disabled. Chromium uses very few | |
| // total resources. | |
| static constexpr size_t kInitialFlatResourcesSize = 192; | |
| // Additional flat array requires the share group lock for safe lazy allocation. Disable lazy | |
| // allocation when locks are unavailable. | |
| static constexpr size_t kAdditionalFlatResourcesSize = 0; | |
| #endif | |
| // Both mFlatResources and mAdditionalFlatResources are valid when kNeedsLock is true. | |
| static constexpr size_t kLocklessFlatResourcesLimit = | |
| kInitialFlatResourcesSize + kAdditionalFlatResourcesSize; | |
| static_assert( | |
| kAdditionalFlatResourcesSize == 0 || kNeedsLock, | |
| "kAdditionalFlatResourcesSize > 0 requires kNeedsLock to ensure safe lazy allocation"); | |
| using Mutex = typename SelectResourceMapMutex<kNeedsLock>::type; | |
| // For the lock-free flat map, experimental testing suggests that 10K is a reasonable upper | |
| // limit, rounded up to 0x3000 here for simplicity. For the map that needs a lock, the flat | |
| // array size is fixed based on observed usage. | |
| static constexpr size_t kFlatResourcesLimit = kNeedsLock ? kInitialFlatResourcesSize : 0x3000; | |
| // Due to the way assign() is implemented, kFlatResourcesLimit / kInitialFlatResourcesSize must | |
| // be a power of 2. | |
| static_assert(kFlatResourcesLimit % kInitialFlatResourcesSize == 0); | |
| static_assert(((kFlatResourcesLimit / kInitialFlatResourcesSize) & | |
| (kFlatResourcesLimit / kInitialFlatResourcesSize - 1)) == 0); | |
| bool containsInHashedResources(GLuint handle) const; | |
| ResourceType *findInHashedResources(GLuint handle) const; | |
| bool eraseFromHashedResources(GLuint handle, ResourceType **resourceOut); | |
| void assignAboveCurrentFlatSize(GLuint handle, ResourceType *resource); | |
| size_t mFlatResourcesSize; | |
| ResourceType **mFlatResources; | |
| // Additional flat array is allocated lazily on first use under mMutex and published through | |
| // store/load, enabling lock-free reads in query(). | |
| std::atomic<ResourceType **> mAdditionalFlatResources; | |
| // A map of GL objects indexed by object ID. | |
| HashMap mHashedResources; | |
| // mFlatResources is allocated at object creation time, with a default size of | |
| // |kInitialFlatResourcesSize|. This is thread safe, because the allocation is done by the | |
| // first context in the share group. The flat map is allowed to grow up to | |
| // |kFlatResourcesLimit|, but only for maps that don't need a lock (kNeedsLock == false). | |
| // | |
| // For maps that don't need a lock, this mutex is a no-op. For those that do, the mutex is | |
| // taken when allocating / deleting objects, as well as when accessing |mHashedResources|. | |
| // Otherwise, access to the flat map (which never gets reallocated due to | |
| // |kInitialFlatResourcesSize == kFlatResourcesLimit|) is lockless. This latter is possible | |
| // because the application is not allowed to gen/delete and bind the same ID in different | |
| // threads at the same time. | |
| // | |
| // Note that because HandleAllocator is not yet thread-safe, glGen* and glDelete* functions | |
| // cannot be free of the share group mutex yet. To remove the share group mutex from those | |
| // functions, likely the HandleAllocator class should be merged with this class, and the | |
| // necessary insert/erase operations done under this same lock. | |
| mutable Mutex mMutex; | |
| }; | |
| // A helper to retrieve the resource map iterators while being explicit that this is not thread | |
| // safe. Usage of iterators are limited to clean up on destruction and capture/replay, neither of | |
| // which can race with other threads in their access to the resource map. | |
| template <typename ResourceType, typename IDType> | |
| class UnsafeResourceMapIter | |
| { | |
| public: | |
| using ResMap = ResourceMap<ResourceType, IDType>; | |
| UnsafeResourceMapIter(const ResMap &resourceMap) : mResourceMap(resourceMap) {} | |
| typename ResMap::Iterator begin() const { return mResourceMap.begin(); } | |
| typename ResMap::Iterator end() const { return mResourceMap.end(); } | |
| typename ResMap::Iterator beginWithNull() const { return mResourceMap.beginWithNull(); } | |
| typename ResMap::Iterator endWithNull() const { return mResourceMap.endWithNull(); } | |
| // Not a constant-time operation, should only be used for verification. | |
| bool empty() const; | |
| private: | |
| const ResMap &mResourceMap; | |
| }; | |
| template <typename ResourceType, typename IDType> | |
| ResourceMap<ResourceType, IDType>::ResourceMap() | |
| : mFlatResourcesSize(kInitialFlatResourcesSize), | |
| mFlatResources(new ResourceType *[kInitialFlatResourcesSize]), | |
| mAdditionalFlatResources(nullptr) | |
| { | |
| ANGLE_UNSAFE_TODO( | |
| memset(mFlatResources, kInvalidPointer, mFlatResourcesSize * sizeof(mFlatResources[0]))); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| ResourceMap<ResourceType, IDType>::~ResourceMap() | |
| { | |
| ASSERT(begin() == end()); | |
| delete[] mFlatResources; | |
| delete[] mAdditionalFlatResources.load(std::memory_order_acquire); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool ResourceMap<ResourceType, IDType>::containsInHashedResources(GLuint handle) const | |
| { | |
| std::lock_guard<Mutex> lock(mMutex); | |
| return mHashedResources.find(handle) != mHashedResources.end(); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| ResourceType *ResourceMap<ResourceType, IDType>::findInHashedResources(GLuint handle) const | |
| { | |
| std::lock_guard<Mutex> lock(mMutex); | |
| auto it = mHashedResources.find(handle); | |
| // Note: it->second can also be nullptr, so nullptr check doesn't work for "contains" | |
| return (it == mHashedResources.end() ? nullptr : it->second); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool ResourceMap<ResourceType, IDType>::eraseFromHashedResources(GLuint handle, | |
| ResourceType **resourceOut) | |
| { | |
| std::lock_guard<Mutex> lock(mMutex); | |
| auto it = mHashedResources.find(handle); | |
| if (it == mHashedResources.end()) | |
| { | |
| return false; | |
| } | |
| *resourceOut = it->second; | |
| mHashedResources.erase(it); | |
| return true; | |
| } | |
| template <typename ResourceType, typename IDType> | |
| ANGLE_INLINE bool ResourceMap<ResourceType, IDType>::contains(IDType id) const | |
| { | |
| GLuint handle = GetIDValue(id); | |
| if (ANGLE_LIKELY(handle < mFlatResourcesSize)) | |
| { | |
| return ANGLE_UNSAFE_TODO(mFlatResources[handle]) != InvalidPointer(); | |
| } | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| if (handle < kLocklessFlatResourcesLimit) | |
| { | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (additionalResources != nullptr) | |
| { | |
| return ANGLE_UNSAFE_TODO(additionalResources[handle - kInitialFlatResourcesSize]) != | |
| InvalidPointer(); | |
| } | |
| return false; | |
| } | |
| } | |
| return containsInHashedResources(handle); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool ResourceMap<ResourceType, IDType>::erase(IDType id, ResourceType **resourceOut) | |
| { | |
| GLuint handle = GetIDValue(id); | |
| if (ANGLE_LIKELY(handle < mFlatResourcesSize)) | |
| { | |
| auto &value = ANGLE_UNSAFE_TODO(mFlatResources[handle]); | |
| if (value == InvalidPointer()) | |
| { | |
| return false; | |
| } | |
| *resourceOut = value; | |
| value = InvalidPointer(); | |
| return true; | |
| } | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| if (handle < kLocklessFlatResourcesLimit) | |
| { | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (additionalResources != nullptr) | |
| { | |
| ResourceType **value = | |
| ANGLE_UNSAFE_TODO(&additionalResources[handle - kInitialFlatResourcesSize]); | |
| if (*value == InvalidPointer()) | |
| { | |
| return false; | |
| } | |
| *resourceOut = *value; | |
| *value = InvalidPointer(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| } | |
| return eraseFromHashedResources(handle, resourceOut); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| void ResourceMap<ResourceType, IDType>::assignAboveCurrentFlatSize(GLuint handle, | |
| ResourceType *resource) | |
| { | |
| if (ANGLE_LIKELY(handle < kFlatResourcesLimit)) | |
| { | |
| // No need for a lock as the flat map never grows when locking is needed. | |
| static_assert(!kNeedsLock || kInitialFlatResourcesSize == kFlatResourcesLimit); | |
| // Use power-of-two. | |
| size_t newSize = mFlatResourcesSize; | |
| while (newSize <= handle) | |
| { | |
| newSize *= 2; | |
| } | |
| ResourceType **oldResources = mFlatResources; | |
| mFlatResources = new ResourceType *[newSize]; | |
| ANGLE_UNSAFE_TODO(memset(&mFlatResources[mFlatResourcesSize], kInvalidPointer, | |
| (newSize - mFlatResourcesSize) * sizeof(mFlatResources[0]))); | |
| ANGLE_UNSAFE_TODO( | |
| memcpy(mFlatResources, oldResources, mFlatResourcesSize * sizeof(mFlatResources[0]))); | |
| mFlatResourcesSize = newSize; | |
| ASSERT(mFlatResourcesSize <= kFlatResourcesLimit); | |
| delete[] oldResources; | |
| ASSERT(mFlatResourcesSize > handle); | |
| ANGLE_UNSAFE_TODO(mFlatResources[handle]) = resource; | |
| return; | |
| } | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| // This should only possible when lock is needed. Otherwise we could just grow | |
| // mFlatResources. | |
| static_assert(kNeedsLock); | |
| if (handle < kLocklessFlatResourcesLimit) | |
| { | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (ANGLE_UNLIKELY(additionalResources == nullptr)) | |
| { | |
| std::lock_guard<Mutex> lock(mMutex); | |
| // Re-read under lock to avoid double allocation if two threads raced here. | |
| // memory_order_relaxed is sufficient as the mutex provides the memory barrier. | |
| additionalResources = mAdditionalFlatResources.load(std::memory_order_relaxed); | |
| if (additionalResources == nullptr) | |
| { | |
| additionalResources = new ResourceType *[kAdditionalFlatResourcesSize]; | |
| ANGLE_UNSAFE_TODO( | |
| memset(additionalResources, kInvalidPointer, | |
| kAdditionalFlatResourcesSize * sizeof(additionalResources[0]))); | |
| // Write the elements before the release-store so that a concurrent reader | |
| // loading the pointer with memory_order_acquire observes a fully initialized | |
| // array. | |
| ANGLE_UNSAFE_TODO(additionalResources[handle - kInitialFlatResourcesSize]) = | |
| resource; | |
| mAdditionalFlatResources.store(additionalResources, std::memory_order_release); | |
| return; | |
| } | |
| } | |
| ANGLE_UNSAFE_TODO(additionalResources[handle - kInitialFlatResourcesSize]) = resource; | |
| return; | |
| } | |
| } | |
| std::lock_guard<Mutex> lock(mMutex); | |
| mHashedResources[handle] = resource; | |
| } | |
| template <typename ResourceType, typename IDType> | |
| ANGLE_INLINE void ResourceMap<ResourceType, IDType>::assign(IDType id, ResourceType *resource) | |
| { | |
| GLuint handle = GetIDValue(id); | |
| if (ANGLE_LIKELY(handle < mFlatResourcesSize)) | |
| { | |
| ANGLE_UNSAFE_TODO(mFlatResources[handle]) = resource; | |
| } | |
| else | |
| { | |
| assignAboveCurrentFlatSize(handle, resource); | |
| } | |
| } | |
| template <typename ResourceType, typename IDType> | |
| typename ResourceMap<ResourceType, IDType>::Iterator ResourceMap<ResourceType, IDType>::begin() | |
| const | |
| { | |
| return Iterator(*this, nextResource(0, true), mHashedResources.begin(), true); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| typename ResourceMap<ResourceType, IDType>::Iterator ResourceMap<ResourceType, IDType>::end() const | |
| { | |
| return Iterator(*this, getFlatEndIndex(), mHashedResources.end(), true); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| typename ResourceMap<ResourceType, IDType>::Iterator | |
| ResourceMap<ResourceType, IDType>::beginWithNull() const | |
| { | |
| return Iterator(*this, nextResource(0, false), mHashedResources.begin(), false); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| typename ResourceMap<ResourceType, IDType>::Iterator | |
| ResourceMap<ResourceType, IDType>::endWithNull() const | |
| { | |
| return Iterator(*this, getFlatEndIndex(), mHashedResources.end(), false); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool UnsafeResourceMapIter<ResourceType, IDType>::empty() const | |
| { | |
| return begin() == end(); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| void ResourceMap<ResourceType, IDType>::clear() | |
| { | |
| // No need for a lock as this is only called on destruction. | |
| ANGLE_UNSAFE_TODO(memset(mFlatResources, kInvalidPointer, | |
| kInitialFlatResourcesSize * sizeof(mFlatResources[0]))); | |
| mFlatResourcesSize = kInitialFlatResourcesSize; | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (additionalResources != nullptr) | |
| { | |
| ANGLE_UNSAFE_TODO( | |
| memset(additionalResources, kInvalidPointer, | |
| kAdditionalFlatResourcesSize * sizeof(additionalResources[0]))); | |
| } | |
| } | |
| mHashedResources.clear(); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| GLuint ResourceMap<ResourceType, IDType>::nextResource(size_t flatIndex, bool skipNulls) const | |
| { | |
| // This function is only used by the iterators, access to which is marked by | |
| // UnsafeResourceMapIter. Locking is the responsibility of the caller. | |
| size_t index = flatIndex; | |
| for (; index < mFlatResourcesSize; index++) | |
| { | |
| if ((ANGLE_UNSAFE_TODO(mFlatResources[index]) != nullptr || !skipNulls) && | |
| ANGLE_UNSAFE_TODO(mFlatResources[index]) != InvalidPointer()) | |
| { | |
| return static_cast<GLuint>(index); | |
| } | |
| } | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| ResourceType **additionalResources = | |
| mAdditionalFlatResources.load(std::memory_order_acquire); | |
| if (additionalResources != nullptr) | |
| { | |
| for (; index < kLocklessFlatResourcesLimit; index++) | |
| { | |
| ResourceType *value = | |
| ANGLE_UNSAFE_TODO(additionalResources[index - kInitialFlatResourcesSize]); | |
| if ((value != nullptr || !skipNulls) && value != InvalidPointer()) | |
| { | |
| return static_cast<GLuint>(index); | |
| } | |
| } | |
| } | |
| return static_cast<GLuint>(kLocklessFlatResourcesLimit); | |
| } | |
| return static_cast<GLuint>(mFlatResourcesSize); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| GLuint ResourceMap<ResourceType, IDType>::getFlatEndIndex() const | |
| { | |
| if constexpr (kAdditionalFlatResourcesSize > 0) | |
| { | |
| return static_cast<GLuint>(kLocklessFlatResourcesLimit); | |
| } | |
| return static_cast<GLuint>(mFlatResourcesSize); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| // static | |
| ResourceType *ResourceMap<ResourceType, IDType>::InvalidPointer() | |
| { | |
| return reinterpret_cast<ResourceType *>(kInvalidPointer); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| ResourceMap<ResourceType, IDType>::Iterator::Iterator( | |
| const ResourceMap &origin, | |
| GLuint flatIndex, | |
| typename ResourceMap<ResourceType, IDType>::HashMap::const_iterator hashIndex, | |
| bool skipNulls) | |
| : mOrigin(origin), mFlatIndex(flatIndex), mHashIndex(hashIndex), mSkipNulls(skipNulls) | |
| { | |
| updateValue(); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool ResourceMap<ResourceType, IDType>::Iterator::operator==(const Iterator &other) const | |
| { | |
| return (mFlatIndex == other.mFlatIndex && mHashIndex == other.mHashIndex); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| bool ResourceMap<ResourceType, IDType>::Iterator::operator!=(const Iterator &other) const | |
| { | |
| return !(*this == other); | |
| } | |
| template <typename ResourceType, typename IDType> | |
| typename ResourceMap<ResourceType, IDType>::Iterator & | |
| ResourceMap<ResourceType, IDType>::Iterator::operator++() | |
| { | |
| if (mFlatIndex < mOrigin.getFlatEndIndex()) | |
| { | |
| mFlatIndex = mOrigin.nextResource(mFlatIndex + 1, mSkipNulls); | |
| } | |
| else | |
| { | |
| mHashIndex++; | |
| } | |
| updateValue(); | |
| return *this; | |
| } | |
| template <typename ResourceType, typename IDType> | |
| const typename ResourceMap<ResourceType, IDType>::IndexAndResource * | |
| ResourceMap<ResourceType, IDType>::Iterator::operator->() const | |
| { | |
| return &mValue; | |
| } | |
| template <typename ResourceType, typename IDType> | |
| const typename ResourceMap<ResourceType, IDType>::IndexAndResource & | |
| ResourceMap<ResourceType, IDType>::Iterator::operator*() const | |
| { | |
| return mValue; | |
| } | |
| template <typename ResourceType, typename IDType> | |
| void ResourceMap<ResourceType, IDType>::Iterator::updateValue() | |
| { | |
| if (mFlatIndex < mOrigin.getFlatEndIndex()) | |
| { | |
| if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResourcesSize)) | |
| { | |
| mValue.first = mFlatIndex; | |
| mValue.second = ANGLE_UNSAFE_TODO(mOrigin.mFlatResources[mFlatIndex]); | |
| } | |
| else | |
| { | |
| ResourceType **additionalResources = | |
| mOrigin.mAdditionalFlatResources.load(std::memory_order_acquire); | |
| ASSERT(additionalResources != nullptr); | |
| mValue.first = mFlatIndex; | |
| mValue.second = ANGLE_UNSAFE_TODO( | |
| additionalResources[mFlatIndex - ResourceMap::kInitialFlatResourcesSize]); | |
| } | |
| } | |
| else if (mHashIndex != mOrigin.mHashedResources.end()) | |
| { | |
| mValue.first = mHashIndex->first; | |
| mValue.second = mHashIndex->second; | |
| } | |
| } | |
| } // namespace gl | |
| #endif // LIBANGLE_RESOURCE_MAP_H_ |