| // Copyright 2011 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // This file contains the implementation of the RingBuffer class. | |
| #include "gpu/command_buffer/client/ring_buffer.h" | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <ostream> | |
| #include "base/check_op.h" | |
| #include "base/notreached.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "gpu/command_buffer/client/cmd_buffer_helper.h" | |
| namespace gpu { | |
| RingBuffer::RingBuffer(scoped_refptr<gpu::Buffer> buffer, | |
| uint32_t alignment, | |
| Offset base_offset, | |
| CommandBufferHelper* helper) | |
| : helper_(helper), | |
| buffer_(buffer), | |
| base_offset_(base_offset), | |
| size_(buffer->size() - base_offset), | |
| alignment_(alignment), | |
| base_(buffer->as_byte_span()) {} | |
| RingBuffer::~RingBuffer() { | |
| DCHECK_EQ(num_used_blocks_, 0u); | |
| for (const auto& block : blocks_) | |
| DCHECK(block.state != IN_USE); | |
| } | |
| void RingBuffer::FreeOldestBlock() { | |
| DCHECK(!blocks_.empty()) << "no free blocks"; | |
| Block& block = blocks_.front(); | |
| DCHECK(block.state != IN_USE) | |
| << "attempt to allocate more than maximum memory"; | |
| if (block.state == FREE_PENDING_TOKEN) { | |
| helper_->WaitForToken(block.token); | |
| } | |
| in_use_offset_ += block.size; | |
| if (in_use_offset_ == size_) { | |
| in_use_offset_ = 0; | |
| } | |
| // If they match then the entire buffer is free. | |
| if (in_use_offset_ == free_offset_) { | |
| in_use_offset_ = 0; | |
| free_offset_ = 0; | |
| } | |
| blocks_.pop_front(); | |
| } | |
| base::span<uint8_t> RingBuffer::Alloc(const uint32_t size) { | |
| DCHECK_LE(size, size_) << "attempt to allocate more than maximum memory"; | |
| // Similarly to malloc, an allocation of 0 allocates at least 1 byte, to | |
| // return different pointers every time. | |
| // Allocate rounded to alignment size so that the offsets are always | |
| // memory-aligned. | |
| uint32_t aligned_size = RoundToAlignment(std::max(size, 1u)); | |
| DCHECK_LE(aligned_size, size_) | |
| << "attempt to allocate more than maximum memory after rounding"; | |
| // Wait until there is enough room. | |
| while (aligned_size > GetLargestFreeSizeNoWaitingInternal()) { | |
| FreeOldestBlock(); | |
| } | |
| if (aligned_size + free_offset_ > size_) { | |
| // Add padding to fill space before wrapping around | |
| blocks_.push_back(Block(free_offset_, size_ - free_offset_, PADDING)); | |
| free_offset_ = 0; | |
| } | |
| Offset offset = free_offset_; | |
| blocks_.push_back(Block(offset, aligned_size, IN_USE)); | |
| num_used_blocks_++; | |
| free_offset_ += aligned_size; | |
| if (free_offset_ == size_) { | |
| free_offset_ = 0; | |
| } | |
| return base_.subspan(offset + base_offset_, size); | |
| } | |
| void RingBuffer::FreePendingToken(void* pointer, uint32_t token) { | |
| Offset offset = GetOffset(pointer); | |
| offset -= base_offset_; | |
| DCHECK(!blocks_.empty()) << "no allocations to free"; | |
| for (Container::reverse_iterator it = blocks_.rbegin(); | |
| it != blocks_.rend(); | |
| ++it) { | |
| Block& block = *it; | |
| if (block.offset == offset) { | |
| DCHECK(block.state == IN_USE) | |
| << "block that corresponds to offset already freed"; | |
| block.token = token; | |
| block.state = FREE_PENDING_TOKEN; | |
| num_used_blocks_--; | |
| return; | |
| } | |
| } | |
| NOTREACHED() << "attempt to free non-existant block"; | |
| } | |
| void RingBuffer::DiscardBlock(void* pointer) { | |
| Offset offset = GetOffset(pointer); | |
| offset -= base_offset_; | |
| DCHECK(!blocks_.empty()) << "no allocations to discard"; | |
| for (Container::reverse_iterator it = blocks_.rbegin(); | |
| it != blocks_.rend(); | |
| ++it) { | |
| Block& block = *it; | |
| if (block.offset == offset) { | |
| DCHECK(block.state != PADDING) | |
| << "block that corresponds to offset already discarded"; | |
| if (block.state == IN_USE) | |
| num_used_blocks_--; | |
| block.state = PADDING; | |
| // Remove block if it were in the back along with any extra padding. | |
| while (!blocks_.empty() && blocks_.back().state == PADDING) { | |
| free_offset_= blocks_.back().offset; | |
| blocks_.pop_back(); | |
| } | |
| // Remove blocks if it were in the front along with extra padding. | |
| while (!blocks_.empty() && blocks_.front().state == PADDING) { | |
| blocks_.pop_front(); | |
| if (blocks_.empty()) | |
| break; | |
| in_use_offset_ = blocks_.front().offset; | |
| } | |
| // In the special case when there are no blocks, we should be reset it. | |
| if (blocks_.empty()) { | |
| in_use_offset_ = 0; | |
| free_offset_ = 0; | |
| } | |
| return; | |
| } | |
| } | |
| NOTREACHED() << "attempt to discard non-existant block"; | |
| } | |
| uint32_t RingBuffer::GetLargestFreeSizeNoWaiting() { | |
| uint32_t size = GetLargestFreeSizeNoWaitingInternal(); | |
| DCHECK_EQ(size, RoundToAlignment(size)); | |
| return size; | |
| } | |
| uint32_t RingBuffer::GetLargestFreeSizeNoWaitingInternal() { | |
| while (!blocks_.empty()) { | |
| Block& block = blocks_.front(); | |
| if (!helper_->HasTokenPassed(block.token) || block.state == IN_USE) break; | |
| FreeOldestBlock(); | |
| } | |
| if (free_offset_ == in_use_offset_) { | |
| if (blocks_.empty()) { | |
| // The entire buffer is free. | |
| DCHECK_EQ(free_offset_, 0u); | |
| return size_; | |
| } else { | |
| // The entire buffer is in use. | |
| return 0; | |
| } | |
| } else if (free_offset_ > in_use_offset_) { | |
| // It's free from free_offset_ to size_ and from 0 to in_use_offset_ | |
| return std::max(size_ - free_offset_, in_use_offset_); | |
| } else { | |
| // It's free from free_offset_ -> in_use_offset_; | |
| return in_use_offset_ - free_offset_; | |
| } | |
| } | |
| uint32_t RingBuffer::GetTotalFreeSizeNoWaiting() { | |
| uint32_t largest_free_size = GetLargestFreeSizeNoWaitingInternal(); | |
| if (free_offset_ > in_use_offset_) { | |
| // It's free from free_offset_ to size_ and from 0 to in_use_offset_. | |
| return size_ - free_offset_ + in_use_offset_; | |
| } else { | |
| return largest_free_size; | |
| } | |
| } | |
| void RingBuffer::ShrinkLastBlock(uint32_t new_size) { | |
| if (blocks_.empty()) | |
| return; | |
| auto& block = blocks_.back(); | |
| DCHECK_LT(new_size, block.size); | |
| DCHECK_EQ(block.state, IN_USE); | |
| // Can't shrink to size 0, see comments in Alloc. | |
| new_size = std::max(new_size, 1u); | |
| // Allocate rounded to alignment size so that the offsets are always | |
| // memory-aligned. | |
| new_size = RoundToAlignment(new_size); | |
| if (new_size == block.size) | |
| return; | |
| free_offset_ = block.offset + new_size; | |
| block.size = new_size; | |
| } | |
| } // namespace gpu |