1// Copyright (c) 2012 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 command buffer helper class.
6
7#include "gpu/command_buffer/client/cmd_buffer_helper.h"
8#include "gpu/command_buffer/common/command_buffer.h"
9#include "gpu/command_buffer/common/trace_event.h"
10
11namespace gpu {
12
13namespace {
14const int kCommandsPerFlushCheck = 100;
15const double kFlushDelay = 1.0 / (5.0 * 60.0);
16}
17
18CommandBufferHelper::CommandBufferHelper(CommandBuffer* command_buffer)
19    : command_buffer_(command_buffer),
20      ring_buffer_id_(-1),
21      ring_buffer_size_(0),
22      entries_(NULL),
23      total_entry_count_(0),
24      token_(0),
25      put_(0),
26      last_put_sent_(0),
27      commands_issued_(0),
28      usable_(true),
29      context_lost_(false),
30      flush_automatically_(true),
31      last_flush_time_(0) {
32}
33
34void CommandBufferHelper::SetAutomaticFlushes(bool enabled) {
35  flush_automatically_ = enabled;
36}
37
38bool CommandBufferHelper::IsContextLost() {
39  if (!context_lost_) {
40    context_lost_ = error::IsError(command_buffer()->GetLastError());
41  }
42  return context_lost_;
43}
44
45bool CommandBufferHelper::AllocateRingBuffer() {
46  if (!usable()) {
47    return false;
48  }
49
50  if (HaveRingBuffer()) {
51    return true;
52  }
53
54  int32 id = -1;
55  Buffer buffer = command_buffer_->CreateTransferBuffer(ring_buffer_size_, &id);
56  if (id < 0) {
57    ClearUsable();
58    return false;
59  }
60
61  ring_buffer_ = buffer;
62  ring_buffer_id_ = id;
63  command_buffer_->SetGetBuffer(id);
64
65  // TODO(gman): Do we really need to call GetState here? We know get & put = 0
66  // Also do we need to check state.num_entries?
67  CommandBuffer::State state = command_buffer_->GetState();
68  entries_ = static_cast<CommandBufferEntry*>(ring_buffer_.ptr);
69  int32 num_ring_buffer_entries =
70      ring_buffer_size_ / sizeof(CommandBufferEntry);
71  if (num_ring_buffer_entries > state.num_entries) {
72    ClearUsable();
73    return false;
74  }
75
76  total_entry_count_ = num_ring_buffer_entries;
77  put_ = state.put_offset;
78  return true;
79}
80
81void CommandBufferHelper::FreeResources() {
82  if (HaveRingBuffer()) {
83    command_buffer_->DestroyTransferBuffer(ring_buffer_id_);
84    ring_buffer_id_ = -1;
85  }
86}
87
88void CommandBufferHelper::FreeRingBuffer() {
89  GPU_CHECK((put_ == get_offset()) ||
90      error::IsError(command_buffer_->GetLastState().error));
91  FreeResources();
92}
93
94bool CommandBufferHelper::Initialize(int32 ring_buffer_size) {
95  ring_buffer_size_ = ring_buffer_size;
96  return AllocateRingBuffer();
97}
98
99CommandBufferHelper::~CommandBufferHelper() {
100  FreeResources();
101}
102
103bool CommandBufferHelper::FlushSync() {
104  if (!usable()) {
105    return false;
106  }
107  last_flush_time_ = clock();
108  last_put_sent_ = put_;
109  CommandBuffer::State state = command_buffer_->FlushSync(put_, get_offset());
110  return state.error == error::kNoError;
111}
112
113void CommandBufferHelper::Flush() {
114  if (usable() && last_put_sent_ != put_) {
115    last_flush_time_ = clock();
116    last_put_sent_ = put_;
117    command_buffer_->Flush(put_);
118  }
119}
120
121// Calls Flush() and then waits until the buffer is empty. Break early if the
122// error is set.
123bool CommandBufferHelper::Finish() {
124  TRACE_EVENT0("gpu", "CommandBufferHelper::Finish");
125  if (!usable()) {
126    return false;
127  }
128  // If there is no work just exit.
129  if (put_ == get_offset()) {
130    return true;
131  }
132  GPU_DCHECK(HaveRingBuffer());
133  do {
134    // Do not loop forever if the flush fails, meaning the command buffer reader
135    // has shutdown.
136    if (!FlushSync())
137      return false;
138  } while (put_ != get_offset());
139
140  return true;
141}
142
143// Inserts a new token into the command stream. It uses an increasing value
144// scheme so that we don't lose tokens (a token has passed if the current token
145// value is higher than that token). Calls Finish() if the token value wraps,
146// which will be rare.
147int32 CommandBufferHelper::InsertToken() {
148  AllocateRingBuffer();
149  if (!usable()) {
150    return token_;
151  }
152  GPU_DCHECK(HaveRingBuffer());
153  // Increment token as 31-bit integer. Negative values are used to signal an
154  // error.
155  token_ = (token_ + 1) & 0x7FFFFFFF;
156  cmd::SetToken* cmd = GetCmdSpace<cmd::SetToken>();
157  if (cmd) {
158    cmd->Init(token_);
159    if (token_ == 0) {
160      TRACE_EVENT0("gpu", "CommandBufferHelper::InsertToken(wrapped)");
161      // we wrapped
162      Finish();
163      GPU_DCHECK_EQ(token_, last_token_read());
164    }
165  }
166  return token_;
167}
168
169// Waits until the current token value is greater or equal to the value passed
170// in argument.
171void CommandBufferHelper::WaitForToken(int32 token) {
172  if (!usable() || !HaveRingBuffer()) {
173    return;
174  }
175  // Return immediately if corresponding InsertToken failed.
176  if (token < 0)
177    return;
178  if (token > token_) return;  // we wrapped
179  while (last_token_read() < token) {
180    if (get_offset() == put_) {
181      GPU_LOG(FATAL) << "Empty command buffer while waiting on a token.";
182      return;
183    }
184    // Do not loop forever if the flush fails, meaning the command buffer reader
185    // has shutdown.
186    if (!FlushSync())
187      return;
188  }
189}
190
191// Waits for available entries, basically waiting until get >= put + count + 1.
192// It actually waits for contiguous entries, so it may need to wrap the buffer
193// around, adding a noops. Thus this function may change the value of put_. The
194// function will return early if an error occurs, in which case the available
195// space may not be available.
196void CommandBufferHelper::WaitForAvailableEntries(int32 count) {
197  AllocateRingBuffer();
198  if (!usable()) {
199    return;
200  }
201  GPU_DCHECK(HaveRingBuffer());
202  GPU_DCHECK(count < total_entry_count_);
203  if (put_ + count > total_entry_count_) {
204    // There's not enough room between the current put and the end of the
205    // buffer, so we need to wrap. We will add noops all the way to the end,
206    // but we need to make sure get wraps first, actually that get is 1 or
207    // more (since put will wrap to 0 after we add the noops).
208    GPU_DCHECK_LE(1, put_);
209    if (get_offset() > put_ || get_offset() == 0) {
210      TRACE_EVENT0("gpu", "CommandBufferHelper::WaitForAvailableEntries");
211      while (get_offset() > put_ || get_offset() == 0) {
212        // Do not loop forever if the flush fails, meaning the command buffer
213        // reader has shutdown.
214        if (!FlushSync())
215          return;
216      }
217    }
218    // Insert Noops to fill out the buffer.
219    int32 num_entries = total_entry_count_ - put_;
220    while (num_entries > 0) {
221      int32 num_to_skip = std::min(CommandHeader::kMaxSize, num_entries);
222      cmd::Noop::Set(&entries_[put_], num_to_skip);
223      put_ += num_to_skip;
224      num_entries -= num_to_skip;
225    }
226    put_ = 0;
227  }
228  if (AvailableEntries() < count) {
229    TRACE_EVENT0("gpu", "CommandBufferHelper::WaitForAvailableEntries1");
230    while (AvailableEntries() < count) {
231      // Do not loop forever if the flush fails, meaning the command buffer
232      // reader has shutdown.
233      if (!FlushSync())
234        return;
235    }
236  }
237  // Force a flush if the buffer is getting half full, or even earlier if the
238  // reader is known to be idle.
239  int32 pending =
240      (put_ + total_entry_count_ - last_put_sent_) % total_entry_count_;
241  int32 limit = total_entry_count_ /
242      ((get_offset() == last_put_sent_) ? 16 : 2);
243  if (pending > limit) {
244    Flush();
245  } else if (flush_automatically_ &&
246             (commands_issued_ % kCommandsPerFlushCheck == 0)) {
247#if !defined(OS_ANDROID)
248    // Allow this command buffer to be pre-empted by another if a "reasonable"
249    // amount of work has been done. On highend machines, this reduces the
250    // latency of GPU commands. However, on Android, this can cause the
251    // kernel to thrash between generating GPU commands and executing them.
252    clock_t current_time = clock();
253    if (current_time - last_flush_time_ > kFlushDelay * CLOCKS_PER_SEC)
254      Flush();
255#endif
256  }
257}
258
259CommandBufferEntry* CommandBufferHelper::GetSpace(uint32 entries) {
260  AllocateRingBuffer();
261  if (!usable()) {
262    return NULL;
263  }
264  GPU_DCHECK(HaveRingBuffer());
265  ++commands_issued_;
266  WaitForAvailableEntries(entries);
267  CommandBufferEntry* space = &entries_[put_];
268  put_ += entries;
269  GPU_DCHECK_LE(put_, total_entry_count_);
270  if (put_ == total_entry_count_) {
271    put_ = 0;
272  }
273  return space;
274}
275
276}  // namespace gpu
277