1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/private/base/SkTArray.h"
9 #include "include/private/base/SkTDArray.h"
10 #include "src/base/SkRandom.h"
11 #include "src/gpu/ganesh/GrMemoryPool.h"
12 #include "tests/Test.h"
13
14 #include <array>
15 #include <cstddef>
16 #include <cstdint>
17 #include <memory>
18
19 // A is the top of an inheritance tree of classes that overload op new and
20 // and delete to use a GrMemoryPool. The objects have values of different types
21 // that can be set and checked.
22 class A {
23 public:
A()24 A() {}
setValues(int v)25 virtual void setValues(int v) {
26 fChar = static_cast<char>(v & 0xFF);
27 }
checkValues(int v)28 virtual bool checkValues(int v) {
29 return fChar == static_cast<char>(v & 0xFF);
30 }
~A()31 virtual ~A() {}
32
operator new(size_t size)33 void* operator new(size_t size) {
34 if (!gPool) {
35 return ::operator new(size);
36 } else {
37 return gPool->allocate(size);
38 }
39 }
40
operator delete(void * p)41 void operator delete(void* p) {
42 if (!gPool) {
43 ::operator delete(p);
44 } else {
45 return gPool->release(p);
46 }
47 }
48
49 static A* Create(SkRandom* r);
50
SetAllocator(size_t preallocSize,size_t minAllocSize)51 static void SetAllocator(size_t preallocSize, size_t minAllocSize) {
52 gPool = GrMemoryPool::Make(preallocSize, minAllocSize);
53 }
54
ResetAllocator()55 static void ResetAllocator() { gPool.reset(); }
56
ValidatePool()57 static void ValidatePool() {
58 #ifdef SK_DEBUG
59 gPool->validate();
60 #endif
61 }
62
63 private:
64 static std::unique_ptr<GrMemoryPool> gPool;
65 char fChar;
66 };
67
68 std::unique_ptr<GrMemoryPool> A::gPool;
69
70 class B : public A {
71 public:
B()72 B() {}
setValues(int v)73 void setValues(int v) override {
74 fDouble = static_cast<double>(v);
75 this->INHERITED::setValues(v);
76 }
checkValues(int v)77 bool checkValues(int v) override {
78 return fDouble == static_cast<double>(v) &&
79 this->INHERITED::checkValues(v);
80 }
81
82 private:
83 double fDouble;
84
85 using INHERITED = A;
86 };
87
88 class C : public A {
89 public:
C()90 C() {}
setValues(int v)91 void setValues(int v) override {
92 fInt64 = static_cast<int64_t>(v);
93 this->INHERITED::setValues(v);
94 }
checkValues(int v)95 bool checkValues(int v) override {
96 return fInt64 == static_cast<int64_t>(v) &&
97 this->INHERITED::checkValues(v);
98 }
99
100 private:
101 int64_t fInt64;
102
103 using INHERITED = A;
104 };
105
106 // D derives from C and owns a dynamically created B
107 class D : public C {
108 public:
D()109 D() {
110 fB = new B();
111 }
setValues(int v)112 void setValues(int v) override {
113 fVoidStar = reinterpret_cast<void*>(static_cast<intptr_t>(v));
114 this->INHERITED::setValues(v);
115 fB->setValues(v);
116 }
checkValues(int v)117 bool checkValues(int v) override {
118 return fVoidStar == reinterpret_cast<void*>(static_cast<intptr_t>(v)) &&
119 fB->checkValues(v) &&
120 this->INHERITED::checkValues(v);
121 }
~D()122 ~D() override {
123 delete fB;
124 }
125 private:
126 void* fVoidStar;
127 B* fB;
128
129 using INHERITED = C;
130 };
131
132 class E : public A {
133 public:
E()134 E() {}
setValues(int v)135 void setValues(int v) override {
136 for (size_t i = 0; i < std::size(fIntArray); ++i) {
137 fIntArray[i] = v;
138 }
139 this->INHERITED::setValues(v);
140 }
checkValues(int v)141 bool checkValues(int v) override {
142 bool ok = true;
143 for (size_t i = 0; ok && i < std::size(fIntArray); ++i) {
144 if (fIntArray[i] != v) {
145 ok = false;
146 }
147 }
148 return ok && this->INHERITED::checkValues(v);
149 }
150 private:
151 int fIntArray[20];
152
153 using INHERITED = A;
154 };
155
Create(SkRandom * r)156 A* A::Create(SkRandom* r) {
157 switch (r->nextRangeU(0, 4)) {
158 case 0:
159 return new A;
160 case 1:
161 return new B;
162 case 2:
163 return new C;
164 case 3:
165 return new D;
166 case 4:
167 return new E;
168 default:
169 // suppress warning
170 return nullptr;
171 }
172 }
173
174 struct Rec {
175 A* fInstance;
176 int fValue;
177 };
178
DEF_TEST(GrMemoryPool,reporter)179 DEF_TEST(GrMemoryPool, reporter) {
180 // prealloc and min alloc sizes for the pool
181 static const size_t gSizes[][2] = {
182 {0, 0},
183 {10 * sizeof(A), 20 * sizeof(A)},
184 {100 * sizeof(A), 100 * sizeof(A)},
185 {500 * sizeof(A), 500 * sizeof(A)},
186 {10000 * sizeof(A), 0},
187 {1, 100 * sizeof(A)},
188 };
189
190 // different percentages of creation vs deletion
191 static const float gCreateFraction[] = {1.f, .95f, 0.75f, .5f};
192 // number of create/destroys per test
193 static const int kNumIters = 20000;
194 // check that all the values stored in A objects are correct after this
195 // number of iterations
196 static const int kCheckPeriod = 500;
197
198 SkRandom r;
199 for (size_t s = 0; s < std::size(gSizes); ++s) {
200 A::SetAllocator(gSizes[s][0], gSizes[s][1]);
201 A::ValidatePool();
202 for (size_t c = 0; c < std::size(gCreateFraction); ++c) {
203 SkTDArray<Rec> instanceRecs;
204 for (int i = 0; i < kNumIters; ++i) {
205 float createOrDestroy = r.nextUScalar1();
206 if (createOrDestroy < gCreateFraction[c] ||
207 0 == instanceRecs.size()) {
208 Rec* rec = instanceRecs.append();
209 rec->fInstance = A::Create(&r);
210 rec->fValue = static_cast<int>(r.nextU());
211 rec->fInstance->setValues(rec->fValue);
212 } else {
213 int d = r.nextRangeU(0, instanceRecs.size() - 1);
214 Rec& rec = instanceRecs[d];
215 REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue));
216 delete rec.fInstance;
217 instanceRecs.removeShuffle(d);
218 }
219 if (0 == i % kCheckPeriod) {
220 A::ValidatePool();
221 for (Rec& rec : instanceRecs) {
222 REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue));
223 }
224 }
225 }
226 for (Rec& rec : instanceRecs) {
227 REPORTER_ASSERT(reporter, rec.fInstance->checkValues(rec.fValue));
228 delete rec.fInstance;
229 }
230 }
231 }
232 }
233
234 // GrMemoryPool requires that it's empty at the point of destruction. This helps
235 // achieving that by releasing all added memory in the destructor.
236 class AutoPoolReleaser {
237 public:
AutoPoolReleaser(GrMemoryPool & pool)238 AutoPoolReleaser(GrMemoryPool& pool): fPool(pool) {
239 }
~AutoPoolReleaser()240 ~AutoPoolReleaser() {
241 for (void* ptr: fAllocated) {
242 fPool.release(ptr);
243 }
244 }
add(void * ptr)245 void add(void* ptr) {
246 fAllocated.push_back(ptr);
247 }
248 private:
249 GrMemoryPool& fPool;
250 SkTArray<void*> fAllocated;
251 };
252
DEF_TEST(GrMemoryPoolAPI,reporter)253 DEF_TEST(GrMemoryPoolAPI, reporter) {
254 constexpr size_t kSmallestMinAllocSize = GrMemoryPool::kMinAllocationSize;
255
256 // Allocates memory until pool adds a new block (pool->size() changes).
257 auto allocateMemory = [](GrMemoryPool& pool, AutoPoolReleaser& r) {
258 size_t origPoolSize = pool.size();
259 while (pool.size() == origPoolSize) {
260 r.add(pool.allocate(31));
261 }
262 };
263
264 // Effective prealloc space capacity is >= kMinAllocationSize.
265 {
266 auto pool = GrMemoryPool::Make(0, 0);
267 REPORTER_ASSERT(reporter, pool->preallocSize() == kSmallestMinAllocSize);
268 }
269
270 // Effective block size capacity >= kMinAllocationSize.
271 {
272 auto pool = GrMemoryPool::Make(kSmallestMinAllocSize, kSmallestMinAllocSize / 2);
273 AutoPoolReleaser r(*pool);
274
275 allocateMemory(*pool, r);
276 REPORTER_ASSERT(reporter, pool->size() == kSmallestMinAllocSize);
277 }
278
279 // Pool allocates exactly preallocSize on creation.
280 {
281 constexpr size_t kPreallocSize = kSmallestMinAllocSize * 5;
282 auto pool = GrMemoryPool::Make(kPreallocSize, 0);
283 REPORTER_ASSERT(reporter, pool->preallocSize() == kPreallocSize);
284 }
285
286 // Pool allocates exactly minAllocSize when it expands.
287 {
288 constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 7;
289 auto pool = GrMemoryPool::Make(0, kMinAllocSize);
290 AutoPoolReleaser r(*pool);
291 REPORTER_ASSERT(reporter, pool->size() == 0);
292
293 allocateMemory(*pool, r);
294 REPORTER_ASSERT(reporter, pool->size() == kMinAllocSize);
295
296 allocateMemory(*pool, r);
297 REPORTER_ASSERT(reporter, pool->size() == 2 * kMinAllocSize);
298 }
299
300 // When asked to allocate amount > minAllocSize, pool allocates larger block
301 // to accommodate all internal structures.
302 {
303 constexpr size_t kMinAllocSize = kSmallestMinAllocSize * 2;
304 auto pool = GrMemoryPool::Make(kSmallestMinAllocSize, kMinAllocSize);
305 AutoPoolReleaser r(*pool);
306
307 REPORTER_ASSERT(reporter, pool->size() == 0);
308
309 constexpr size_t hugeSize = 10 * kMinAllocSize;
310 r.add(pool->allocate(hugeSize));
311 REPORTER_ASSERT(reporter, pool->size() > hugeSize);
312
313 // Block size allocated to accommodate huge request doesn't include any extra
314 // space, so next allocation request allocates a new block.
315 size_t hugeBlockSize = pool->size();
316 r.add(pool->allocate(0));
317 REPORTER_ASSERT(reporter, pool->size() == hugeBlockSize + kMinAllocSize);
318 }
319 }
320