// 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/logging.h" #include "gpu/command_buffer/client/cmd_buffer_helper.h" namespace gpu { AlignedRingBuffer::~AlignedRingBuffer() { } 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()) { helper_->Finish(); helper_->command_buffer()->DestroyTransferBuffer(buffer_id_); buffer_id_ = -1; buffer_.ptr = NULL; buffer_.size = 0; result_buffer_ = NULL; result_shm_offset_ = 0; ring_buffer_.reset(); bytes_since_last_flush_ = 0; } } bool TransferBuffer::HaveBuffer() const { 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; gpu::Buffer buffer = helper_->command_buffer()->CreateTransferBuffer(size, &id); if (id != -1) { buffer_ = buffer; ring_buffer_.reset(new AlignedRingBuffer( alignment_, id, result_size_, buffer_.size - result_size_, helper_, static_cast<char*>(buffer_.ptr) + result_size_)); buffer_id_ = id; result_buffer_ = buffer_.ptr; result_shm_offset_ = 0; return; } // we failed so don't try larger than this. max_buffer_size_ = size / 2; } usable_ = false; } // Returns the integer i such as 2^i <= n < 2^(i+1) static int Log2Floor(uint32 n) { if (n == 0) return -1; int log = 0; uint32 value = n; for (int i = 4; i >= 0; --i) { int shift = (1 << i); uint32 x = value >> shift; if (x != 0) { value = x; log += shift; } } DCHECK_EQ(value, 1u); return log; } // Returns the integer i such as 2^(i-1) < n <= 2^i static int Log2Ceiling(uint32 n) { if (n == 0) { return -1; } else { // Log2Floor returns -1 for 0, so the following works correctly for n=1. return 1 + Log2Floor(n - 1); } } static unsigned int ComputePOTSize(unsigned int dimension) { return (dimension == 0) ? 0 : 1 << 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