/* * 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 GrAllocator_DEFINED #define GrAllocator_DEFINED #include "GrConfig.h" #include "GrTypes.h" #include "SkNoncopyable.h" #include "SkTArray.h" #include "SkTypes.h" #include class GrAllocator : SkNoncopyable { public: ~GrAllocator() { this->reset(); } /** * Create an allocator * * @param itemSize the size of each item to allocate * @param itemsPerBlock the number of items to allocate at once * @param initialBlock optional memory to use for the first block. * Must be at least itemSize*itemsPerBlock sized. * Caller is responsible for freeing this memory. */ GrAllocator(size_t itemSize, int itemsPerBlock, void* initialBlock) : fItemSize(itemSize) , fItemsPerBlock(itemsPerBlock) , fOwnFirstBlock(nullptr == initialBlock) , fCount(0) , fInsertionIndexInBlock(0) { SkASSERT(itemsPerBlock > 0); fBlockSize = fItemSize * fItemsPerBlock; if (fOwnFirstBlock) { // This force us to allocate a new block on push_back(). fInsertionIndexInBlock = fItemsPerBlock; } else { fBlocks.push_back() = initialBlock; fInsertionIndexInBlock = 0; } } /** * Adds an item and returns pointer to it. * * @return pointer to the added item. */ void* push_back() { // we always have at least one block if (fItemsPerBlock == fInsertionIndexInBlock) { fBlocks.push_back() = sk_malloc_throw(fBlockSize); fInsertionIndexInBlock = 0; } void* ret = (char*)fBlocks.back() + fItemSize * fInsertionIndexInBlock; ++fCount; ++fInsertionIndexInBlock; return ret; } /** * Remove the last item, only call if count() != 0 */ void pop_back() { SkASSERT(fCount); SkASSERT(fInsertionIndexInBlock > 0); --fInsertionIndexInBlock; --fCount; if (0 == fInsertionIndexInBlock) { // Never delete the first block if (fBlocks.count() > 1) { sk_free(fBlocks.back()); fBlocks.pop_back(); fInsertionIndexInBlock = fItemsPerBlock; } } } /** * Removes all added items. */ void reset() { int firstBlockToFree = fOwnFirstBlock ? 0 : 1; for (int i = firstBlockToFree; i < fBlocks.count(); ++i) { sk_free(fBlocks[i]); } if (fOwnFirstBlock) { fBlocks.reset(); // This force us to allocate a new block on push_back(). fInsertionIndexInBlock = fItemsPerBlock; } else { fBlocks.pop_back_n(fBlocks.count() - 1); fInsertionIndexInBlock = 0; } fCount = 0; } /** * Returns the item count. */ int count() const { return fCount; } /** * Is the count 0? */ bool empty() const { return 0 == fCount; } /** * Access first item, only call if count() != 0 */ void* front() { SkASSERT(fCount); SkASSERT(fInsertionIndexInBlock > 0); return (char*)(fBlocks.front()); } /** * Access first item, only call if count() != 0 */ const void* front() const { SkASSERT(fCount); SkASSERT(fInsertionIndexInBlock > 0); return (const char*)(fBlocks.front()); } /** * Access last item, only call if count() != 0 */ void* back() { SkASSERT(fCount); SkASSERT(fInsertionIndexInBlock > 0); return (char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize; } /** * Access last item, only call if count() != 0 */ const void* back() const { SkASSERT(fCount); SkASSERT(fInsertionIndexInBlock > 0); return (const char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize; } /** * Iterates through the allocator. This is faster than using operator[] when walking linearly * through the allocator. */ class Iter { public: /** * Initializes the iterator. next() must be called before get(). */ Iter(const GrAllocator* allocator) : fAllocator(allocator) , fBlockIndex(-1) , fIndexInBlock(allocator->fItemsPerBlock - 1) , fItemIndex(-1) {} /** * Advances the iterator. Iteration is finished when next() returns false. */ bool next() { ++fIndexInBlock; ++fItemIndex; if (fIndexInBlock == fAllocator->fItemsPerBlock) { ++fBlockIndex; fIndexInBlock = 0; } return fItemIndex < fAllocator->fCount; } /** * Gets the current iterator value. Call next() at least once before calling. Don't call * after next() returns false. */ void* get() const { SkASSERT(fItemIndex >= 0 && fItemIndex < fAllocator->fCount); return (char*) fAllocator->fBlocks[fBlockIndex] + fIndexInBlock * fAllocator->fItemSize; } private: const GrAllocator* fAllocator; int fBlockIndex; int fIndexInBlock; int fItemIndex; }; /** * Access item by index. */ void* operator[] (int i) { SkASSERT(i >= 0 && i < fCount); return (char*)fBlocks[i / fItemsPerBlock] + fItemSize * (i % fItemsPerBlock); } /** * Access item by index. */ const void* operator[] (int i) const { SkASSERT(i >= 0 && i < fCount); return (const char*)fBlocks[i / fItemsPerBlock] + fItemSize * (i % fItemsPerBlock); } protected: /** * Set first block of memory to write into. Must be called before any other methods. * This requires that you have passed nullptr in the constructor. * * @param initialBlock optional memory to use for the first block. * Must be at least itemSize*itemsPerBlock sized. * Caller is responsible for freeing this memory. */ void setInitialBlock(void* initialBlock) { SkASSERT(0 == fCount); SkASSERT(0 == fBlocks.count()); SkASSERT(fItemsPerBlock == fInsertionIndexInBlock); fOwnFirstBlock = false; fBlocks.push_back() = initialBlock; fInsertionIndexInBlock = 0; } // For access to above function. template friend class GrTAllocator; private: static const int NUM_INIT_BLOCK_PTRS = 8; SkSTArray fBlocks; size_t fBlockSize; size_t fItemSize; int fItemsPerBlock; bool fOwnFirstBlock; int fCount; int fInsertionIndexInBlock; typedef SkNoncopyable INHERITED; }; template class GrTAllocator; template void* operator new(size_t, GrTAllocator*); template class GrTAllocator : SkNoncopyable { public: virtual ~GrTAllocator() { this->reset(); } /** * Create an allocator * * @param itemsPerBlock the number of items to allocate at once */ explicit GrTAllocator(int itemsPerBlock) : fAllocator(sizeof(T), itemsPerBlock, nullptr) {} /** * Adds an item and returns it. * * @return the added item. */ T& push_back() { void* item = fAllocator.push_back(); SkASSERT(item); new (item) T; return *(T*)item; } T& push_back(const T& t) { void* item = fAllocator.push_back(); SkASSERT(item); new (item) T(t); return *(T*)item; } template T& emplace_back(Args&&... args) { void* item = fAllocator.push_back(); SkASSERT(item); new (item) T(std::forward(args)...); return *(T*)item; } /** * Remove the last item, only call if count() != 0 */ void pop_back() { this->back().~T(); fAllocator.pop_back(); } /** * Removes all added items. */ void reset() { int c = fAllocator.count(); for (int i = 0; i < c; ++i) { ((T*)fAllocator[i])->~T(); } fAllocator.reset(); } /** * Returns the item count. */ int count() const { return fAllocator.count(); } /** * Is the count 0? */ bool empty() const { return fAllocator.empty(); } /** * Access first item, only call if count() != 0 */ T& front() { return *(T*)fAllocator.front(); } /** * Access first item, only call if count() != 0 */ const T& front() const { return *(T*)fAllocator.front(); } /** * Access last item, only call if count() != 0 */ T& back() { return *(T*)fAllocator.back(); } /** * Access last item, only call if count() != 0 */ const T& back() const { return *(const T*)fAllocator.back(); } /** * Iterates through the allocator. This is faster than using operator[] when walking linearly * through the allocator. */ class Iter { public: /** * Initializes the iterator. next() must be called before get() or ops * and ->. */ Iter(const GrTAllocator* allocator) : fImpl(&allocator->fAllocator) {} /** * Advances the iterator. Iteration is finished when next() returns false. */ bool next() { return fImpl.next(); } /** * Gets the current iterator value. Call next() at least once before calling. Don't call * after next() returns false. */ T* get() const { return (T*) fImpl.get(); } /** * Convenience operators. Same rules for calling apply as get(). */ T& operator*() const { return *this->get(); } T* operator->() const { return this->get(); } private: GrAllocator::Iter fImpl; }; /** * Access item by index. */ T& operator[] (int i) { return *(T*)(fAllocator[i]); } /** * Access item by index. */ const T& operator[] (int i) const { return *(const T*)(fAllocator[i]); } protected: /* * Set first block of memory to write into. Must be called before any other methods. * * @param initialBlock optional memory to use for the first block. * Must be at least size(T)*itemsPerBlock sized. * Caller is responsible for freeing this memory. */ void setInitialBlock(void* initialBlock) { fAllocator.setInitialBlock(initialBlock); } private: friend void* operator new(size_t, GrTAllocator*); GrAllocator fAllocator; typedef SkNoncopyable INHERITED; }; template class GrSTAllocator : public GrTAllocator { private: typedef GrTAllocator INHERITED; public: GrSTAllocator() : INHERITED(N) { this->setInitialBlock(fStorage.get()); } private: SkAlignedSTStorage fStorage; }; template void* operator new(size_t size, GrTAllocator* allocator) { return allocator->fAllocator.push_back(); } // Skia doesn't use C++ exceptions but it may be compiled with them enabled. Having an op delete // to match the op new silences warnings about missing op delete when a constructor throws an // exception. template void operator delete(void*, GrTAllocator*) { SK_ABORT("Invalid Operation"); } #define GrNEW_APPEND_TO_ALLOCATOR(allocator_ptr, type_name, args) \ new (allocator_ptr) type_name args #endif