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