Home | History | Annotate | Download | only in client
      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