1
2/*
3 * Copyright 2010 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "GrBufferAllocPool.h"
11#include "GrDrawTargetCaps.h"
12#include "GrGpu.h"
13#include "GrIndexBuffer.h"
14#include "GrTypes.h"
15#include "GrVertexBuffer.h"
16
17#include "SkTraceEvent.h"
18
19#ifdef SK_DEBUG
20    #define VALIDATE validate
21#else
22    static void VALIDATE(bool = false) {}
23#endif
24
25// page size
26#define GrBufferAllocPool_MIN_BLOCK_SIZE ((size_t)1 << 12)
27
28#define UNMAP_BUFFER(block)                                                               \
29do {                                                                                      \
30    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("skia.gpu"),                           \
31                         "GrBufferAllocPool Unmapping Buffer",                            \
32                         TRACE_EVENT_SCOPE_THREAD,                                        \
33                         "percent_unwritten",                                             \
34                         (float)((block).fBytesFree) / (block).fBuffer->gpuMemorySize()); \
35    (block).fBuffer->unmap();                                                             \
36} while (false)
37
38GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu,
39                                     BufferType bufferType,
40                                     bool frequentResetHint,
41                                     size_t blockSize,
42                                     int preallocBufferCnt) :
43        fBlocks(SkTMax(8, 2*preallocBufferCnt)) {
44
45    SkASSERT(gpu);
46    fGpu = gpu;
47    fGpu->ref();
48    fGpuIsReffed = true;
49
50    fBufferType = bufferType;
51    fFrequentResetHint = frequentResetHint;
52    fBufferPtr = NULL;
53    fMinBlockSize = SkTMax(GrBufferAllocPool_MIN_BLOCK_SIZE, blockSize);
54
55    fBytesInUse = 0;
56
57    fPreallocBuffersInUse = 0;
58    fPreallocBufferStartIdx = 0;
59    for (int i = 0; i < preallocBufferCnt; ++i) {
60        GrGeometryBuffer* buffer = this->createBuffer(fMinBlockSize);
61        if (buffer) {
62            *fPreallocBuffers.append() = buffer;
63        }
64    }
65}
66
67GrBufferAllocPool::~GrBufferAllocPool() {
68    VALIDATE();
69    if (fBlocks.count()) {
70        GrGeometryBuffer* buffer = fBlocks.back().fBuffer;
71        if (buffer->isMapped()) {
72            UNMAP_BUFFER(fBlocks.back());
73        }
74    }
75    while (!fBlocks.empty()) {
76        destroyBlock();
77    }
78    fPreallocBuffers.unrefAll();
79    releaseGpuRef();
80}
81
82void GrBufferAllocPool::releaseGpuRef() {
83    if (fGpuIsReffed) {
84        fGpu->unref();
85        fGpuIsReffed = false;
86    }
87}
88
89void GrBufferAllocPool::reset() {
90    VALIDATE();
91    fBytesInUse = 0;
92    if (fBlocks.count()) {
93        GrGeometryBuffer* buffer = fBlocks.back().fBuffer;
94        if (buffer->isMapped()) {
95            UNMAP_BUFFER(fBlocks.back());
96        }
97    }
98    // fPreallocBuffersInUse will be decremented down to zero in the while loop
99    int preallocBuffersInUse = fPreallocBuffersInUse;
100    while (!fBlocks.empty()) {
101        this->destroyBlock();
102    }
103    if (fPreallocBuffers.count()) {
104        // must set this after above loop.
105        fPreallocBufferStartIdx = (fPreallocBufferStartIdx +
106                                   preallocBuffersInUse) %
107                                  fPreallocBuffers.count();
108    }
109    // we may have created a large cpu mirror of a large VB. Reset the size
110    // to match our pre-allocated VBs.
111    fCpuData.reset(fMinBlockSize);
112    SkASSERT(0 == fPreallocBuffersInUse);
113    VALIDATE();
114}
115
116void GrBufferAllocPool::unmap() {
117    VALIDATE();
118
119    if (fBufferPtr) {
120        BufferBlock& block = fBlocks.back();
121        if (block.fBuffer->isMapped()) {
122            UNMAP_BUFFER(block);
123        } else {
124            size_t flushSize = block.fBuffer->gpuMemorySize() - block.fBytesFree;
125            this->flushCpuData(fBlocks.back(), flushSize);
126        }
127        fBufferPtr = NULL;
128    }
129    VALIDATE();
130}
131
132#ifdef SK_DEBUG
133void GrBufferAllocPool::validate(bool unusedBlockAllowed) const {
134    if (fBufferPtr) {
135        SkASSERT(!fBlocks.empty());
136        if (fBlocks.back().fBuffer->isMapped()) {
137            GrGeometryBuffer* buf = fBlocks.back().fBuffer;
138            SkASSERT(buf->mapPtr() == fBufferPtr);
139        } else {
140            SkASSERT(fCpuData.get() == fBufferPtr);
141        }
142    } else {
143        SkASSERT(fBlocks.empty() || !fBlocks.back().fBuffer->isMapped());
144    }
145    size_t bytesInUse = 0;
146    for (int i = 0; i < fBlocks.count() - 1; ++i) {
147        SkASSERT(!fBlocks[i].fBuffer->isMapped());
148    }
149    for (int i = 0; i < fBlocks.count(); ++i) {
150        size_t bytes = fBlocks[i].fBuffer->gpuMemorySize() - fBlocks[i].fBytesFree;
151        bytesInUse += bytes;
152        SkASSERT(bytes || unusedBlockAllowed);
153    }
154
155    SkASSERT(bytesInUse == fBytesInUse);
156    if (unusedBlockAllowed) {
157        SkASSERT((fBytesInUse && !fBlocks.empty()) ||
158                 (!fBytesInUse && (fBlocks.count() < 2)));
159    } else {
160        SkASSERT((0 == fBytesInUse) == fBlocks.empty());
161    }
162}
163#endif
164
165void* GrBufferAllocPool::makeSpace(size_t size,
166                                   size_t alignment,
167                                   const GrGeometryBuffer** buffer,
168                                   size_t* offset) {
169    VALIDATE();
170
171    SkASSERT(buffer);
172    SkASSERT(offset);
173
174    if (fBufferPtr) {
175        BufferBlock& back = fBlocks.back();
176        size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
177        size_t pad = GrSizeAlignUpPad(usedBytes,
178                                      alignment);
179        if ((size + pad) <= back.fBytesFree) {
180            usedBytes += pad;
181            *offset = usedBytes;
182            *buffer = back.fBuffer;
183            back.fBytesFree -= size + pad;
184            fBytesInUse += size + pad;
185            VALIDATE();
186            return (void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes);
187        }
188    }
189
190    // We could honor the space request using by a partial update of the current
191    // VB (if there is room). But we don't currently use draw calls to GL that
192    // allow the driver to know that previously issued draws won't read from
193    // the part of the buffer we update. Also, the GL buffer implementation
194    // may be cheating on the actual buffer size by shrinking the buffer on
195    // updateData() if the amount of data passed is less than the full buffer
196    // size.
197
198    if (!createBlock(size)) {
199        return NULL;
200    }
201    SkASSERT(fBufferPtr);
202
203    *offset = 0;
204    BufferBlock& back = fBlocks.back();
205    *buffer = back.fBuffer;
206    back.fBytesFree -= size;
207    fBytesInUse += size;
208    VALIDATE();
209    return fBufferPtr;
210}
211
212int GrBufferAllocPool::currentBufferItems(size_t itemSize) const {
213    VALIDATE();
214    if (fBufferPtr) {
215        const BufferBlock& back = fBlocks.back();
216        size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
217        size_t pad = GrSizeAlignUpPad(usedBytes, itemSize);
218        return static_cast<int>((back.fBytesFree - pad) / itemSize);
219    } else if (fPreallocBuffersInUse < fPreallocBuffers.count()) {
220        return static_cast<int>(fMinBlockSize / itemSize);
221    }
222    return 0;
223}
224
225int GrBufferAllocPool::preallocatedBuffersRemaining() const {
226    return fPreallocBuffers.count() - fPreallocBuffersInUse;
227}
228
229int GrBufferAllocPool::preallocatedBufferCount() const {
230    return fPreallocBuffers.count();
231}
232
233void GrBufferAllocPool::putBack(size_t bytes) {
234    VALIDATE();
235
236    // if the putBack unwinds all the preallocated buffers then we will
237    // advance the starting index. As blocks are destroyed fPreallocBuffersInUse
238    // will be decremented. I will reach zero if all blocks using preallocated
239    // buffers are released.
240    int preallocBuffersInUse = fPreallocBuffersInUse;
241
242    while (bytes) {
243        // caller shouldnt try to put back more than they've taken
244        SkASSERT(!fBlocks.empty());
245        BufferBlock& block = fBlocks.back();
246        size_t bytesUsed = block.fBuffer->gpuMemorySize() - block.fBytesFree;
247        if (bytes >= bytesUsed) {
248            bytes -= bytesUsed;
249            fBytesInUse -= bytesUsed;
250            // if we locked a vb to satisfy the make space and we're releasing
251            // beyond it, then unmap it.
252            if (block.fBuffer->isMapped()) {
253                UNMAP_BUFFER(block);
254            }
255            this->destroyBlock();
256        } else {
257            block.fBytesFree += bytes;
258            fBytesInUse -= bytes;
259            bytes = 0;
260            break;
261        }
262    }
263    if (!fPreallocBuffersInUse && fPreallocBuffers.count()) {
264            fPreallocBufferStartIdx = (fPreallocBufferStartIdx +
265                                       preallocBuffersInUse) %
266                                      fPreallocBuffers.count();
267    }
268    VALIDATE();
269}
270
271bool GrBufferAllocPool::createBlock(size_t requestSize) {
272
273    size_t size = SkTMax(requestSize, fMinBlockSize);
274    SkASSERT(size >= GrBufferAllocPool_MIN_BLOCK_SIZE);
275
276    VALIDATE();
277
278    BufferBlock& block = fBlocks.push_back();
279
280    if (size == fMinBlockSize &&
281        fPreallocBuffersInUse < fPreallocBuffers.count()) {
282
283        uint32_t nextBuffer = (fPreallocBuffersInUse +
284                               fPreallocBufferStartIdx) %
285                              fPreallocBuffers.count();
286        block.fBuffer = fPreallocBuffers[nextBuffer];
287        block.fBuffer->ref();
288        ++fPreallocBuffersInUse;
289    } else {
290        block.fBuffer = this->createBuffer(size);
291        if (NULL == block.fBuffer) {
292            fBlocks.pop_back();
293            return false;
294        }
295    }
296
297    block.fBytesFree = size;
298    if (fBufferPtr) {
299        SkASSERT(fBlocks.count() > 1);
300        BufferBlock& prev = fBlocks.fromBack(1);
301        if (prev.fBuffer->isMapped()) {
302            UNMAP_BUFFER(prev);
303        } else {
304            this->flushCpuData(prev, prev.fBuffer->gpuMemorySize() - prev.fBytesFree);
305        }
306        fBufferPtr = NULL;
307    }
308
309    SkASSERT(NULL == fBufferPtr);
310
311    // If the buffer is CPU-backed we map it because it is free to do so and saves a copy.
312    // Otherwise when buffer mapping is supported:
313    //      a) If the frequently reset hint is set we only map when the requested size meets a
314    //      threshold (since we don't expect it is likely that we will see more vertex data)
315    //      b) If the hint is not set we map if the buffer size is greater than the threshold.
316    bool attemptMap = block.fBuffer->isCPUBacked();
317    if (!attemptMap && GrDrawTargetCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags()) {
318        if (fFrequentResetHint) {
319            attemptMap = requestSize > GR_GEOM_BUFFER_MAP_THRESHOLD;
320        } else {
321            attemptMap = size > GR_GEOM_BUFFER_MAP_THRESHOLD;
322        }
323    }
324
325    if (attemptMap) {
326        fBufferPtr = block.fBuffer->map();
327    }
328
329    if (NULL == fBufferPtr) {
330        fBufferPtr = fCpuData.reset(size);
331    }
332
333    VALIDATE(true);
334
335    return true;
336}
337
338void GrBufferAllocPool::destroyBlock() {
339    SkASSERT(!fBlocks.empty());
340
341    BufferBlock& block = fBlocks.back();
342    if (fPreallocBuffersInUse > 0) {
343        uint32_t prevPreallocBuffer = (fPreallocBuffersInUse +
344                                       fPreallocBufferStartIdx +
345                                       (fPreallocBuffers.count() - 1)) %
346                                      fPreallocBuffers.count();
347        if (block.fBuffer == fPreallocBuffers[prevPreallocBuffer]) {
348            --fPreallocBuffersInUse;
349        }
350    }
351    SkASSERT(!block.fBuffer->isMapped());
352    block.fBuffer->unref();
353    fBlocks.pop_back();
354    fBufferPtr = NULL;
355}
356
357void GrBufferAllocPool::flushCpuData(const BufferBlock& block, size_t flushSize) {
358    GrGeometryBuffer* buffer = block.fBuffer;
359    SkASSERT(buffer);
360    SkASSERT(!buffer->isMapped());
361    SkASSERT(fCpuData.get() == fBufferPtr);
362    SkASSERT(flushSize <= buffer->gpuMemorySize());
363    VALIDATE(true);
364
365    if (GrDrawTargetCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() &&
366        flushSize > GR_GEOM_BUFFER_MAP_THRESHOLD) {
367        void* data = buffer->map();
368        if (data) {
369            memcpy(data, fBufferPtr, flushSize);
370            UNMAP_BUFFER(block);
371            return;
372        }
373    }
374    buffer->updateData(fBufferPtr, flushSize);
375    VALIDATE(true);
376}
377
378GrGeometryBuffer* GrBufferAllocPool::createBuffer(size_t size) {
379    if (kIndex_BufferType == fBufferType) {
380        return fGpu->createIndexBuffer(size, true);
381    } else {
382        SkASSERT(kVertex_BufferType == fBufferType);
383        return fGpu->createVertexBuffer(size, true);
384    }
385}
386
387////////////////////////////////////////////////////////////////////////////////
388
389GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu,
390                                                 bool frequentResetHint,
391                                                 size_t bufferSize,
392                                                 int preallocBufferCnt)
393: GrBufferAllocPool(gpu,
394                    kVertex_BufferType,
395                    frequentResetHint,
396                    bufferSize,
397                    preallocBufferCnt) {
398}
399
400void* GrVertexBufferAllocPool::makeSpace(size_t vertexSize,
401                                         int vertexCount,
402                                         const GrVertexBuffer** buffer,
403                                         int* startVertex) {
404
405    SkASSERT(vertexCount >= 0);
406    SkASSERT(buffer);
407    SkASSERT(startVertex);
408
409    size_t offset = 0; // assign to suppress warning
410    const GrGeometryBuffer* geomBuffer = NULL; // assign to suppress warning
411    void* ptr = INHERITED::makeSpace(vertexSize * vertexCount,
412                                     vertexSize,
413                                     &geomBuffer,
414                                     &offset);
415
416    *buffer = (const GrVertexBuffer*) geomBuffer;
417    SkASSERT(0 == offset % vertexSize);
418    *startVertex = static_cast<int>(offset / vertexSize);
419    return ptr;
420}
421
422bool GrVertexBufferAllocPool::appendVertices(size_t vertexSize,
423                                             int vertexCount,
424                                             const void* vertices,
425                                             const GrVertexBuffer** buffer,
426                                             int* startVertex) {
427    void* space = makeSpace(vertexSize, vertexCount, buffer, startVertex);
428    if (space) {
429        memcpy(space,
430               vertices,
431               vertexSize * vertexCount);
432        return true;
433    } else {
434        return false;
435    }
436}
437
438int GrVertexBufferAllocPool::preallocatedBufferVertices(size_t vertexSize) const {
439    return static_cast<int>(INHERITED::preallocatedBufferSize() / vertexSize);
440}
441
442int GrVertexBufferAllocPool::currentBufferVertices(size_t vertexSize) const {
443    return currentBufferItems(vertexSize);
444}
445
446////////////////////////////////////////////////////////////////////////////////
447
448GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu,
449                                               bool frequentResetHint,
450                                               size_t bufferSize,
451                                               int preallocBufferCnt)
452: GrBufferAllocPool(gpu,
453                    kIndex_BufferType,
454                    frequentResetHint,
455                    bufferSize,
456                    preallocBufferCnt) {
457}
458
459void* GrIndexBufferAllocPool::makeSpace(int indexCount,
460                                        const GrIndexBuffer** buffer,
461                                        int* startIndex) {
462
463    SkASSERT(indexCount >= 0);
464    SkASSERT(buffer);
465    SkASSERT(startIndex);
466
467    size_t offset = 0; // assign to suppress warning
468    const GrGeometryBuffer* geomBuffer = NULL; // assign to suppress warning
469    void* ptr = INHERITED::makeSpace(indexCount * sizeof(uint16_t),
470                                     sizeof(uint16_t),
471                                     &geomBuffer,
472                                     &offset);
473
474    *buffer = (const GrIndexBuffer*) geomBuffer;
475    SkASSERT(0 == offset % sizeof(uint16_t));
476    *startIndex = static_cast<int>(offset / sizeof(uint16_t));
477    return ptr;
478}
479
480bool GrIndexBufferAllocPool::appendIndices(int indexCount,
481                                           const void* indices,
482                                           const GrIndexBuffer** buffer,
483                                           int* startIndex) {
484    void* space = makeSpace(indexCount, buffer, startIndex);
485    if (space) {
486        memcpy(space, indices, sizeof(uint16_t) * indexCount);
487        return true;
488    } else {
489        return false;
490    }
491}
492
493int GrIndexBufferAllocPool::preallocatedBufferIndices() const {
494    return static_cast<int>(INHERITED::preallocatedBufferSize() / sizeof(uint16_t));
495}
496
497int GrIndexBufferAllocPool::currentBufferIndices() const {
498    return currentBufferItems(sizeof(uint16_t));
499}
500