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