Home | History | Annotate | Download | only in resources
      1 // Copyright 2014 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 "cc/resources/texture_uploader.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/debug/trace_event.h"
     11 #include "base/metrics/histogram.h"
     12 #include "cc/base/util.h"
     13 #include "cc/resources/prioritized_resource.h"
     14 #include "cc/resources/resource.h"
     15 #include "gpu/GLES2/gl2extchromium.h"
     16 #include "gpu/command_buffer/client/gles2_interface.h"
     17 #include "third_party/khronos/GLES2/gl2.h"
     18 #include "third_party/khronos/GLES2/gl2ext.h"
     19 #include "ui/gfx/rect.h"
     20 #include "ui/gfx/vector2d.h"
     21 
     22 using gpu::gles2::GLES2Interface;
     23 
     24 namespace {
     25 
     26 // How many previous uploads to use when predicting future throughput.
     27 static const size_t kUploadHistorySizeMax = 1000;
     28 static const size_t kUploadHistorySizeInitial = 100;
     29 
     30 // Global estimated number of textures per second to maintain estimates across
     31 // subsequent instances of TextureUploader.
     32 // More than one thread will not access this variable, so we do not need to
     33 // synchronize access.
     34 static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0;
     35 
     36 // Flush interval when performing texture uploads.
     37 static const size_t kTextureUploadFlushPeriod = 4;
     38 
     39 }  // anonymous namespace
     40 
     41 namespace cc {
     42 
     43 TextureUploader::Query::Query(GLES2Interface* gl)
     44     : gl_(gl),
     45       query_id_(0),
     46       value_(0),
     47       has_value_(false),
     48       is_non_blocking_(false) {
     49   gl_->GenQueriesEXT(1, &query_id_);
     50 }
     51 
     52 TextureUploader::Query::~Query() { gl_->DeleteQueriesEXT(1, &query_id_); }
     53 
     54 void TextureUploader::Query::Begin() {
     55   has_value_ = false;
     56   is_non_blocking_ = false;
     57   gl_->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_);
     58 }
     59 
     60 void TextureUploader::Query::End() {
     61   gl_->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM);
     62 }
     63 
     64 bool TextureUploader::Query::IsPending() {
     65   unsigned available = 1;
     66   gl_->GetQueryObjectuivEXT(
     67       query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
     68   return !available;
     69 }
     70 
     71 unsigned TextureUploader::Query::Value() {
     72   if (!has_value_) {
     73     gl_->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &value_);
     74     has_value_ = true;
     75   }
     76   return value_;
     77 }
     78 
     79 TextureUploader::TextureUploader(GLES2Interface* gl)
     80     : gl_(gl),
     81       num_blocking_texture_uploads_(0),
     82       sub_image_size_(0),
     83       num_texture_uploads_since_last_flush_(0) {
     84   for (size_t i = kUploadHistorySizeInitial; i > 0; i--)
     85     textures_per_second_history_.insert(kDefaultEstimatedTexturesPerSecond);
     86 }
     87 
     88 TextureUploader::~TextureUploader() {}
     89 
     90 size_t TextureUploader::NumBlockingUploads() {
     91   ProcessQueries();
     92   return num_blocking_texture_uploads_;
     93 }
     94 
     95 void TextureUploader::MarkPendingUploadsAsNonBlocking() {
     96   for (ScopedPtrDeque<Query>::iterator it = pending_queries_.begin();
     97        it != pending_queries_.end();
     98        ++it) {
     99     if ((*it)->is_non_blocking())
    100       continue;
    101 
    102     num_blocking_texture_uploads_--;
    103     (*it)->mark_as_non_blocking();
    104   }
    105 
    106   DCHECK(!num_blocking_texture_uploads_);
    107 }
    108 
    109 double TextureUploader::EstimatedTexturesPerSecond() {
    110   ProcessQueries();
    111 
    112   // Use the median as our estimate.
    113   std::multiset<double>::iterator median = textures_per_second_history_.begin();
    114   std::advance(median, textures_per_second_history_.size() / 2);
    115   return *median;
    116 }
    117 
    118 void TextureUploader::BeginQuery() {
    119   // Check to see if any of the pending queries are free before allocating a
    120   // new one. If this is not done, queries may be allocated without bound.
    121   // http://crbug.com/398072
    122   if (available_queries_.empty())
    123     ProcessQueries();
    124 
    125   if (available_queries_.empty())
    126     available_queries_.push_back(Query::Create(gl_));
    127 
    128   available_queries_.front()->Begin();
    129 }
    130 
    131 void TextureUploader::EndQuery() {
    132   available_queries_.front()->End();
    133   pending_queries_.push_back(available_queries_.take_front());
    134   num_blocking_texture_uploads_++;
    135 }
    136 
    137 void TextureUploader::Upload(const uint8* image,
    138                              const gfx::Rect& image_rect,
    139                              const gfx::Rect& source_rect,
    140                              gfx::Vector2d dest_offset,
    141                              ResourceFormat format,
    142                              const gfx::Size& size) {
    143   CHECK(image_rect.Contains(source_rect));
    144 
    145   bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size;
    146 
    147   if (is_full_upload)
    148     BeginQuery();
    149 
    150   if (format == ETC1) {
    151     // ETC1 does not support subimage uploads.
    152     DCHECK(is_full_upload);
    153     UploadWithTexImageETC1(image, size);
    154   } else {
    155     UploadWithMapTexSubImage(
    156         image, image_rect, source_rect, dest_offset, format);
    157   }
    158 
    159   if (is_full_upload)
    160     EndQuery();
    161 
    162   num_texture_uploads_since_last_flush_++;
    163   if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod)
    164     Flush();
    165 }
    166 
    167 void TextureUploader::Flush() {
    168   if (!num_texture_uploads_since_last_flush_)
    169     return;
    170 
    171   gl_->ShallowFlushCHROMIUM();
    172 
    173   num_texture_uploads_since_last_flush_ = 0;
    174 }
    175 
    176 void TextureUploader::ReleaseCachedQueries() {
    177   ProcessQueries();
    178   available_queries_.clear();
    179 }
    180 
    181 void TextureUploader::UploadWithTexSubImage(const uint8* image,
    182                                             const gfx::Rect& image_rect,
    183                                             const gfx::Rect& source_rect,
    184                                             gfx::Vector2d dest_offset,
    185                                             ResourceFormat format) {
    186   TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage");
    187 
    188   // Early-out if this is a no-op, and assert that |image| be valid if this is
    189   // not a no-op.
    190   if (source_rect.IsEmpty())
    191     return;
    192   DCHECK(image);
    193 
    194   // Offset from image-rect to source-rect.
    195   gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
    196 
    197   const uint8* pixel_source;
    198   unsigned bytes_per_pixel = BitsPerPixel(format) / 8;
    199   // Use 4-byte row alignment (OpenGL default) for upload performance.
    200   // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
    201   unsigned upload_image_stride =
    202       RoundUp(bytes_per_pixel * source_rect.width(), 4u);
    203 
    204   if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
    205       !offset.x()) {
    206     pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()];
    207   } else {
    208     size_t needed_size = upload_image_stride * source_rect.height();
    209     if (sub_image_size_ < needed_size) {
    210       sub_image_.reset(new uint8[needed_size]);
    211       sub_image_size_ = needed_size;
    212     }
    213     // Strides not equal, so do a row-by-row memcpy from the
    214     // paint results into a temp buffer for uploading.
    215     for (int row = 0; row < source_rect.height(); ++row)
    216       memcpy(&sub_image_[upload_image_stride * row],
    217              &image[bytes_per_pixel *
    218                     (offset.x() + (offset.y() + row) * image_rect.width())],
    219              source_rect.width() * bytes_per_pixel);
    220 
    221     pixel_source = &sub_image_[0];
    222   }
    223 
    224   gl_->TexSubImage2D(GL_TEXTURE_2D,
    225                      0,
    226                      dest_offset.x(),
    227                      dest_offset.y(),
    228                      source_rect.width(),
    229                      source_rect.height(),
    230                      GLDataFormat(format),
    231                      GLDataType(format),
    232                      pixel_source);
    233 }
    234 
    235 void TextureUploader::UploadWithMapTexSubImage(const uint8* image,
    236                                                const gfx::Rect& image_rect,
    237                                                const gfx::Rect& source_rect,
    238                                                gfx::Vector2d dest_offset,
    239                                                ResourceFormat format) {
    240   TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage");
    241 
    242   // Early-out if this is a no-op, and assert that |image| be valid if this is
    243   // not a no-op.
    244   if (source_rect.IsEmpty())
    245     return;
    246   DCHECK(image);
    247   // Compressed textures have no implementation of mapTexSubImage.
    248   DCHECK_NE(ETC1, format);
    249 
    250   // Offset from image-rect to source-rect.
    251   gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
    252 
    253   unsigned bytes_per_pixel = BitsPerPixel(format) / 8;
    254   // Use 4-byte row alignment (OpenGL default) for upload performance.
    255   // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
    256   unsigned upload_image_stride =
    257       RoundUp(bytes_per_pixel * source_rect.width(), 4u);
    258 
    259   // Upload tile data via a mapped transfer buffer
    260   uint8* pixel_dest =
    261       static_cast<uint8*>(gl_->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
    262                                                         0,
    263                                                         dest_offset.x(),
    264                                                         dest_offset.y(),
    265                                                         source_rect.width(),
    266                                                         source_rect.height(),
    267                                                         GLDataFormat(format),
    268                                                         GLDataType(format),
    269                                                         GL_WRITE_ONLY));
    270 
    271   if (!pixel_dest) {
    272     UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
    273     return;
    274   }
    275 
    276   if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
    277       !offset.x()) {
    278     memcpy(pixel_dest,
    279            &image[image_rect.width() * bytes_per_pixel * offset.y()],
    280            source_rect.height() * image_rect.width() * bytes_per_pixel);
    281   } else {
    282     // Strides not equal, so do a row-by-row memcpy from the
    283     // paint results into the pixel_dest.
    284     for (int row = 0; row < source_rect.height(); ++row) {
    285       memcpy(&pixel_dest[upload_image_stride * row],
    286              &image[bytes_per_pixel *
    287                     (offset.x() + (offset.y() + row) * image_rect.width())],
    288              source_rect.width() * bytes_per_pixel);
    289     }
    290   }
    291 
    292   gl_->UnmapTexSubImage2DCHROMIUM(pixel_dest);
    293 }
    294 
    295 void TextureUploader::UploadWithTexImageETC1(const uint8* image,
    296                                              const gfx::Size& size) {
    297   TRACE_EVENT0("cc", "TextureUploader::UploadWithTexImageETC1");
    298   DCHECK_EQ(0, size.width() % 4);
    299   DCHECK_EQ(0, size.height() % 4);
    300 
    301   gl_->CompressedTexImage2D(GL_TEXTURE_2D,
    302                             0,
    303                             GLInternalFormat(ETC1),
    304                             size.width(),
    305                             size.height(),
    306                             0,
    307                             Resource::MemorySizeBytes(size, ETC1),
    308                             image);
    309 }
    310 
    311 void TextureUploader::ProcessQueries() {
    312   while (!pending_queries_.empty()) {
    313     if (pending_queries_.front()->IsPending())
    314       break;
    315 
    316     unsigned us_elapsed = pending_queries_.front()->Value();
    317     UMA_HISTOGRAM_CUSTOM_COUNTS(
    318         "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50);
    319 
    320     // Clamp the queries to saner values in case the queries fail.
    321     us_elapsed = std::max(1u, us_elapsed);
    322     us_elapsed = std::min(15000u, us_elapsed);
    323 
    324     if (!pending_queries_.front()->is_non_blocking())
    325       num_blocking_texture_uploads_--;
    326 
    327     // Remove the min and max value from our history and insert the new one.
    328     double textures_per_second = 1.0 / (us_elapsed * 1e-6);
    329     if (textures_per_second_history_.size() >= kUploadHistorySizeMax) {
    330       textures_per_second_history_.erase(textures_per_second_history_.begin());
    331       textures_per_second_history_.erase(--textures_per_second_history_.end());
    332     }
    333     textures_per_second_history_.insert(textures_per_second);
    334 
    335     available_queries_.push_back(pending_queries_.take_front());
    336   }
    337 }
    338 
    339 }  // namespace cc
    340