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#include "gpu/command_buffer/service/gpu_scheduler.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/compiler_specific.h"
10#include "base/debug/trace_event.h"
11#include "base/message_loop/message_loop.h"
12#include "base/time/time.h"
13#include "ui/gl/gl_bindings.h"
14#include "ui/gl/gl_fence.h"
15#include "ui/gl/gl_switches.h"
16
17#if defined(OS_WIN)
18#include "base/win/windows_version.h"
19#endif
20
21using ::base::SharedMemory;
22
23namespace gpu {
24
25namespace {
26const int64 kRescheduleTimeOutDelay = 1000;
27const int64 kUnscheduleFenceTimeOutDelay = 10000;
28}
29
30GpuScheduler::GpuScheduler(CommandBuffer* command_buffer,
31                           AsyncAPIInterface* handler,
32                           gles2::GLES2Decoder* decoder)
33    : command_buffer_(command_buffer),
34      handler_(handler),
35      decoder_(decoder),
36      unscheduled_count_(0),
37      rescheduled_count_(0),
38      reschedule_task_factory_(this),
39      was_preempted_(false) {}
40
41GpuScheduler::~GpuScheduler() {
42}
43
44void GpuScheduler::PutChanged() {
45  TRACE_EVENT1(
46     "gpu", "GpuScheduler:PutChanged",
47     "decoder", decoder_ ? decoder_->GetLogger()->GetLogPrefix() : "None");
48
49  CommandBuffer::State state = command_buffer_->GetState();
50
51  // If there is no parser, exit.
52  if (!parser_.get()) {
53    DCHECK_EQ(state.get_offset, state.put_offset);
54    return;
55  }
56
57  parser_->set_put(state.put_offset);
58  if (state.error != error::kNoError)
59    return;
60
61  // Check that the GPU has passed all fences.
62  if (!PollUnscheduleFences())
63    return;
64
65  // One of the unschedule fence tasks might have unscheduled us.
66  if (!IsScheduled())
67    return;
68
69  base::TimeTicks begin_time(base::TimeTicks::HighResNow());
70  error::Error error = error::kNoError;
71  while (!parser_->IsEmpty()) {
72    if (IsPreempted())
73      break;
74
75    DCHECK(IsScheduled());
76    DCHECK(unschedule_fences_.empty());
77
78    error = parser_->ProcessCommand();
79
80    if (error == error::kDeferCommandUntilLater) {
81      DCHECK_GT(unscheduled_count_, 0);
82      break;
83    }
84
85    // TODO(piman): various classes duplicate various pieces of state, leading
86    // to needlessly complex update logic. It should be possible to simply
87    // share the state across all of them.
88    command_buffer_->SetGetOffset(static_cast<int32>(parser_->get()));
89
90    if (error::IsError(error)) {
91      LOG(ERROR) << "[" << decoder_ << "] "
92                 << "GPU PARSE ERROR: " << error;
93      command_buffer_->SetContextLostReason(decoder_->GetContextLostReason());
94      command_buffer_->SetParseError(error);
95      break;
96    }
97
98    if (!command_processed_callback_.is_null())
99      command_processed_callback_.Run();
100
101    if (unscheduled_count_ > 0)
102      break;
103  }
104
105  if (decoder_) {
106    if (!error::IsError(error) && decoder_->WasContextLost()) {
107      command_buffer_->SetContextLostReason(decoder_->GetContextLostReason());
108      command_buffer_->SetParseError(error::kLostContext);
109    }
110    decoder_->AddProcessingCommandsTime(
111        base::TimeTicks::HighResNow() - begin_time);
112  }
113}
114
115void GpuScheduler::SetScheduled(bool scheduled) {
116  TRACE_EVENT2("gpu", "GpuScheduler:SetScheduled", "this", this,
117               "new unscheduled_count_",
118               unscheduled_count_ + (scheduled? -1 : 1));
119  if (scheduled) {
120    // If the scheduler was rescheduled after a timeout, ignore the subsequent
121    // calls to SetScheduled when they eventually arrive until they are all
122    // accounted for.
123    if (rescheduled_count_ > 0) {
124      --rescheduled_count_;
125      return;
126    } else {
127      --unscheduled_count_;
128    }
129
130    DCHECK_GE(unscheduled_count_, 0);
131
132    if (unscheduled_count_ == 0) {
133      TRACE_EVENT_ASYNC_END1("gpu", "ProcessingSwap", this,
134                             "GpuScheduler", this);
135      // When the scheduler transitions from the unscheduled to the scheduled
136      // state, cancel the task that would reschedule it after a timeout.
137      reschedule_task_factory_.InvalidateWeakPtrs();
138
139      if (!scheduling_changed_callback_.is_null())
140        scheduling_changed_callback_.Run(true);
141    }
142  } else {
143    ++unscheduled_count_;
144    if (unscheduled_count_ == 1) {
145      TRACE_EVENT_ASYNC_BEGIN1("gpu", "ProcessingSwap", this,
146                               "GpuScheduler", this);
147#if defined(OS_WIN)
148      if (base::win::GetVersion() < base::win::VERSION_VISTA) {
149        // When the scheduler transitions from scheduled to unscheduled, post a
150        // delayed task that it will force it back into a scheduled state after
151        // a timeout. This should only be necessary on pre-Vista.
152        base::MessageLoop::current()->PostDelayedTask(
153            FROM_HERE,
154            base::Bind(&GpuScheduler::RescheduleTimeOut,
155                       reschedule_task_factory_.GetWeakPtr()),
156            base::TimeDelta::FromMilliseconds(kRescheduleTimeOutDelay));
157      }
158#endif
159      if (!scheduling_changed_callback_.is_null())
160        scheduling_changed_callback_.Run(false);
161    }
162  }
163}
164
165bool GpuScheduler::IsScheduled() {
166  return unscheduled_count_ == 0;
167}
168
169bool GpuScheduler::HasMoreWork() {
170  return !unschedule_fences_.empty() ||
171         (decoder_ && decoder_->ProcessPendingQueries()) ||
172         HasMoreIdleWork();
173}
174
175void GpuScheduler::SetSchedulingChangedCallback(
176    const SchedulingChangedCallback& callback) {
177  scheduling_changed_callback_ = callback;
178}
179
180Buffer GpuScheduler::GetSharedMemoryBuffer(int32 shm_id) {
181  return command_buffer_->GetTransferBuffer(shm_id);
182}
183
184void GpuScheduler::set_token(int32 token) {
185  command_buffer_->SetToken(token);
186}
187
188bool GpuScheduler::SetGetBuffer(int32 transfer_buffer_id) {
189  Buffer ring_buffer = command_buffer_->GetTransferBuffer(transfer_buffer_id);
190  if (!ring_buffer.ptr) {
191    return false;
192  }
193
194  if (!parser_.get()) {
195    parser_.reset(new CommandParser(handler_));
196  }
197
198  parser_->SetBuffer(
199      ring_buffer.ptr,
200      ring_buffer.size,
201      0,
202      ring_buffer.size);
203
204  SetGetOffset(0);
205  return true;
206}
207
208bool GpuScheduler::SetGetOffset(int32 offset) {
209  if (parser_->set_get(offset)) {
210    command_buffer_->SetGetOffset(static_cast<int32>(parser_->get()));
211    return true;
212  }
213  return false;
214}
215
216int32 GpuScheduler::GetGetOffset() {
217  return parser_->get();
218}
219
220void GpuScheduler::SetCommandProcessedCallback(
221    const base::Closure& callback) {
222  command_processed_callback_ = callback;
223}
224
225void GpuScheduler::DeferToFence(base::Closure task) {
226  unschedule_fences_.push(make_linked_ptr(
227       new UnscheduleFence(gfx::GLFence::Create(), task)));
228  SetScheduled(false);
229}
230
231bool GpuScheduler::PollUnscheduleFences() {
232  if (unschedule_fences_.empty())
233    return true;
234
235  if (unschedule_fences_.front()->fence.get()) {
236    base::Time now = base::Time::Now();
237    base::TimeDelta timeout =
238        base::TimeDelta::FromMilliseconds(kUnscheduleFenceTimeOutDelay);
239
240    while (!unschedule_fences_.empty()) {
241      const UnscheduleFence& fence = *unschedule_fences_.front();
242      if (fence.fence->HasCompleted() ||
243          now - fence.issue_time > timeout) {
244        unschedule_fences_.front()->task.Run();
245        unschedule_fences_.pop();
246        SetScheduled(true);
247      } else {
248        return false;
249      }
250    }
251  } else {
252    glFinish();
253
254    while (!unschedule_fences_.empty()) {
255      unschedule_fences_.front()->task.Run();
256      unschedule_fences_.pop();
257      SetScheduled(true);
258    }
259  }
260
261  return true;
262}
263
264bool GpuScheduler::IsPreempted() {
265  if (!preemption_flag_.get())
266    return false;
267
268  if (!was_preempted_ && preemption_flag_->IsSet()) {
269    TRACE_COUNTER_ID1("gpu", "GpuScheduler::Preempted", this, 1);
270    was_preempted_ = true;
271  } else if (was_preempted_ && !preemption_flag_->IsSet()) {
272    TRACE_COUNTER_ID1("gpu", "GpuScheduler::Preempted", this, 0);
273    was_preempted_ = false;
274  }
275
276  return preemption_flag_->IsSet();
277}
278
279bool GpuScheduler::HasMoreIdleWork() {
280  return (decoder_ && decoder_->HasMoreIdleWork());
281}
282
283void GpuScheduler::PerformIdleWork() {
284  if (!decoder_)
285    return;
286  decoder_->PerformIdleWork();
287}
288
289void GpuScheduler::RescheduleTimeOut() {
290  int new_count = unscheduled_count_ + rescheduled_count_;
291
292  rescheduled_count_ = 0;
293
294  while (unscheduled_count_)
295    SetScheduled(true);
296
297  rescheduled_count_ = new_count;
298}
299
300GpuScheduler::UnscheduleFence::UnscheduleFence(gfx::GLFence* fence_,
301                                               base::Closure task_)
302  : fence(fence_),
303    issue_time(base::Time::Now()),
304    task(task_) {
305}
306
307GpuScheduler::UnscheduleFence::~UnscheduleFence() {
308}
309
310}  // namespace gpu
311