cmd_buffer_helper.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca// Use of this source code is governed by a BSD-style license that can be
3de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca// found in the LICENSE file.
4de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca
5de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca// This file contains the implementation of the command buffer helper class.
6de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca
7de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca#include "gpu/command_buffer/client/cmd_buffer_helper.h"
8de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca
9de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca#include "base/logging.h"
10de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca#include "gpu/command_buffer/common/command_buffer.h"
11de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca#include "gpu/command_buffer/common/trace_event.h"
12de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca
13de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonsecanamespace gpu {
14de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca
15de29ee0e6de9e34d19ed20017028614cce7b4ba1José FonsecaCommandBufferHelper::CommandBufferHelper(CommandBuffer* command_buffer)
16de29ee0e6de9e34d19ed20017028614cce7b4ba1José Fonseca    : command_buffer_(command_buffer),
17      ring_buffer_id_(-1),
18      ring_buffer_size_(0),
19      entries_(NULL),
20      total_entry_count_(0),
21      immediate_entry_count_(0),
22      token_(0),
23      put_(0),
24      last_put_sent_(0),
25#if defined(CMD_HELPER_PERIODIC_FLUSH_CHECK)
26      commands_issued_(0),
27#endif
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  CalcImmediateEntries(0);
37}
38
39bool CommandBufferHelper::IsContextLost() {
40  if (!context_lost_) {
41    context_lost_ = error::IsError(command_buffer()->GetLastError());
42  }
43  return context_lost_;
44}
45
46void CommandBufferHelper::CalcImmediateEntries(int waiting_count) {
47  DCHECK_GE(waiting_count, 0);
48
49  // Check if usable & allocated.
50  if (!usable() || !HaveRingBuffer()) {
51    immediate_entry_count_ = 0;
52    return;
53  }
54
55  // Get maximum safe contiguous entries.
56  const int32 curr_get = get_offset();
57  if (curr_get > put_) {
58    immediate_entry_count_ = curr_get - put_ - 1;
59  } else {
60    immediate_entry_count_ =
61        total_entry_count_ - put_ - (curr_get == 0 ? 1 : 0);
62  }
63
64  // Limit entry count to force early flushing.
65  if (flush_automatically_) {
66    int32 limit =
67        total_entry_count_ /
68        ((curr_get == last_put_sent_) ? kAutoFlushSmall : kAutoFlushBig);
69
70    int32 pending =
71        (put_ + total_entry_count_ - last_put_sent_) % total_entry_count_;
72
73    if (pending > 0 && pending >= limit) {
74      // Time to force flush.
75      immediate_entry_count_ = 0;
76    } else {
77      // Limit remaining entries, but not lower than waiting_count entries to
78      // prevent deadlock when command size is greater than the flush limit.
79      limit -= pending;
80      limit = limit < waiting_count ? waiting_count : limit;
81      immediate_entry_count_ =
82          immediate_entry_count_ > limit ? limit : immediate_entry_count_;
83    }
84  }
85}
86
87bool CommandBufferHelper::AllocateRingBuffer() {
88  if (!usable()) {
89    return false;
90  }
91
92  if (HaveRingBuffer()) {
93    return true;
94  }
95
96  int32 id = -1;
97  Buffer buffer = command_buffer_->CreateTransferBuffer(ring_buffer_size_, &id);
98  if (id < 0) {
99    ClearUsable();
100    return false;
101  }
102
103  ring_buffer_ = buffer;
104  ring_buffer_id_ = id;
105  command_buffer_->SetGetBuffer(id);
106
107  // TODO(gman): Do we really need to call GetState here? We know get & put = 0
108  // Also do we need to check state.num_entries?
109  CommandBuffer::State state = command_buffer_->GetState();
110  entries_ = static_cast<CommandBufferEntry*>(ring_buffer_.ptr);
111  int32 num_ring_buffer_entries =
112      ring_buffer_size_ / sizeof(CommandBufferEntry);
113  if (num_ring_buffer_entries > state.num_entries) {
114    ClearUsable();
115    return false;
116  }
117
118  total_entry_count_ = num_ring_buffer_entries;
119  put_ = state.put_offset;
120  CalcImmediateEntries(0);
121  return true;
122}
123
124void CommandBufferHelper::FreeResources() {
125  if (HaveRingBuffer()) {
126    command_buffer_->DestroyTransferBuffer(ring_buffer_id_);
127    ring_buffer_id_ = -1;
128    CalcImmediateEntries(0);
129  }
130}
131
132void CommandBufferHelper::FreeRingBuffer() {
133  CHECK((put_ == get_offset()) ||
134      error::IsError(command_buffer_->GetLastState().error));
135  FreeResources();
136}
137
138bool CommandBufferHelper::Initialize(int32 ring_buffer_size) {
139  ring_buffer_size_ = ring_buffer_size;
140  return AllocateRingBuffer();
141}
142
143CommandBufferHelper::~CommandBufferHelper() {
144  FreeResources();
145}
146
147bool CommandBufferHelper::FlushSync() {
148  if (!usable()) {
149    return false;
150  }
151
152  // Wrap put_ before flush.
153  if (put_ == total_entry_count_)
154    put_ = 0;
155
156  last_flush_time_ = clock();
157  last_put_sent_ = put_;
158  CommandBuffer::State state = command_buffer_->FlushSync(put_, get_offset());
159  CalcImmediateEntries(0);
160  return state.error == error::kNoError;
161}
162
163void CommandBufferHelper::Flush() {
164  // Wrap put_ before flush.
165  if (put_ == total_entry_count_)
166    put_ = 0;
167
168  if (usable() && last_put_sent_ != put_) {
169    last_flush_time_ = clock();
170    last_put_sent_ = put_;
171    command_buffer_->Flush(put_);
172    CalcImmediateEntries(0);
173  }
174}
175
176#if defined(CMD_HELPER_PERIODIC_FLUSH_CHECK)
177void CommandBufferHelper::PeriodicFlushCheck() {
178  clock_t current_time = clock();
179  if (current_time - last_flush_time_ > kPeriodicFlushDelay * CLOCKS_PER_SEC)
180    Flush();
181}
182#endif
183
184// Calls Flush() and then waits until the buffer is empty. Break early if the
185// error is set.
186bool CommandBufferHelper::Finish() {
187  TRACE_EVENT0("gpu", "CommandBufferHelper::Finish");
188  if (!usable()) {
189    return false;
190  }
191  // If there is no work just exit.
192  if (put_ == get_offset()) {
193    return true;
194  }
195  DCHECK(HaveRingBuffer());
196  do {
197    // Do not loop forever if the flush fails, meaning the command buffer reader
198    // has shutdown.
199    if (!FlushSync())
200      return false;
201  } while (put_ != get_offset());
202
203  return true;
204}
205
206// Inserts a new token into the command stream. It uses an increasing value
207// scheme so that we don't lose tokens (a token has passed if the current token
208// value is higher than that token). Calls Finish() if the token value wraps,
209// which will be rare.
210int32 CommandBufferHelper::InsertToken() {
211  AllocateRingBuffer();
212  if (!usable()) {
213    return token_;
214  }
215  DCHECK(HaveRingBuffer());
216  // Increment token as 31-bit integer. Negative values are used to signal an
217  // error.
218  token_ = (token_ + 1) & 0x7FFFFFFF;
219  cmd::SetToken* cmd = GetCmdSpace<cmd::SetToken>();
220  if (cmd) {
221    cmd->Init(token_);
222    if (token_ == 0) {
223      TRACE_EVENT0("gpu", "CommandBufferHelper::InsertToken(wrapped)");
224      // we wrapped
225      Finish();
226      DCHECK_EQ(token_, last_token_read());
227    }
228  }
229  return token_;
230}
231
232// Waits until the current token value is greater or equal to the value passed
233// in argument.
234void CommandBufferHelper::WaitForToken(int32 token) {
235  if (!usable() || !HaveRingBuffer()) {
236    return;
237  }
238  // Return immediately if corresponding InsertToken failed.
239  if (token < 0)
240    return;
241  if (token > token_) return;  // we wrapped
242  while (last_token_read() < token) {
243    if (get_offset() == put_) {
244      LOG(FATAL) << "Empty command buffer while waiting on a token.";
245      return;
246    }
247    // Do not loop forever if the flush fails, meaning the command buffer reader
248    // has shutdown.
249    if (!FlushSync())
250      return;
251  }
252}
253
254// Waits for available entries, basically waiting until get >= put + count + 1.
255// It actually waits for contiguous entries, so it may need to wrap the buffer
256// around, adding a noops. Thus this function may change the value of put_. The
257// function will return early if an error occurs, in which case the available
258// space may not be available.
259void CommandBufferHelper::WaitForAvailableEntries(int32 count) {
260  AllocateRingBuffer();
261  if (!usable()) {
262    return;
263  }
264  DCHECK(HaveRingBuffer());
265  DCHECK(count < total_entry_count_);
266  if (put_ + count > total_entry_count_) {
267    // There's not enough room between the current put and the end of the
268    // buffer, so we need to wrap. We will add noops all the way to the end,
269    // but we need to make sure get wraps first, actually that get is 1 or
270    // more (since put will wrap to 0 after we add the noops).
271    DCHECK_LE(1, put_);
272    int32 curr_get = get_offset();
273    if (curr_get > put_ || curr_get == 0) {
274      TRACE_EVENT0("gpu", "CommandBufferHelper::WaitForAvailableEntries");
275      while (curr_get > put_ || curr_get == 0) {
276        // Do not loop forever if the flush fails, meaning the command buffer
277        // reader has shutdown.
278        if (!FlushSync())
279          return;
280        curr_get = get_offset();
281      }
282    }
283    // Insert Noops to fill out the buffer.
284    int32 num_entries = total_entry_count_ - put_;
285    while (num_entries > 0) {
286      int32 num_to_skip = std::min(CommandHeader::kMaxSize, num_entries);
287      cmd::Noop::Set(&entries_[put_], num_to_skip);
288      put_ += num_to_skip;
289      num_entries -= num_to_skip;
290    }
291    put_ = 0;
292  }
293
294  // Try to get 'count' entries without flushing.
295  CalcImmediateEntries(count);
296  if (immediate_entry_count_ < count) {
297    // Try again with a shallow Flush().
298    Flush();
299    CalcImmediateEntries(count);
300    if (immediate_entry_count_ < count) {
301      // Buffer is full.  Need to wait for entries.
302      TRACE_EVENT0("gpu", "CommandBufferHelper::WaitForAvailableEntries1");
303      while (immediate_entry_count_ < count) {
304        // Do not loop forever if the flush fails, meaning the command buffer
305        // reader has shutdown.
306        if (!FlushSync())
307          return;
308        CalcImmediateEntries(count);
309      }
310    }
311  }
312}
313
314
315}  // namespace gpu
316