| /* |
| * 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 "include/private/base/SkTArray.h" |
| #include "include/private/base/SkTDArray.h" |
| #include "include/utils/SkRandom.h" |
| #include "src/gpu/ganesh/GrMemoryPool.h" |
| #include "tests/Test.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| |
| // 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 & 0xFF); |
| } |
| virtual bool checkValues(int v) { |
| return fChar == static_cast<char>(v & 0xFF); |
| } |
| virtual ~A() {} |
| |
| void* operator new(size_t size) { |
| if (!gPool) { |
| return ::operator new(size); |
| } else { |
| return gPool->allocate(size); |
| } |
| } |
| |
| void operator delete(void* p) { |
| if (!gPool) { |
| ::operator delete(p); |
| } else { |
| return gPool->release(p); |
| } |
| } |
| |
| static A* Create(SkRandom* r); |
| |
| static void SetAllocator(size_t preallocSize, size_t minAllocSize) { |
| gPool = GrMemoryPool::Make(preallocSize, minAllocSize); |
| } |
| |
| static void ResetAllocator() { gPool.reset(); } |
| |
| static void ValidatePool() { |
| #ifdef SK_DEBUG |
| gPool->validate(); |
| #endif |
| } |
| |
| private: |
| static std::unique_ptr<GrMemoryPool> gPool; |
| char fChar; |
| }; |
| |
| std::unique_ptr<GrMemoryPool> A::gPool; |
| |
| class B : public A { |
| public: |
| B() {} |
| void setValues(int v) override { |
| fDouble = static_cast<double>(v); |
| this->INHERITED::setValues(v); |
| } |
| bool checkValues(int v) override { |
| return fDouble == static_cast<double>(v) && |
| this->INHERITED::checkValues(v); |
| } |
| |
| private: |
| double fDouble; |
| |
| using INHERITED = A; |
| }; |
| |
| class C : public A { |
| public: |
| C() {} |
| void setValues(int v) override { |
| fInt64 = static_cast<int64_t>(v); |
| this->INHERITED::setValues(v); |
| } |
| bool checkValues(int v) override { |
| return fInt64 == static_cast<int64_t>(v) && |
| this->INHERITED::checkValues(v); |
| } |
| |
| private: |
| int64_t fInt64; |
| |
| using INHERITED = A; |
| }; |
| |
| // D derives from C and owns a dynamically created B |
| class D : public C { |
| public: |
| D() { |
| fB = new B(); |
| } |
| void setValues(int v) override { |
| fVoidStar = reinterpret_cast<void*>(static_cast<intptr_t>(v)); |
| this->INHERITED::setValues(v); |
| fB->setValues(v); |
| } |
| bool checkValues(int v) override { |
| return fVoidStar == reinterpret_cast<void*>(static_cast<intptr_t>(v)) && |
| fB->checkValues(v) && |
| this->INHERITED::checkValues(v); |
| } |
| ~D() override { |
| delete fB; |
| } |
| private: |
| void* fVoidStar; |
| B* fB; |
| |
| using INHERITED = C; |
| }; |
| |
| class E : public A { |
| public: |
| E() {} |
| void setValues(int v) override { |
| for (size_t i = 0; i < std::size(fIntArray); ++i) { |
| fIntArray[i] = v; |
| } |
| this->INHERITED::setValues(v); |
| } |
| bool checkValues(int v) override { |
| bool ok = true; |
| for (size_t i = 0; ok && i < std::size(fIntArray); ++i) { |
| if (fIntArray[i] != v) { |
| ok = false; |
| } |
| } |
| return ok && this->INHERITED::checkValues(v); |
| } |
| private: |
| int fIntArray[20]; |
| |
| using INHERITED = A; |
| }; |
| |
| 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 < std::size(gSizes); ++s) { |
| A::SetAllocator(gSizes[s][0], gSizes[s][1]); |
| A::ValidatePool(); |
| for (size_t c = 0; c < std::size(gCreateFraction); ++c) { |
| SkTDArray<Rec> instanceRecs; |
| for (int i = 0; i < kNumIters; ++i) { |
| float createOrDestroy = r.nextUScalar1(); |
| if (createOrDestroy < gCreateFraction[c] || |
| 0 == instanceRecs.size()) { |
| 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.size() - 1); |
| Rec& rec = instanceRecs[d]; |
| REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue)); |
| delete rec.fInstance; |
| instanceRecs.removeShuffle(d); |
| } |
| if (0 == i % kCheckPeriod) { |
| A::ValidatePool(); |
| for (Rec& rec : instanceRecs) { |
| REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue)); |
| } |
| } |
| } |
| for (Rec& rec : instanceRecs) { |
| 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::kMinAllocationSize; |
| |
| // 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 >= kMinAllocationSize. |
| { |
| auto pool = GrMemoryPool::Make(0, 0); |
| REPORTER_ASSERT(reporter, pool->preallocSize() == kSmallestMinAllocSize); |
| } |
| |
| // Effective block size capacity >= kMinAllocationSize. |
| { |
| auto pool = GrMemoryPool::Make(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; |
| auto pool = GrMemoryPool::Make(kPreallocSize, 0); |
| REPORTER_ASSERT(reporter, pool->preallocSize() == kPreallocSize); |
| } |
| |
| // Pool allocates exactly minAllocSize when it expands. |
| { |
| constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 7; |
| auto pool = GrMemoryPool::Make(0, kMinAllocSize); |
| AutoPoolReleaser r(*pool); |
| REPORTER_ASSERT(reporter, pool->size() == 0); |
| |
| 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; |
| auto pool = GrMemoryPool::Make(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); |
| } |
| } |