1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file contains the implementation of the RingBuffer class.
6
7#include "gpu/command_buffer/client/ring_buffer.h"
8
9#include <algorithm>
10
11#include "base/logging.h"
12#include "gpu/command_buffer/client/cmd_buffer_helper.h"
13
14namespace gpu {
15
16RingBuffer::RingBuffer(unsigned int alignment, Offset base_offset,
17                       unsigned int size, CommandBufferHelper* helper,
18                       void* base)
19    : helper_(helper),
20      base_offset_(base_offset),
21      size_(size),
22      free_offset_(0),
23      in_use_offset_(0),
24      alignment_(alignment),
25      base_(static_cast<int8*>(base) - base_offset) {
26}
27
28RingBuffer::~RingBuffer() {
29  // Free blocks pending tokens.
30  while (!blocks_.empty()) {
31    FreeOldestBlock();
32  }
33}
34
35void RingBuffer::FreeOldestBlock() {
36  DCHECK(!blocks_.empty()) << "no free blocks";
37  Block& block = blocks_.front();
38  DCHECK(block.state != IN_USE)
39      << "attempt to allocate more than maximum memory";
40  if (block.state == FREE_PENDING_TOKEN) {
41    helper_->WaitForToken(block.token);
42  }
43  in_use_offset_ += block.size;
44  if (in_use_offset_ == size_) {
45    in_use_offset_ = 0;
46  }
47  // If they match then the entire buffer is free.
48  if (in_use_offset_ == free_offset_) {
49    in_use_offset_ = 0;
50    free_offset_ = 0;
51  }
52  blocks_.pop_front();
53}
54
55void* RingBuffer::Alloc(unsigned int size) {
56  DCHECK_LE(size, size_) << "attempt to allocate more than maximum memory";
57  DCHECK(blocks_.empty() || blocks_.back().state != IN_USE)
58      << "Attempt to alloc another block before freeing the previous.";
59  // Similarly to malloc, an allocation of 0 allocates at least 1 byte, to
60  // return different pointers every time.
61  if (size == 0) size = 1;
62  // Allocate rounded to alignment size so that the offsets are always
63  // memory-aligned.
64  size = RoundToAlignment(size);
65
66  // Wait until there is enough room.
67  while (size > GetLargestFreeSizeNoWaiting()) {
68    FreeOldestBlock();
69  }
70
71  if (size + free_offset_ > size_) {
72    // Add padding to fill space before wrapping around
73    blocks_.push_back(Block(free_offset_, size_ - free_offset_, PADDING));
74    free_offset_ = 0;
75  }
76
77  Offset offset = free_offset_;
78  blocks_.push_back(Block(offset, size, IN_USE));
79  free_offset_ += size;
80  if (free_offset_ == size_) {
81    free_offset_ = 0;
82  }
83  return GetPointer(offset + base_offset_);
84}
85
86void RingBuffer::FreePendingToken(void* pointer,
87                                  unsigned int token) {
88  Offset offset = GetOffset(pointer);
89  offset -= base_offset_;
90  DCHECK(!blocks_.empty()) << "no allocations to free";
91  for (Container::reverse_iterator it = blocks_.rbegin();
92        it != blocks_.rend();
93        ++it) {
94    Block& block = *it;
95    if (block.offset == offset) {
96      DCHECK(block.state == IN_USE)
97          << "block that corresponds to offset already freed";
98      block.token = token;
99      block.state = FREE_PENDING_TOKEN;
100      return;
101    }
102  }
103  NOTREACHED() << "attempt to free non-existant block";
104}
105
106unsigned int RingBuffer::GetLargestFreeSizeNoWaiting() {
107  unsigned int last_token_read = helper_->last_token_read();
108  while (!blocks_.empty()) {
109    Block& block = blocks_.front();
110    if (block.token > last_token_read || block.state == IN_USE) break;
111    FreeOldestBlock();
112  }
113  if (free_offset_ == in_use_offset_) {
114    if (blocks_.empty()) {
115      // The entire buffer is free.
116      DCHECK_EQ(free_offset_, 0u);
117      return size_;
118    } else {
119      // The entire buffer is in use.
120      return 0;
121    }
122  } else if (free_offset_ > in_use_offset_) {
123    // It's free from free_offset_ to size_ and from 0 to in_use_offset_
124    return std::max(size_ - free_offset_, in_use_offset_);
125  } else {
126    // It's free from free_offset_ -> in_use_offset_;
127    return in_use_offset_ - free_offset_;
128  }
129}
130
131}  // namespace gpu
132