#include "gpu/command_buffer/client/fenced_allocator.h"
#include <stdint.h>
#include <algorithm>
#include "base/numerics/clamped_math.h"
#include "gpu/command_buffer/client/cmd_buffer_helper.h"
namespace gpu {
namespace {
uint32_t RoundDown(uint32_t size) {
return size & ~(FencedAllocator::kAllocAlignment - 1);
}
base::CheckedNumeric<uint32_t> RoundUp(uint32_t size) {
return (base::CheckedNumeric<uint32_t>(size) +
(FencedAllocator::kAllocAlignment - 1)) &
~(FencedAllocator::kAllocAlignment - 1);
}
}
FencedAllocator::FencedAllocator(uint32_t size, CommandBufferHelper* helper)
: helper_(helper), bytes_in_use_(0) {
Block block = { FREE, 0, RoundDown(size), kUnusedToken };
blocks_.push_back(block);
}
FencedAllocator::~FencedAllocator() {
DCHECK_EQ(bytes_in_use_, 0u);
}
FencedAllocator::Offset FencedAllocator::Alloc(uint32_t size) {
if (size == 0) {
return kInvalidOffset;
}
uint32_t aligned_size = 0;
if (!RoundUp(size).AssignIfValid(&aligned_size)) {
return kInvalidOffset;
}
for (uint32_t i = 0; i < blocks_.size(); ++i) {
Block &block = blocks_[i];
if (block.state == FREE && block.size >= aligned_size) {
return AllocInBlock(i, aligned_size);
}
}
for (uint32_t i = 0; i < blocks_.size(); ++i) {
if (blocks_[i].state != FREE_PENDING_TOKEN)
continue;
i = WaitForTokenAndFreeBlock(i);
if (blocks_[i].size >= aligned_size)
return AllocInBlock(i, aligned_size);
}
return kInvalidOffset;
}
void FencedAllocator::Free(FencedAllocator::Offset offset) {
BlockIndex index = GetBlockByOffset(offset);
Block &block = blocks_[index];
DCHECK_NE(block.state, FREE);
DCHECK_EQ(block.offset, offset);
if (block.state == IN_USE)
bytes_in_use_ -= block.size;
block.state = FREE;
CollapseFreeBlock(index);
}
void FencedAllocator::FreePendingToken(FencedAllocator::Offset offset,
int32_t token) {
BlockIndex index = GetBlockByOffset(offset);
Block &block = blocks_[index];
DCHECK_EQ(block.offset, offset);
if (block.state == IN_USE)
bytes_in_use_ -= block.size;
block.state = FREE_PENDING_TOKEN;
block.token = token;
}
uint32_t FencedAllocator::GetLargestFreeSize() {
FreeUnused();
uint32_t max_size = 0;
for (uint32_t i = 0; i < blocks_.size(); ++i) {
Block &block = blocks_[i];
if (block.state == FREE)
max_size = std::max(max_size, block.size);
}
return max_size;
}
uint32_t FencedAllocator::GetLargestFreeOrPendingSize() {
uint32_t max_size = 0;
uint32_t current_size = 0;
for (uint32_t i = 0; i < blocks_.size(); ++i) {
Block &block = blocks_[i];
if (block.state == IN_USE) {
max_size = std::max(max_size, current_size);
current_size = 0;
} else {
DCHECK(block.state == FREE || block.state == FREE_PENDING_TOKEN);
current_size += block.size;
}
}
return std::max(max_size, current_size);
}
uint32_t FencedAllocator::GetFreeSize() {
FreeUnused();
uint32_t size = 0;
for (uint32_t i = 0; i < blocks_.size(); ++i) {
Block& block = blocks_[i];
if (block.state == FREE)
size += block.size;
}
return size;
}
bool FencedAllocator::CheckConsistency() {
if (blocks_.size() < 1) return false;
for (uint32_t i = 0; i < blocks_.size() - 1; ++i) {
Block ¤t = blocks_[i];
Block &next = blocks_[i + 1];
if (next.offset <= current.offset)
return false;
if (next.offset != current.offset + current.size)
return false;
if (current.state == FREE && next.state == FREE)
return false;
}
return true;
}
bool FencedAllocator::InUseOrFreePending() {
return blocks_.size() != 1 || blocks_[0].state != FREE;
}
FencedAllocator::State FencedAllocator::GetBlockStatusForTest(
Offset offset,
int32_t* token_if_pending) {
BlockIndex index = GetBlockByOffset(offset);
Block& block = blocks_[index];
if ((block.state == FREE_PENDING_TOKEN) && token_if_pending)
*token_if_pending = block.token;
return block.state;
}
FencedAllocator::BlockIndex FencedAllocator::CollapseFreeBlock(
BlockIndex index) {
if (index + 1 < blocks_.size()) {
Block &next = blocks_[index + 1];
if (next.state == FREE) {
blocks_[index].size += next.size;
blocks_.erase(blocks_.begin() + index + 1);
}
}
if (index > 0) {
Block &prev = blocks_[index - 1];
if (prev.state == FREE) {
prev.size += blocks_[index].size;
blocks_.erase(blocks_.begin() + index);
--index;
}
}
return index;
}
FencedAllocator::BlockIndex FencedAllocator::WaitForTokenAndFreeBlock(
BlockIndex index) {
Block &block = blocks_[index];
DCHECK_EQ(block.state, FREE_PENDING_TOKEN);
helper_->WaitForToken(block.token);
block.state = FREE;
return CollapseFreeBlock(index);
}
void FencedAllocator::FreeUnused() {
helper_->RefreshCachedToken();
for (uint32_t i = 0; i < blocks_.size();) {
Block& block = blocks_[i];
if (block.state == FREE_PENDING_TOKEN &&
helper_->HasCachedTokenPassed(block.token)) {
block.state = FREE;
i = CollapseFreeBlock(i);
} else {
++i;
}
}
}
FencedAllocator::Offset FencedAllocator::AllocInBlock(BlockIndex index,
uint32_t size) {
Block &block = blocks_[index];
DCHECK_GE(block.size, size);
DCHECK_EQ(block.state, FREE);
Offset offset = block.offset;
bytes_in_use_ += size;
if (block.size == size) {
block.state = IN_USE;
return offset;
}
Block newblock = { FREE, offset + size, block.size - size, kUnusedToken};
block.state = IN_USE;
block.size = size;
blocks_.insert(blocks_.begin() + index + 1, newblock);
return offset;
}
FencedAllocator::BlockIndex FencedAllocator::GetBlockByOffset(Offset offset) {
Block templ = { IN_USE, offset, 0, kUnusedToken };
Container::iterator it = std::lower_bound(blocks_.begin(), blocks_.end(),
templ, OffsetCmp());
CHECK(it != blocks_.end());
return it-blocks_.begin();
}
}