// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // A class to Manage a growing transfer buffer. #include "gpu/command_buffer/client/transfer_buffer.h" #include "base/bits.h" #include "base/debug/trace_event.h" #include "base/logging.h" #include "gpu/command_buffer/client/cmd_buffer_helper.h" namespace gpu { TransferBuffer::TransferBuffer( CommandBufferHelper* helper) : helper_(helper), result_size_(0), default_buffer_size_(0), min_buffer_size_(0), max_buffer_size_(0), alignment_(0), size_to_flush_(0), bytes_since_last_flush_(0), buffer_id_(-1), result_buffer_(NULL), result_shm_offset_(0), usable_(true) { } TransferBuffer::~TransferBuffer() { Free(); } bool TransferBuffer::Initialize( unsigned int default_buffer_size, unsigned int result_size, unsigned int min_buffer_size, unsigned int max_buffer_size, unsigned int alignment, unsigned int size_to_flush) { result_size_ = result_size; default_buffer_size_ = default_buffer_size; min_buffer_size_ = min_buffer_size; max_buffer_size_ = max_buffer_size; alignment_ = alignment; size_to_flush_ = size_to_flush; ReallocateRingBuffer(default_buffer_size_ - result_size); return HaveBuffer(); } void TransferBuffer::Free() { if (HaveBuffer()) { TRACE_EVENT0("gpu", "TransferBuffer::Free"); helper_->Finish(); helper_->command_buffer()->DestroyTransferBuffer(buffer_id_); buffer_id_ = -1; buffer_ = NULL; result_buffer_ = NULL; result_shm_offset_ = 0; ring_buffer_.reset(); bytes_since_last_flush_ = 0; } } bool TransferBuffer::HaveBuffer() const { DCHECK(buffer_id_ == -1 || buffer_); return buffer_id_ != -1; } RingBuffer::Offset TransferBuffer::GetOffset(void* pointer) const { return ring_buffer_->GetOffset(pointer); } void TransferBuffer::FreePendingToken(void* p, unsigned int token) { ring_buffer_->FreePendingToken(p, token); if (bytes_since_last_flush_ >= size_to_flush_ && size_to_flush_ > 0) { helper_->Flush(); bytes_since_last_flush_ = 0; } } void TransferBuffer::AllocateRingBuffer(unsigned int size) { for (;size >= min_buffer_size_; size /= 2) { int32 id = -1; scoped_refptr buffer = helper_->command_buffer()->CreateTransferBuffer(size, &id); if (id != -1) { DCHECK(buffer); buffer_ = buffer; ring_buffer_.reset(new RingBuffer( alignment_, result_size_, buffer_->size() - result_size_, helper_, static_cast(buffer_->memory()) + result_size_)); buffer_id_ = id; result_buffer_ = buffer_->memory(); result_shm_offset_ = 0; return; } // we failed so don't try larger than this. max_buffer_size_ = size / 2; } usable_ = false; } static unsigned int ComputePOTSize(unsigned int dimension) { return (dimension == 0) ? 0 : 1 << base::bits::Log2Ceiling(dimension); } void TransferBuffer::ReallocateRingBuffer(unsigned int size) { // What size buffer would we ask for if we needed a new one? unsigned int needed_buffer_size = ComputePOTSize(size + result_size_); needed_buffer_size = std::max(needed_buffer_size, min_buffer_size_); needed_buffer_size = std::max(needed_buffer_size, default_buffer_size_); needed_buffer_size = std::min(needed_buffer_size, max_buffer_size_); if (usable_ && (!HaveBuffer() || needed_buffer_size > buffer_->size())) { if (HaveBuffer()) { Free(); } AllocateRingBuffer(needed_buffer_size); } } void* TransferBuffer::AllocUpTo( unsigned int size, unsigned int* size_allocated) { DCHECK(size_allocated); ReallocateRingBuffer(size); if (!HaveBuffer()) { return NULL; } unsigned int max_size = ring_buffer_->GetLargestFreeOrPendingSize(); *size_allocated = std::min(max_size, size); bytes_since_last_flush_ += *size_allocated; return ring_buffer_->Alloc(*size_allocated); } void* TransferBuffer::Alloc(unsigned int size) { ReallocateRingBuffer(size); if (!HaveBuffer()) { return NULL; } unsigned int max_size = ring_buffer_->GetLargestFreeOrPendingSize(); if (size > max_size) { return NULL; } bytes_since_last_flush_ += size; return ring_buffer_->Alloc(size); } void* TransferBuffer::GetResultBuffer() { ReallocateRingBuffer(result_size_); return result_buffer_; } int TransferBuffer::GetResultOffset() { ReallocateRingBuffer(result_size_); return result_shm_offset_; } int TransferBuffer::GetShmId() { ReallocateRingBuffer(result_size_); return buffer_id_; } unsigned int TransferBuffer::GetCurrentMaxAllocationWithoutRealloc() const { return HaveBuffer() ? ring_buffer_->GetLargestFreeOrPendingSize() : 0; } unsigned int TransferBuffer::GetMaxAllocation() const { return HaveBuffer() ? max_buffer_size_ - result_size_ : 0; } void ScopedTransferBufferPtr::Release() { if (buffer_) { transfer_buffer_->FreePendingToken(buffer_, helper_->InsertToken()); buffer_ = NULL; size_ = 0; } } void ScopedTransferBufferPtr::Reset(unsigned int new_size) { Release(); // NOTE: we allocate buffers of size 0 so that HaveBuffer will be true, so // that address will return a pointer just like malloc, and so that GetShmId // will be valid. That has the side effect that we'll insert a token on free. // We could add code skip the token for a zero size buffer but it doesn't seem // worth the complication. buffer_ = transfer_buffer_->AllocUpTo(new_size, &size_); } } // namespace gpu