| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "Test.h" |
| // This is a GPU-backend specific test |
| #if SK_SUPPORT_GPU |
| #include "GrMemoryPool.h" |
| #include "SkRandom.h" |
| #include "SkTArray.h" |
| #include "SkTDArray.h" |
| #include "SkTemplates.h" |
| |
| // A is the top of an inheritance tree of classes that overload op new and |
| // and delete to use a GrMemoryPool. The objects have values of different types |
| // that can be set and checked. |
| class A { |
| public: |
| A() {} |
| virtual void setValues(int v) { |
| fChar = static_cast<char>(v); |
| } |
| virtual bool checkValues(int v) { |
| return fChar == static_cast<char>(v); |
| } |
| virtual ~A() {} |
| |
| void* operator new(size_t size) { |
| if (!gPool.get()) { |
| return ::operator new(size); |
| } else { |
| return gPool->allocate(size); |
| } |
| } |
| |
| void operator delete(void* p) { |
| if (!gPool.get()) { |
| ::operator delete(p); |
| } else { |
| return gPool->release(p); |
| } |
| } |
| |
| static A* Create(SkRandom* r); |
| |
| static void SetAllocator(size_t preallocSize, size_t minAllocSize) { |
| GrMemoryPool* pool = new GrMemoryPool(preallocSize, minAllocSize); |
| gPool.reset(pool); |
| } |
| |
| static void ResetAllocator() { |
| gPool.reset(nullptr); |
| } |
| |
| private: |
| static std::unique_ptr<GrMemoryPool> gPool; |
| char fChar; |
| }; |
| |
| std::unique_ptr<GrMemoryPool> A::gPool; |
| |
| class B : public A { |
| public: |
| B() {} |
| virtual void setValues(int v) { |
| fDouble = static_cast<double>(v); |
| this->INHERITED::setValues(v); |
| } |
| virtual bool checkValues(int v) { |
| return fDouble == static_cast<double>(v) && |
| this->INHERITED::checkValues(v); |
| } |
| virtual ~B() {} |
| |
| private: |
| double fDouble; |
| |
| typedef A INHERITED; |
| }; |
| |
| class C : public A { |
| public: |
| C() {} |
| virtual void setValues(int v) { |
| fInt64 = static_cast<int64_t>(v); |
| this->INHERITED::setValues(v); |
| } |
| virtual bool checkValues(int v) { |
| return fInt64 == static_cast<int64_t>(v) && |
| this->INHERITED::checkValues(v); |
| } |
| virtual ~C() {} |
| |
| private: |
| int64_t fInt64; |
| |
| typedef A INHERITED; |
| }; |
| |
| // D derives from C and owns a dynamically created B |
| class D : public C { |
| public: |
| D() { |
| fB = new B(); |
| } |
| virtual void setValues(int v) { |
| fVoidStar = reinterpret_cast<void*>(static_cast<intptr_t>(v)); |
| this->INHERITED::setValues(v); |
| fB->setValues(v); |
| } |
| virtual bool checkValues(int v) { |
| return fVoidStar == reinterpret_cast<void*>(static_cast<intptr_t>(v)) && |
| fB->checkValues(v) && |
| this->INHERITED::checkValues(v); |
| } |
| virtual ~D() { |
| delete fB; |
| } |
| private: |
| void* fVoidStar; |
| B* fB; |
| |
| typedef C INHERITED; |
| }; |
| |
| class E : public A { |
| public: |
| E() {} |
| virtual void setValues(int v) { |
| for (size_t i = 0; i < SK_ARRAY_COUNT(fIntArray); ++i) { |
| fIntArray[i] = v; |
| } |
| this->INHERITED::setValues(v); |
| } |
| virtual bool checkValues(int v) { |
| bool ok = true; |
| for (size_t i = 0; ok && i < SK_ARRAY_COUNT(fIntArray); ++i) { |
| if (fIntArray[i] != v) { |
| ok = false; |
| } |
| } |
| return ok && this->INHERITED::checkValues(v); |
| } |
| virtual ~E() {} |
| private: |
| int fIntArray[20]; |
| |
| typedef A INHERITED; |
| }; |
| |
| A* A::Create(SkRandom* r) { |
| switch (r->nextRangeU(0, 4)) { |
| case 0: |
| return new A; |
| case 1: |
| return new B; |
| case 2: |
| return new C; |
| case 3: |
| return new D; |
| case 4: |
| return new E; |
| default: |
| // suppress warning |
| return nullptr; |
| } |
| } |
| |
| struct Rec { |
| A* fInstance; |
| int fValue; |
| }; |
| |
| DEF_TEST(GrMemoryPool, reporter) { |
| // prealloc and min alloc sizes for the pool |
| static const size_t gSizes[][2] = { |
| {0, 0}, |
| {10 * sizeof(A), 20 * sizeof(A)}, |
| {100 * sizeof(A), 100 * sizeof(A)}, |
| {500 * sizeof(A), 500 * sizeof(A)}, |
| {10000 * sizeof(A), 0}, |
| {1, 100 * sizeof(A)}, |
| }; |
| // different percentages of creation vs deletion |
| static const float gCreateFraction[] = {1.f, .95f, 0.75f, .5f}; |
| // number of create/destroys per test |
| static const int kNumIters = 20000; |
| // check that all the values stored in A objects are correct after this |
| // number of iterations |
| static const int kCheckPeriod = 500; |
| |
| SkRandom r; |
| for (size_t s = 0; s < SK_ARRAY_COUNT(gSizes); ++s) { |
| A::SetAllocator(gSizes[s][0], gSizes[s][1]); |
| for (size_t c = 0; c < SK_ARRAY_COUNT(gCreateFraction); ++c) { |
| SkTDArray<Rec> instanceRecs; |
| for (int i = 0; i < kNumIters; ++i) { |
| float createOrDestroy = r.nextUScalar1(); |
| if (createOrDestroy < gCreateFraction[c] || |
| 0 == instanceRecs.count()) { |
| Rec* rec = instanceRecs.append(); |
| rec->fInstance = A::Create(&r); |
| rec->fValue = static_cast<int>(r.nextU()); |
| rec->fInstance->setValues(rec->fValue); |
| } else { |
| int d = r.nextRangeU(0, instanceRecs.count() - 1); |
| Rec& rec = instanceRecs[d]; |
| REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue)); |
| delete rec.fInstance; |
| instanceRecs.removeShuffle(d); |
| } |
| if (0 == i % kCheckPeriod) { |
| for (int r = 0; r < instanceRecs.count(); ++r) { |
| Rec& rec = instanceRecs[r]; |
| REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue)); |
| } |
| } |
| } |
| for (int i = 0; i < instanceRecs.count(); ++i) { |
| Rec& rec = instanceRecs[i]; |
| REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue)); |
| delete rec.fInstance; |
| } |
| } |
| } |
| } |
| |
| // GrMemoryPool requires that it's empty at the point of destruction. This helps |
| // achieving that by releasing all added memory in the destructor. |
| class AutoPoolReleaser { |
| public: |
| AutoPoolReleaser(GrMemoryPool& pool): fPool(pool) { |
| } |
| ~AutoPoolReleaser() { |
| for (void* ptr: fAllocated) { |
| fPool.release(ptr); |
| } |
| } |
| void add(void* ptr) { |
| fAllocated.push_back(ptr); |
| } |
| private: |
| GrMemoryPool& fPool; |
| SkTArray<void*> fAllocated; |
| }; |
| |
| DEF_TEST(GrMemoryPoolAPI, reporter) { |
| constexpr size_t kSmallestMinAllocSize = GrMemoryPool::kSmallestMinAllocSize; |
| |
| // Allocates memory until pool adds a new block (pool.size() changes). |
| auto allocateMemory = [](GrMemoryPool& pool, AutoPoolReleaser& r) { |
| size_t origPoolSize = pool.size(); |
| while (pool.size() == origPoolSize) { |
| r.add(pool.allocate(31)); |
| } |
| }; |
| |
| // Effective prealloc space capacity is >= kSmallestMinAllocSize. |
| { |
| GrMemoryPool pool(0, 0); |
| REPORTER_ASSERT(reporter, pool.preallocSize() == kSmallestMinAllocSize); |
| } |
| |
| // Effective prealloc space capacity is >= minAllocSize. |
| { |
| constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 2; |
| GrMemoryPool pool(kSmallestMinAllocSize, kMinAllocSize); |
| REPORTER_ASSERT(reporter, pool.preallocSize() == kMinAllocSize); |
| } |
| |
| // Effective block size capacity >= kSmallestMinAllocSize. |
| { |
| GrMemoryPool pool(kSmallestMinAllocSize, kSmallestMinAllocSize / 2); |
| AutoPoolReleaser r(pool); |
| |
| allocateMemory(pool, r); |
| REPORTER_ASSERT(reporter, pool.size() == kSmallestMinAllocSize); |
| } |
| |
| // Pool allocates exactly preallocSize on creation. |
| { |
| constexpr size_t kPreallocSize = kSmallestMinAllocSize * 5; |
| GrMemoryPool pool(kPreallocSize, 0); |
| REPORTER_ASSERT(reporter, pool.preallocSize() == kPreallocSize); |
| } |
| |
| // Pool allocates exactly minAllocSize when it expands. |
| { |
| constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 7; |
| GrMemoryPool pool(0, kMinAllocSize); |
| AutoPoolReleaser r(pool); |
| |
| allocateMemory(pool, r); |
| REPORTER_ASSERT(reporter, pool.size() == kMinAllocSize); |
| |
| allocateMemory(pool, r); |
| REPORTER_ASSERT(reporter, pool.size() == 2 * kMinAllocSize); |
| } |
| |
| // When asked to allocate amount > minAllocSize, pool allocates larger block |
| // to accommodate all internal structures. |
| { |
| constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 2; |
| GrMemoryPool pool(kSmallestMinAllocSize, kMinAllocSize); |
| AutoPoolReleaser r(pool); |
| |
| REPORTER_ASSERT(reporter, pool.size() == 0); |
| |
| constexpr size_t hugeSize = 10 * kMinAllocSize; |
| r.add(pool.allocate(hugeSize)); |
| REPORTER_ASSERT(reporter, pool.size() > hugeSize); |
| |
| // Block size allocated to accommodate huge request doesn't include any extra |
| // space, so next allocation request allocates a new block. |
| size_t hugeBlockSize = pool.size(); |
| r.add(pool.allocate(0)); |
| REPORTER_ASSERT(reporter, pool.size() == hugeBlockSize + kMinAllocSize); |
| } |
| } |
| |
| DEF_TEST(GrObjectMemoryPoolAPI, reporter) { |
| struct Data { |
| int value[5]; |
| }; |
| using DataObjectPool = GrObjectMemoryPool<Data>; |
| constexpr size_t kSmallestMinAllocCount = DataObjectPool::kSmallestMinAllocCount; |
| |
| // Allocates objects until pool adds a new block (pool.size() changes). |
| // Returns number of objects that fit into the current block (i.e. before pool.size() |
| // changed; newly allocated block always ends up with one object allocated from it). |
| auto allocateObjects = [](DataObjectPool& pool, AutoPoolReleaser& r) -> size_t { |
| size_t count = 0; |
| size_t origPoolSize = pool.size(); |
| while (pool.size() == origPoolSize) { |
| r.add(pool.allocate()); |
| count++; |
| } |
| return count - 1; |
| }; |
| |
| // Effective prealloc space capacity is >= kSmallestMinAllocCount. |
| { |
| DataObjectPool pool(kSmallestMinAllocCount / 3, 0); |
| AutoPoolReleaser r(pool); |
| |
| size_t preallocCount = allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, preallocCount == kSmallestMinAllocCount); |
| } |
| |
| // Effective prealloc space capacity is >= minAllocCount. |
| { |
| DataObjectPool pool(kSmallestMinAllocCount, 2 * kSmallestMinAllocCount); |
| AutoPoolReleaser r(pool); |
| |
| size_t preallocCount = allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, preallocCount == 2 * kSmallestMinAllocCount); |
| } |
| |
| // Effective block capacity is >= kSmallestMinAllocCount. |
| { |
| DataObjectPool pool(kSmallestMinAllocCount, kSmallestMinAllocCount / 2); |
| AutoPoolReleaser r(pool); |
| |
| // Fill prealloc space |
| allocateObjects(pool, r); |
| |
| size_t minAllocCount = 1 + allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, minAllocCount == kSmallestMinAllocCount); |
| } |
| |
| // Pool allocates space for exactly preallocCount objects on creation. |
| { |
| constexpr size_t kPreallocCount = kSmallestMinAllocCount * 7 / 3; |
| DataObjectPool pool(kPreallocCount, 0); |
| AutoPoolReleaser r(pool); |
| |
| size_t preallocCount = allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, preallocCount == kPreallocCount); |
| } |
| |
| // Pool allocates space for minAllocCount objects when it adds a new block. |
| { |
| constexpr size_t kMinAllocCount = kSmallestMinAllocCount * 11 / 3; |
| DataObjectPool pool(0, kMinAllocCount); |
| AutoPoolReleaser r(pool); |
| |
| // Fill prealloc space |
| allocateObjects(pool, r); |
| |
| size_t firstBlockCount = 1 + allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, firstBlockCount == kMinAllocCount); |
| |
| size_t secondBlockCount = 1 + allocateObjects(pool, r); |
| REPORTER_ASSERT(reporter, secondBlockCount == kMinAllocCount); |
| } |
| } |
| |
| #endif |