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