blob: 74f778ed76db5d2ad6ca1b5337975b80f8aabd01 [file] [log] [blame]
/*
* 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 "src/base/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);
}
}