/*
* Copyright 2010 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrBufferAllocPool.h"
#include "GrBuffer.h"
#include "GrCaps.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrResourceProvider.h"
#include "GrTypes.h"
#include "SkSafeMath.h"
#include "SkTraceEvent.h"
#ifdef SK_DEBUG
#define VALIDATE validate
#else
static void VALIDATE(bool = false) {}
#endif
static const size_t MIN_VERTEX_BUFFER_SIZE = 1 << 15;
static const size_t MIN_INDEX_BUFFER_SIZE = 1 << 12;
// page size
#define GrBufferAllocPool_MIN_BLOCK_SIZE ((size_t)1 << 15)
#define UNMAP_BUFFER(block) \
do { \
TRACE_EVENT_INSTANT1("skia.gpu", \
"GrBufferAllocPool Unmapping Buffer", \
TRACE_EVENT_SCOPE_THREAD, \
"percent_unwritten", \
(float)((block).fBytesFree) / (block).fBuffer->gpuMemorySize()); \
(block).fBuffer->unmap(); \
} while (false)
GrBufferAllocPool::GrBufferAllocPool(GrGpu* gpu, GrBufferType bufferType, size_t blockSize)
: fBlocks(8) {
fGpu = SkRef(gpu);
fCpuData = nullptr;
fBufferType = bufferType;
fBufferPtr = nullptr;
fMinBlockSize = SkTMax(GrBufferAllocPool_MIN_BLOCK_SIZE, blockSize);
fBytesInUse = 0;
fBufferMapThreshold = gpu->caps()->bufferMapThreshold();
}
void GrBufferAllocPool::deleteBlocks() {
if (fBlocks.count()) {
GrBuffer* buffer = fBlocks.back().fBuffer;
if (buffer->isMapped()) {
UNMAP_BUFFER(fBlocks.back());
}
}
while (!fBlocks.empty()) {
this->destroyBlock();
}
SkASSERT(!fBufferPtr);
}
GrBufferAllocPool::~GrBufferAllocPool() {
VALIDATE();
this->deleteBlocks();
sk_free(fCpuData);
fGpu->unref();
}
void GrBufferAllocPool::reset() {
VALIDATE();
fBytesInUse = 0;
this->deleteBlocks();
this->resetCpuData(0); // delete all the cpu-side memory
VALIDATE();
}
void GrBufferAllocPool::unmap() {
VALIDATE();
if (fBufferPtr) {
BufferBlock& block = fBlocks.back();
if (block.fBuffer->isMapped()) {
UNMAP_BUFFER(block);
} else {
size_t flushSize = block.fBuffer->gpuMemorySize() - block.fBytesFree;
this->flushCpuData(fBlocks.back(), flushSize);
}
fBufferPtr = nullptr;
}
VALIDATE();
}
#ifdef SK_DEBUG
void GrBufferAllocPool::validate(bool unusedBlockAllowed) const {
bool wasDestroyed = false;
if (fBufferPtr) {
SkASSERT(!fBlocks.empty());
if (fBlocks.back().fBuffer->isMapped()) {
GrBuffer* buf = fBlocks.back().fBuffer;
SkASSERT(buf->mapPtr() == fBufferPtr);
} else {
SkASSERT(fCpuData == fBufferPtr);
}
} else {
SkASSERT(fBlocks.empty() || !fBlocks.back().fBuffer->isMapped());
}
size_t bytesInUse = 0;
for (int i = 0; i < fBlocks.count() - 1; ++i) {
SkASSERT(!fBlocks[i].fBuffer->isMapped());
}
for (int i = 0; !wasDestroyed && i < fBlocks.count(); ++i) {
if (fBlocks[i].fBuffer->wasDestroyed()) {
wasDestroyed = true;
} else {
size_t bytes = fBlocks[i].fBuffer->gpuMemorySize() - fBlocks[i].fBytesFree;
bytesInUse += bytes;
SkASSERT(bytes || unusedBlockAllowed);
}
}
if (!wasDestroyed) {
SkASSERT(bytesInUse == fBytesInUse);
if (unusedBlockAllowed) {
SkASSERT((fBytesInUse && !fBlocks.empty()) ||
(!fBytesInUse && (fBlocks.count() < 2)));
} else {
SkASSERT((0 == fBytesInUse) == fBlocks.empty());
}
}
}
#endif
void* GrBufferAllocPool::makeSpace(size_t size,
size_t alignment,
const GrBuffer** buffer,
size_t* offset) {
VALIDATE();
SkASSERT(buffer);
SkASSERT(offset);
if (fBufferPtr) {
BufferBlock& back = fBlocks.back();
size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
size_t pad = GrSizeAlignUpPad(usedBytes, alignment);
if ((size + pad) <= back.fBytesFree) {
memset((void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes), 0, pad);
usedBytes += pad;
*offset = usedBytes;
*buffer = back.fBuffer;
back.fBytesFree -= size + pad;
fBytesInUse += size + pad;
VALIDATE();
return (void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes);
}
}
// We could honor the space request using by a partial update of the current
// VB (if there is room). But we don't currently use draw calls to GL that
// allow the driver to know that previously issued draws won't read from
// the part of the buffer we update. Also, the GL buffer implementation
// may be cheating on the actual buffer size by shrinking the buffer on
// updateData() if the amount of data passed is less than the full buffer
// size.
if (!this->createBlock(size)) {
return nullptr;
}
SkASSERT(fBufferPtr);
*offset = 0;
BufferBlock& back = fBlocks.back();
*buffer = back.fBuffer;
back.fBytesFree -= size;
fBytesInUse += size;
VALIDATE();
return fBufferPtr;
}
void* GrBufferAllocPool::makeSpaceAtLeast(size_t minSize,
size_t fallbackSize,
size_t alignment,
const GrBuffer** buffer,
size_t* offset,
size_t* actualSize) {
VALIDATE();
SkASSERT(buffer);
SkASSERT(offset);
SkASSERT(actualSize);
if (fBufferPtr) {
BufferBlock& back = fBlocks.back();
size_t usedBytes = back.fBuffer->gpuMemorySize() - back.fBytesFree;
size_t pad = GrSizeAlignUpPad(usedBytes, alignment);
if ((minSize + pad) <= back.fBytesFree) {
// Consume padding first, to make subsequent alignment math easier
memset((void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes), 0, pad);
usedBytes += pad;
back.fBytesFree -= pad;
fBytesInUse += pad;
// Give caller all remaining space in this block (but aligned correctly)
size_t size = GrSizeAlignDown(back.fBytesFree, alignment);
*offset = usedBytes;
*buffer = back.fBuffer;
*actualSize = size;
back.fBytesFree -= size;
fBytesInUse += size;
VALIDATE();
return (void*)(reinterpret_cast<intptr_t>(fBufferPtr) + usedBytes);
}
}
// We could honor the space request using by a partial update of the current
// VB (if there is room). But we don't currently use draw calls to GL that
// allow the driver to know that previously issued draws won't read from
// the part of the buffer we update. Also, the GL buffer implementation
// may be cheating on the actual buffer size by shrinking the buffer on
// updateData() if the amount of data passed is less than the full buffer
// size.
if (!this->createBlock(fallbackSize)) {
return nullptr;
}
SkASSERT(fBufferPtr);
*offset = 0;
BufferBlock& back = fBlocks.back();
*buffer = back.fBuffer;
*actualSize = fallbackSize;
back.fBytesFree -= fallbackSize;
fBytesInUse += fallbackSize;
VALIDATE();
return fBufferPtr;
}
void GrBufferAllocPool::putBack(size_t bytes) {
VALIDATE();
while (bytes) {
// caller shouldn't try to put back more than they've taken
SkASSERT(!fBlocks.empty());
BufferBlock& block = fBlocks.back();
size_t bytesUsed = block.fBuffer->gpuMemorySize() - block.fBytesFree;
if (bytes >= bytesUsed) {
bytes -= bytesUsed;
fBytesInUse -= bytesUsed;
// if we locked a vb to satisfy the make space and we're releasing
// beyond it, then unmap it.
if (block.fBuffer->isMapped()) {
UNMAP_BUFFER(block);
}
this->destroyBlock();
} else {
block.fBytesFree += bytes;
fBytesInUse -= bytes;
bytes = 0;
break;
}
}
VALIDATE();
}
bool GrBufferAllocPool::createBlock(size_t requestSize) {
size_t size = SkTMax(requestSize, fMinBlockSize);
SkASSERT(size >= GrBufferAllocPool_MIN_BLOCK_SIZE);
VALIDATE();
BufferBlock& block = fBlocks.push_back();
block.fBuffer = this->getBuffer(size);
if (!block.fBuffer) {
fBlocks.pop_back();
return false;
}
block.fBytesFree = block.fBuffer->gpuMemorySize();
if (fBufferPtr) {
SkASSERT(fBlocks.count() > 1);
BufferBlock& prev = fBlocks.fromBack(1);
if (prev.fBuffer->isMapped()) {
UNMAP_BUFFER(prev);
} else {
this->flushCpuData(prev, prev.fBuffer->gpuMemorySize() - prev.fBytesFree);
}
fBufferPtr = nullptr;
}
SkASSERT(!fBufferPtr);
// If the buffer is CPU-backed we map it because it is free to do so and saves a copy.
// Otherwise when buffer mapping is supported we map if the buffer size is greater than the
// threshold.
bool attemptMap = block.fBuffer->isCPUBacked();
if (!attemptMap && GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags()) {
attemptMap = size > fBufferMapThreshold;
}
if (attemptMap) {
fBufferPtr = block.fBuffer->map();
}
if (!fBufferPtr) {
fBufferPtr = this->resetCpuData(block.fBytesFree);
}
VALIDATE(true);
return true;
}
void GrBufferAllocPool::destroyBlock() {
SkASSERT(!fBlocks.empty());
BufferBlock& block = fBlocks.back();
SkASSERT(!block.fBuffer->isMapped());
block.fBuffer->unref();
fBlocks.pop_back();
fBufferPtr = nullptr;
}
void* GrBufferAllocPool::resetCpuData(size_t newSize) {
sk_free(fCpuData);
if (newSize) {
if (fGpu->caps()->mustClearUploadedBufferData()) {
fCpuData = sk_calloc_throw(newSize);
} else {
fCpuData = sk_malloc_throw(newSize);
}
} else {
fCpuData = nullptr;
}
return fCpuData;
}
void GrBufferAllocPool::flushCpuData(const BufferBlock& block, size_t flushSize) {
GrBuffer* buffer = block.fBuffer;
SkASSERT(buffer);
SkASSERT(!buffer->isMapped());
SkASSERT(fCpuData == fBufferPtr);
SkASSERT(flushSize <= buffer->gpuMemorySize());
VALIDATE(true);
if (GrCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() &&
flushSize > fBufferMapThreshold) {
void* data = buffer->map();
if (data) {
memcpy(data, fBufferPtr, flushSize);
UNMAP_BUFFER(block);
return;
}
}
buffer->updateData(fBufferPtr, flushSize);
VALIDATE(true);
}
GrBuffer* GrBufferAllocPool::getBuffer(size_t size) {
auto resourceProvider = fGpu->getContext()->contextPriv().resourceProvider();
// Shouldn't have to use this flag (https://bug.skia.org/4156)
static const uint32_t kFlags = GrResourceProvider::kNoPendingIO_Flag;
return resourceProvider->createBuffer(size, fBufferType, kDynamic_GrAccessPattern, kFlags);
}
////////////////////////////////////////////////////////////////////////////////
GrVertexBufferAllocPool::GrVertexBufferAllocPool(GrGpu* gpu)
: GrBufferAllocPool(gpu, kVertex_GrBufferType, MIN_VERTEX_BUFFER_SIZE) {
}
void* GrVertexBufferAllocPool::makeSpace(size_t vertexSize,
int vertexCount,
const GrBuffer** buffer,
int* startVertex) {
SkASSERT(vertexCount >= 0);
SkASSERT(buffer);
SkASSERT(startVertex);
size_t offset SK_INIT_TO_AVOID_WARNING;
void* ptr = INHERITED::makeSpace(SkSafeMath::Mul(vertexSize, vertexCount),
vertexSize,
buffer,
&offset);
SkASSERT(0 == offset % vertexSize);
*startVertex = static_cast<int>(offset / vertexSize);
return ptr;
}
void* GrVertexBufferAllocPool::makeSpaceAtLeast(size_t vertexSize, int minVertexCount,
int fallbackVertexCount, const GrBuffer** buffer,
int* startVertex, int* actualVertexCount) {
SkASSERT(minVertexCount >= 0);
SkASSERT(fallbackVertexCount >= minVertexCount);
SkASSERT(buffer);
SkASSERT(startVertex);
SkASSERT(actualVertexCount);
size_t offset SK_INIT_TO_AVOID_WARNING;
size_t actualSize SK_INIT_TO_AVOID_WARNING;
void* ptr = INHERITED::makeSpaceAtLeast(SkSafeMath::Mul(vertexSize, minVertexCount),
SkSafeMath::Mul(vertexSize, fallbackVertexCount),
vertexSize,
buffer,
&offset,
&actualSize);
SkASSERT(0 == offset % vertexSize);
*startVertex = static_cast<int>(offset / vertexSize);
SkASSERT(0 == actualSize % vertexSize);
SkASSERT(actualSize >= vertexSize * minVertexCount);
*actualVertexCount = static_cast<int>(actualSize / vertexSize);
return ptr;
}
////////////////////////////////////////////////////////////////////////////////
GrIndexBufferAllocPool::GrIndexBufferAllocPool(GrGpu* gpu)
: GrBufferAllocPool(gpu, kIndex_GrBufferType, MIN_INDEX_BUFFER_SIZE) {
}
void* GrIndexBufferAllocPool::makeSpace(int indexCount,
const GrBuffer** buffer,
int* startIndex) {
SkASSERT(indexCount >= 0);
SkASSERT(buffer);
SkASSERT(startIndex);
size_t offset SK_INIT_TO_AVOID_WARNING;
void* ptr = INHERITED::makeSpace(SkSafeMath::Mul(indexCount, sizeof(uint16_t)),
sizeof(uint16_t),
buffer,
&offset);
SkASSERT(0 == offset % sizeof(uint16_t));
*startIndex = static_cast<int>(offset / sizeof(uint16_t));
return ptr;
}
void* GrIndexBufferAllocPool::makeSpaceAtLeast(int minIndexCount, int fallbackIndexCount,
const GrBuffer** buffer, int* startIndex,
int* actualIndexCount) {
SkASSERT(minIndexCount >= 0);
SkASSERT(fallbackIndexCount >= minIndexCount);
SkASSERT(buffer);
SkASSERT(startIndex);
SkASSERT(actualIndexCount);
size_t offset SK_INIT_TO_AVOID_WARNING;
size_t actualSize SK_INIT_TO_AVOID_WARNING;
void* ptr = INHERITED::makeSpaceAtLeast(SkSafeMath::Mul(minIndexCount, sizeof(uint16_t)),
SkSafeMath::Mul(fallbackIndexCount, sizeof(uint16_t)),
sizeof(uint16_t),
buffer,
&offset,
&actualSize);
SkASSERT(0 == offset % sizeof(uint16_t));
*startIndex = static_cast<int>(offset / sizeof(uint16_t));
SkASSERT(0 == actualSize % sizeof(uint16_t));
SkASSERT(actualSize >= minIndexCount * sizeof(uint16_t));
*actualIndexCount = static_cast<int>(actualSize / sizeof(uint16_t));
return ptr;
}