/* * 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