| // 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(); | 
 | } | 
 |  | 
 | void* RingBuffer::Alloc(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. | 
 |   if (size == 0) size = 1; | 
 |   // Allocate rounded to alignment size so that the offsets are always | 
 |   // memory-aligned. | 
 |   size = RoundToAlignment(size); | 
 |   DCHECK_LE(size, size_) | 
 |       << "attempt to allocate more than maximum memory after rounding"; | 
 |  | 
 |   // Wait until there is enough room. | 
 |   while (size > GetLargestFreeSizeNoWaitingInternal()) { | 
 |     FreeOldestBlock(); | 
 |   } | 
 |  | 
 |   if (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, size, IN_USE)); | 
 |   num_used_blocks_++; | 
 |  | 
 |   free_offset_ += size; | 
 |   if (free_offset_ == size_) { | 
 |     free_offset_ = 0; | 
 |   } | 
 |  | 
 |   return GetPointer(offset + base_offset_); | 
 | } | 
 |  | 
 | 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 |