|  | /* | 
|  | * Copyright 2010 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #ifndef GrTAllocator_DEFINED | 
|  | #define GrTAllocator_DEFINED | 
|  |  | 
|  | #include "src/gpu/GrBlockAllocator.h" | 
|  |  | 
|  | template <typename T, int StartingItems = 1> | 
|  | class GrTAllocator { | 
|  | public: | 
|  | /** | 
|  | * Create an allocator that defaults to using StartingItems as heap increment. | 
|  | */ | 
|  | GrTAllocator() | 
|  | : fTotalCount(0) | 
|  | , fAllocator(GrBlockAllocator::GrowthPolicy::kFixed) {} | 
|  |  | 
|  | /** | 
|  | * Create an allocator | 
|  | * | 
|  | * @param   itemsPerBlock   the number of items to allocate at once | 
|  | */ | 
|  | explicit GrTAllocator(int itemsPerBlock) | 
|  | : fTotalCount(0) | 
|  | , fAllocator(GrBlockAllocator::GrowthPolicy::kFixed, | 
|  | GrBlockAllocator::BlockOverhead<alignof(T)>() + sizeof(T)*itemsPerBlock) {} | 
|  |  | 
|  | ~GrTAllocator() { this->reset(); } | 
|  |  | 
|  | /** | 
|  | * Adds an item and returns it. | 
|  | * | 
|  | * @return the added item. | 
|  | */ | 
|  | T& push_back() { | 
|  | return *new (this->pushItem()) T; | 
|  | } | 
|  |  | 
|  | T& push_back(const T& t) { | 
|  | return *new (this->pushItem()) T(t); | 
|  | } | 
|  |  | 
|  | T& push_back(T&& t) { | 
|  | return *new (this->pushItem()) T(std::move(t)); | 
|  | } | 
|  |  | 
|  | template <typename... Args> | 
|  | T& emplace_back(Args&&... args) { | 
|  | return *new (this->pushItem()) T(std::forward<Args>(args)...); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Remove the last item, only call if count() != 0 | 
|  | */ | 
|  | void pop_back() { | 
|  | SkASSERT(fTotalCount > 0); | 
|  |  | 
|  | GrBlockAllocator::Block* block = fAllocator->currentBlock(); | 
|  | int newCount = block->metadata() - 1; | 
|  |  | 
|  | // Run dtor for the popped item | 
|  | int releaseIndex; | 
|  | GetItemAndOffset(block, newCount, &releaseIndex)->~T(); | 
|  |  | 
|  | if (newCount == 0) { | 
|  | fAllocator->releaseBlock(block); | 
|  | } else { | 
|  | // Since this always follows LIFO, the block should always be able to release the memory | 
|  | SkAssertResult(block->release(releaseIndex, releaseIndex + sizeof(T))); | 
|  | block->setMetadata(newCount); | 
|  | } | 
|  |  | 
|  | fTotalCount--; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Removes all added items. | 
|  | */ | 
|  | void reset() { | 
|  | // Invoke destructors in reverse order | 
|  | for (const auto* b : fAllocator->rblocks()) { | 
|  | int c = b->metadata(); | 
|  | for (int i = c - 1; i >= 0; i--) { | 
|  | GetItem(b, i)->~T(); | 
|  | } | 
|  | } | 
|  |  | 
|  | fAllocator->reset(); | 
|  | fTotalCount = 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the item count. | 
|  | */ | 
|  | int count() const { | 
|  | #ifdef SK_DEBUG | 
|  | // Confirm total count matches sum of block counts | 
|  | int count = 0; | 
|  | int blockCount = 0; | 
|  | for (const auto* b :fAllocator->blocks()) { | 
|  | count += b->metadata(); | 
|  | blockCount++; | 
|  | } | 
|  | SkASSERT(count == fTotalCount); | 
|  | #endif | 
|  | return fTotalCount; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Is the count 0? | 
|  | */ | 
|  | bool empty() const { return this->count() == 0; } | 
|  |  | 
|  | /** | 
|  | * Access first item, only call if count() != 0 | 
|  | */ | 
|  | T& front() { | 
|  | // This assumes that the head block actually have room to store the first item. | 
|  | static_assert(StartingItems >= 1); | 
|  | SkASSERT(fTotalCount > 0); | 
|  | return *(GetItem(fAllocator->headBlock(), 0)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Access first item, only call if count() != 0 | 
|  | */ | 
|  | const T& front() const { | 
|  | SkASSERT(fTotalCount > 0); | 
|  | return *(GetItem(fAllocator->headBlock(), 0)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Access last item, only call if count() != 0 | 
|  | */ | 
|  | T& back() { | 
|  | SkASSERT(fTotalCount > 0); | 
|  | int blockCount = fAllocator->currentBlock()->metadata(); | 
|  | return *(GetItem(fAllocator->currentBlock(), blockCount - 1)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Access last item, only call if count() != 0 | 
|  | */ | 
|  | const T& back() const { | 
|  | SkASSERT(fTotalCount > 0); | 
|  | int blockCount = fAllocator->currentBlock()->metadata(); | 
|  | return *(GetItem(fAllocator->currentBlock(), blockCount - 1)); | 
|  | } | 
|  |  | 
|  | template<bool Const> | 
|  | class Iterator { | 
|  | using BlockIter = typename GrBlockAllocator::BlockIter<true, Const>; | 
|  | using C = typename std::conditional<Const, const T, T>::type; | 
|  | using AllocatorT = typename std::conditional<Const, const GrTAllocator, GrTAllocator>::type; | 
|  | public: | 
|  | Iterator(AllocatorT* allocator) : fBlockIter(allocator->fAllocator.allocator()) {} | 
|  |  | 
|  | class Item { | 
|  | public: | 
|  | bool operator!=(const Item& other) const { | 
|  | if (other.fBlock != fBlock) { | 
|  | // Treat an empty head block the same as the end block | 
|  | bool blockEmpty = !(*fBlock) || (*fBlock)->metadata() == 0; | 
|  | bool otherEmpty = !(*other.fBlock) || (*other.fBlock)->metadata() == 0; | 
|  | return !blockEmpty || !otherEmpty; | 
|  | } else { | 
|  | // Same block, so differentiate by index into it (unless it's the "end" block | 
|  | // in which case ignore index). | 
|  | return SkToBool(*fBlock) && other.fIndex != fIndex; | 
|  | } | 
|  | } | 
|  |  | 
|  | C& operator*() const { | 
|  | C* item = const_cast<C*>(static_cast<const C*>((*fBlock)->ptr(fIndex))); | 
|  | SkDEBUGCODE(int offset;) | 
|  | SkASSERT(item == GetItemAndOffset(*fBlock, fItem, &offset) && offset == fIndex); | 
|  | return *item; | 
|  | } | 
|  |  | 
|  | Item& operator++() { | 
|  | const auto* block = *fBlock; | 
|  | fItem++; | 
|  | fIndex += sizeof(T); | 
|  | if (fItem >= block->metadata()) { | 
|  | ++fBlock; | 
|  | fItem = 0; | 
|  | fIndex = StartingIndex(fBlock); | 
|  | } | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | private: | 
|  | friend Iterator; | 
|  | using BlockItem = typename BlockIter::Item; | 
|  |  | 
|  | Item(BlockItem block) : fBlock(block), fItem(0), fIndex(StartingIndex(block)) {} | 
|  |  | 
|  | static int StartingIndex(const BlockItem& block) { | 
|  | return *block ? (*block)->template firstAlignedOffset<alignof(T)>() : 0; | 
|  | } | 
|  |  | 
|  | BlockItem fBlock; | 
|  | int       fItem; | 
|  | int       fIndex; | 
|  | }; | 
|  |  | 
|  | Item begin() const { return Item(fBlockIter.begin()); } | 
|  | Item end() const { return Item(fBlockIter.end()); } | 
|  |  | 
|  | private: | 
|  | const BlockIter fBlockIter; | 
|  | }; | 
|  |  | 
|  | using Iter = Iterator<false>; | 
|  | using CIter = Iterator<true>; | 
|  |  | 
|  | /** | 
|  | * Prefer using a for-range loop when iterating over all allocated items, vs. index access. | 
|  | */ | 
|  | Iter items() { return Iter(this); } | 
|  | CIter items() const { return CIter(this); } | 
|  |  | 
|  | /** | 
|  | * Access item by index. Not an operator[] since it should not be considered constant time. | 
|  | */ | 
|  | T& item(int i) { | 
|  | // Iterate over blocks until we find the one that contains i. | 
|  | SkASSERT(i >= 0 && i < fTotalCount); | 
|  | for (const auto* b : fAllocator->blocks()) { | 
|  | int blockCount = b->metadata(); | 
|  | if (i < blockCount) { | 
|  | return *GetItem(b, i); | 
|  | } else { | 
|  | i -= blockCount; | 
|  | } | 
|  | } | 
|  | SkUNREACHABLE; | 
|  | } | 
|  | const T& item(int i) const { | 
|  | return const_cast<GrTAllocator<T>*>(this)->item(i); | 
|  | } | 
|  |  | 
|  | private: | 
|  | static constexpr size_t StartingSize = | 
|  | GrBlockAllocator::Overhead<alignof(T)>() + StartingItems * sizeof(T); | 
|  |  | 
|  | static T* GetItemAndOffset(const GrBlockAllocator::Block* block, int index, int* offset) { | 
|  | SkASSERT(index >= 0 && index < block->metadata()); | 
|  | *offset = block->firstAlignedOffset<alignof(T)>() + index * sizeof(T); | 
|  | return const_cast<T*>(static_cast<const T*>(block->ptr(*offset))); | 
|  | } | 
|  |  | 
|  | static T* GetItem(const GrBlockAllocator::Block* block, int index) { | 
|  | int offset; | 
|  | return GetItemAndOffset(block, index, &offset); | 
|  | } | 
|  |  | 
|  | void* pushItem() { | 
|  | // 'template' required because fAllocator is a template, calling a template member | 
|  | auto br = fAllocator->template allocate<alignof(T)>(sizeof(T)); | 
|  | br.fBlock->setMetadata(br.fBlock->metadata() + 1); | 
|  | fTotalCount++; | 
|  | return br.fBlock->ptr(br.fAlignedOffset); | 
|  | } | 
|  |  | 
|  | // Each Block in the allocator tracks their count of items, but it's convenient to store | 
|  | // the sum of their counts as well. | 
|  | int fTotalCount; | 
|  |  | 
|  | // N represents the number of items, whereas GrSBlockAllocator takes total bytes, so must | 
|  | // account for the block allocator's size too. | 
|  | GrSBlockAllocator<StartingSize> fAllocator; | 
|  | }; | 
|  |  | 
|  | #endif |