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/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 if (available_queries_.empty()) 120 available_queries_.push_back(Query::Create(gl_)); 121 122 available_queries_.front()->Begin(); 123 } 124 125 void TextureUploader::EndQuery() { 126 available_queries_.front()->End(); 127 pending_queries_.push_back(available_queries_.take_front()); 128 num_blocking_texture_uploads_++; 129 } 130 131 void TextureUploader::Upload(const uint8* image, 132 gfx::Rect image_rect, 133 gfx::Rect source_rect, 134 gfx::Vector2d dest_offset, 135 ResourceFormat format, 136 gfx::Size size) { 137 CHECK(image_rect.Contains(source_rect)); 138 139 bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size; 140 141 if (is_full_upload) 142 BeginQuery(); 143 144 if (format == ETC1) { 145 // ETC1 does not support subimage uploads. 146 DCHECK(is_full_upload); 147 UploadWithTexImageETC1(image, size); 148 } else { 149 UploadWithMapTexSubImage( 150 image, image_rect, source_rect, dest_offset, format); 151 } 152 153 if (is_full_upload) 154 EndQuery(); 155 156 num_texture_uploads_since_last_flush_++; 157 if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod) 158 Flush(); 159 } 160 161 void TextureUploader::Flush() { 162 if (!num_texture_uploads_since_last_flush_) 163 return; 164 165 gl_->ShallowFlushCHROMIUM(); 166 167 num_texture_uploads_since_last_flush_ = 0; 168 } 169 170 void TextureUploader::ReleaseCachedQueries() { 171 ProcessQueries(); 172 available_queries_.clear(); 173 } 174 175 void TextureUploader::UploadWithTexSubImage(const uint8* image, 176 gfx::Rect image_rect, 177 gfx::Rect source_rect, 178 gfx::Vector2d dest_offset, 179 ResourceFormat format) { 180 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage"); 181 182 // Early-out if this is a no-op, and assert that |image| be valid if this is 183 // not a no-op. 184 if (source_rect.IsEmpty()) 185 return; 186 DCHECK(image); 187 188 // Offset from image-rect to source-rect. 189 gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); 190 191 const uint8* pixel_source; 192 unsigned bytes_per_pixel = BitsPerPixel(format) / 8; 193 // Use 4-byte row alignment (OpenGL default) for upload performance. 194 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. 195 unsigned upload_image_stride = 196 RoundUp(bytes_per_pixel * source_rect.width(), 4u); 197 198 if (upload_image_stride == image_rect.width() * bytes_per_pixel && 199 !offset.x()) { 200 pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()]; 201 } else { 202 size_t needed_size = upload_image_stride * source_rect.height(); 203 if (sub_image_size_ < needed_size) { 204 sub_image_.reset(new uint8[needed_size]); 205 sub_image_size_ = needed_size; 206 } 207 // Strides not equal, so do a row-by-row memcpy from the 208 // paint results into a temp buffer for uploading. 209 for (int row = 0; row < source_rect.height(); ++row) 210 memcpy(&sub_image_[upload_image_stride * row], 211 &image[bytes_per_pixel * 212 (offset.x() + (offset.y() + row) * image_rect.width())], 213 source_rect.width() * bytes_per_pixel); 214 215 pixel_source = &sub_image_[0]; 216 } 217 218 gl_->TexSubImage2D(GL_TEXTURE_2D, 219 0, 220 dest_offset.x(), 221 dest_offset.y(), 222 source_rect.width(), 223 source_rect.height(), 224 GLDataFormat(format), 225 GLDataType(format), 226 pixel_source); 227 } 228 229 void TextureUploader::UploadWithMapTexSubImage(const uint8* image, 230 gfx::Rect image_rect, 231 gfx::Rect source_rect, 232 gfx::Vector2d dest_offset, 233 ResourceFormat format) { 234 TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage"); 235 236 // Early-out if this is a no-op, and assert that |image| be valid if this is 237 // not a no-op. 238 if (source_rect.IsEmpty()) 239 return; 240 DCHECK(image); 241 // Compressed textures have no implementation of mapTexSubImage. 242 DCHECK_NE(ETC1, format); 243 244 // Offset from image-rect to source-rect. 245 gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); 246 247 unsigned bytes_per_pixel = BitsPerPixel(format) / 8; 248 // Use 4-byte row alignment (OpenGL default) for upload performance. 249 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. 250 unsigned upload_image_stride = 251 RoundUp(bytes_per_pixel * source_rect.width(), 4u); 252 253 // Upload tile data via a mapped transfer buffer 254 uint8* pixel_dest = 255 static_cast<uint8*>(gl_->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D, 256 0, 257 dest_offset.x(), 258 dest_offset.y(), 259 source_rect.width(), 260 source_rect.height(), 261 GLDataFormat(format), 262 GLDataType(format), 263 GL_WRITE_ONLY)); 264 265 if (!pixel_dest) { 266 UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format); 267 return; 268 } 269 270 if (upload_image_stride == image_rect.width() * bytes_per_pixel && 271 !offset.x()) { 272 memcpy(pixel_dest, 273 &image[image_rect.width() * bytes_per_pixel * offset.y()], 274 source_rect.height() * image_rect.width() * bytes_per_pixel); 275 } else { 276 // Strides not equal, so do a row-by-row memcpy from the 277 // paint results into the pixel_dest. 278 for (int row = 0; row < source_rect.height(); ++row) { 279 memcpy(&pixel_dest[upload_image_stride * row], 280 &image[bytes_per_pixel * 281 (offset.x() + (offset.y() + row) * image_rect.width())], 282 source_rect.width() * bytes_per_pixel); 283 } 284 } 285 286 gl_->UnmapTexSubImage2DCHROMIUM(pixel_dest); 287 } 288 289 void TextureUploader::UploadWithTexImageETC1(const uint8* image, 290 gfx::Size size) { 291 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexImageETC1"); 292 DCHECK_EQ(0, size.width() % 4); 293 DCHECK_EQ(0, size.height() % 4); 294 295 gl_->CompressedTexImage2D(GL_TEXTURE_2D, 296 0, 297 GLInternalFormat(ETC1), 298 size.width(), 299 size.height(), 300 0, 301 Resource::MemorySizeBytes(size, ETC1), 302 image); 303 } 304 305 void TextureUploader::ProcessQueries() { 306 while (!pending_queries_.empty()) { 307 if (pending_queries_.front()->IsPending()) 308 break; 309 310 unsigned us_elapsed = pending_queries_.front()->Value(); 311 UMA_HISTOGRAM_CUSTOM_COUNTS( 312 "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50); 313 314 // Clamp the queries to saner values in case the queries fail. 315 us_elapsed = std::max(1u, us_elapsed); 316 us_elapsed = std::min(15000u, us_elapsed); 317 318 if (!pending_queries_.front()->is_non_blocking()) 319 num_blocking_texture_uploads_--; 320 321 // Remove the min and max value from our history and insert the new one. 322 double textures_per_second = 1.0 / (us_elapsed * 1e-6); 323 if (textures_per_second_history_.size() >= kUploadHistorySizeMax) { 324 textures_per_second_history_.erase(textures_per_second_history_.begin()); 325 textures_per_second_history_.erase(--textures_per_second_history_.end()); 326 } 327 textures_per_second_history_.insert(textures_per_second); 328 329 available_queries_.push_back(pending_queries_.take_front()); 330 } 331 } 332 333 } // namespace cc 334