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 22using gpu::gles2::GLES2Interface; 23 24namespace { 25 26// How many previous uploads to use when predicting future throughput. 27static const size_t kUploadHistorySizeMax = 1000; 28static 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. 34static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0; 35 36// Flush interval when performing texture uploads. 37static const size_t kTextureUploadFlushPeriod = 4; 38 39} // anonymous namespace 40 41namespace cc { 42 43TextureUploader::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 52TextureUploader::Query::~Query() { gl_->DeleteQueriesEXT(1, &query_id_); } 53 54void TextureUploader::Query::Begin() { 55 has_value_ = false; 56 is_non_blocking_ = false; 57 gl_->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_); 58} 59 60void TextureUploader::Query::End() { 61 gl_->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM); 62} 63 64bool TextureUploader::Query::IsPending() { 65 unsigned available = 1; 66 gl_->GetQueryObjectuivEXT( 67 query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available); 68 return !available; 69} 70 71unsigned 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 79TextureUploader::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 88TextureUploader::~TextureUploader() {} 89 90size_t TextureUploader::NumBlockingUploads() { 91 ProcessQueries(); 92 return num_blocking_texture_uploads_; 93} 94 95void 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 109double 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 118void 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 131void TextureUploader::EndQuery() { 132 available_queries_.front()->End(); 133 pending_queries_.push_back(available_queries_.take_front()); 134 num_blocking_texture_uploads_++; 135} 136 137void 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 167void 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 176void TextureUploader::ReleaseCachedQueries() { 177 ProcessQueries(); 178 available_queries_.clear(); 179} 180 181void 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 235void 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 295void 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 311void 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