Index: cc/resources/texture_uploader.cc |
diff --git a/cc/resources/texture_uploader.cc b/cc/resources/texture_uploader.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..601fa8fbff89de41c26075cf1be5d97dff61fdd3 |
--- /dev/null |
+++ b/cc/resources/texture_uploader.cc |
@@ -0,0 +1,315 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "cc/resources/texture_uploader.h" |
+ |
+#include <algorithm> |
+#include <vector> |
+ |
+#include "base/metrics/histogram.h" |
+#include "base/trace_event/trace_event.h" |
+#include "cc/base/util.h" |
+#include "cc/resources/resource.h" |
+#include "gpu/GLES2/gl2extchromium.h" |
+#include "gpu/command_buffer/client/gles2_interface.h" |
+#include "third_party/khronos/GLES2/gl2.h" |
+#include "third_party/khronos/GLES2/gl2ext.h" |
+#include "ui/gfx/geometry/rect.h" |
+#include "ui/gfx/geometry/vector2d.h" |
+ |
+using gpu::gles2::GLES2Interface; |
+ |
+namespace { |
+ |
+// How many previous uploads to use when predicting future throughput. |
+static const size_t kUploadHistorySizeMax = 1000; |
+static const size_t kUploadHistorySizeInitial = 100; |
+ |
+// Global estimated number of textures per second to maintain estimates across |
+// subsequent instances of TextureUploader. |
+// More than one thread will not access this variable, so we do not need to |
+// synchronize access. |
+static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0; |
+ |
+// Flush interval when performing texture uploads. |
+static const size_t kTextureUploadFlushPeriod = 4; |
+ |
+} // anonymous namespace |
+ |
+namespace cc { |
+ |
+TextureUploader::Query::Query(GLES2Interface* gl) |
+ : gl_(gl), |
+ query_id_(0), |
+ value_(0), |
+ has_value_(false), |
+ is_non_blocking_(false) { |
+ gl_->GenQueriesEXT(1, &query_id_); |
+} |
+ |
+TextureUploader::Query::~Query() { gl_->DeleteQueriesEXT(1, &query_id_); } |
+ |
+void TextureUploader::Query::Begin() { |
+ has_value_ = false; |
+ is_non_blocking_ = false; |
+ gl_->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_); |
+} |
+ |
+void TextureUploader::Query::End() { |
+ gl_->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM); |
+} |
+ |
+bool TextureUploader::Query::IsPending() { |
+ unsigned available = 1; |
+ gl_->GetQueryObjectuivEXT( |
+ query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available); |
+ return !available; |
+} |
+ |
+unsigned TextureUploader::Query::Value() { |
+ if (!has_value_) { |
+ gl_->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &value_); |
+ has_value_ = true; |
+ } |
+ return value_; |
+} |
+ |
+TextureUploader::TextureUploader(GLES2Interface* gl) |
+ : gl_(gl), |
+ num_blocking_texture_uploads_(0), |
+ sub_image_size_(0), |
+ num_texture_uploads_since_last_flush_(0) { |
+ for (size_t i = kUploadHistorySizeInitial; i > 0; i--) |
+ textures_per_second_history_.insert(kDefaultEstimatedTexturesPerSecond); |
+} |
+ |
+TextureUploader::~TextureUploader() {} |
+ |
+size_t TextureUploader::NumBlockingUploads() { |
+ ProcessQueries(); |
+ return num_blocking_texture_uploads_; |
+} |
+ |
+void TextureUploader::MarkPendingUploadsAsNonBlocking() { |
+ for (ScopedPtrDeque<Query>::iterator it = pending_queries_.begin(); |
+ it != pending_queries_.end(); |
+ ++it) { |
+ if ((*it)->is_non_blocking()) |
+ continue; |
+ |
+ num_blocking_texture_uploads_--; |
+ (*it)->mark_as_non_blocking(); |
+ } |
+ |
+ DCHECK(!num_blocking_texture_uploads_); |
+} |
+ |
+double TextureUploader::EstimatedTexturesPerSecond() { |
+ ProcessQueries(); |
+ |
+ // Use the median as our estimate. |
+ std::multiset<double>::iterator median = textures_per_second_history_.begin(); |
+ std::advance(median, textures_per_second_history_.size() / 2); |
+ return *median; |
+} |
+ |
+void TextureUploader::BeginQuery() { |
+ // Check to see if any of the pending queries are free before allocating a |
+ // new one. If this is not done, queries may be allocated without bound. |
+ // http://crbug.com/398072 |
+ if (available_queries_.empty()) |
+ ProcessQueries(); |
+ |
+ if (available_queries_.empty()) |
+ available_queries_.push_back(Query::Create(gl_)); |
+ |
+ available_queries_.front()->Begin(); |
+} |
+ |
+void TextureUploader::EndQuery() { |
+ available_queries_.front()->End(); |
+ pending_queries_.push_back(available_queries_.take_front()); |
+ num_blocking_texture_uploads_++; |
+} |
+ |
+void TextureUploader::Upload(const uint8* image, |
+ const gfx::Rect& image_rect, |
+ const gfx::Rect& source_rect, |
+ gfx::Vector2d dest_offset, |
+ ResourceFormat format, |
+ const gfx::Size& size) { |
+ CHECK(image_rect.Contains(source_rect)); |
+ |
+ bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size; |
+ |
+ if (is_full_upload) |
+ BeginQuery(); |
+ |
+ UploadWithMapTexSubImage(image, image_rect, source_rect, dest_offset, format); |
+ |
+ if (is_full_upload) |
+ EndQuery(); |
+ |
+ num_texture_uploads_since_last_flush_++; |
+ if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod) |
+ Flush(); |
+} |
+ |
+void TextureUploader::Flush() { |
+ if (!num_texture_uploads_since_last_flush_) |
+ return; |
+ |
+ gl_->ShallowFlushCHROMIUM(); |
+ |
+ num_texture_uploads_since_last_flush_ = 0; |
+} |
+ |
+void TextureUploader::ReleaseCachedQueries() { |
+ ProcessQueries(); |
+ available_queries_.clear(); |
+} |
+ |
+void TextureUploader::UploadWithTexSubImage(const uint8* image, |
+ const gfx::Rect& image_rect, |
+ const gfx::Rect& source_rect, |
+ gfx::Vector2d dest_offset, |
+ ResourceFormat format) { |
+ TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage"); |
+ |
+ // Early-out if this is a no-op, and assert that |image| be valid if this is |
+ // not a no-op. |
+ if (source_rect.IsEmpty()) |
+ return; |
+ DCHECK(image); |
+ |
+ // Offset from image-rect to source-rect. |
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); |
+ |
+ const uint8* pixel_source; |
+ unsigned bytes_per_pixel = BitsPerPixel(format) / 8; |
+ // Use 4-byte row alignment (OpenGL default) for upload performance. |
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. |
+ unsigned upload_image_stride = |
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u); |
+ |
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel && |
+ !offset.x()) { |
+ pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()]; |
+ } else { |
+ size_t needed_size = upload_image_stride * source_rect.height(); |
+ if (sub_image_size_ < needed_size) { |
+ sub_image_.reset(new uint8[needed_size]); |
+ sub_image_size_ = needed_size; |
+ } |
+ // Strides not equal, so do a row-by-row memcpy from the |
+ // paint results into a temp buffer for uploading. |
+ for (int row = 0; row < source_rect.height(); ++row) |
+ memcpy(&sub_image_[upload_image_stride * row], |
+ &image[bytes_per_pixel * |
+ (offset.x() + (offset.y() + row) * image_rect.width())], |
+ source_rect.width() * bytes_per_pixel); |
+ |
+ pixel_source = &sub_image_[0]; |
+ } |
+ |
+ gl_->TexSubImage2D(GL_TEXTURE_2D, |
+ 0, |
+ dest_offset.x(), |
+ dest_offset.y(), |
+ source_rect.width(), |
+ source_rect.height(), |
+ GLDataFormat(format), |
+ GLDataType(format), |
+ pixel_source); |
+} |
+ |
+void TextureUploader::UploadWithMapTexSubImage(const uint8* image, |
+ const gfx::Rect& image_rect, |
+ const gfx::Rect& source_rect, |
+ gfx::Vector2d dest_offset, |
+ ResourceFormat format) { |
+ TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage"); |
+ |
+ // Early-out if this is a no-op, and assert that |image| be valid if this is |
+ // not a no-op. |
+ if (source_rect.IsEmpty()) |
+ return; |
+ DCHECK(image); |
+ // Compressed textures have no implementation of mapTexSubImage. |
+ DCHECK_NE(ETC1, format); |
+ |
+ // Offset from image-rect to source-rect. |
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); |
+ |
+ unsigned bytes_per_pixel = BitsPerPixel(format) / 8; |
+ // Use 4-byte row alignment (OpenGL default) for upload performance. |
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. |
+ unsigned upload_image_stride = |
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u); |
+ |
+ // Upload tile data via a mapped transfer buffer |
+ uint8* pixel_dest = |
+ static_cast<uint8*>(gl_->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D, |
+ 0, |
+ dest_offset.x(), |
+ dest_offset.y(), |
+ source_rect.width(), |
+ source_rect.height(), |
+ GLDataFormat(format), |
+ GLDataType(format), |
+ GL_WRITE_ONLY)); |
+ |
+ if (!pixel_dest) { |
+ UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format); |
+ return; |
+ } |
+ |
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel && |
+ !offset.x()) { |
+ memcpy(pixel_dest, |
+ &image[image_rect.width() * bytes_per_pixel * offset.y()], |
+ source_rect.height() * image_rect.width() * bytes_per_pixel); |
+ } else { |
+ // Strides not equal, so do a row-by-row memcpy from the |
+ // paint results into the pixel_dest. |
+ for (int row = 0; row < source_rect.height(); ++row) { |
+ memcpy(&pixel_dest[upload_image_stride * row], |
+ &image[bytes_per_pixel * |
+ (offset.x() + (offset.y() + row) * image_rect.width())], |
+ source_rect.width() * bytes_per_pixel); |
+ } |
+ } |
+ |
+ gl_->UnmapTexSubImage2DCHROMIUM(pixel_dest); |
+} |
+ |
+void TextureUploader::ProcessQueries() { |
+ while (!pending_queries_.empty()) { |
+ if (pending_queries_.front()->IsPending()) |
+ break; |
+ |
+ unsigned us_elapsed = pending_queries_.front()->Value(); |
+ UMA_HISTOGRAM_CUSTOM_COUNTS( |
+ "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50); |
+ |
+ // Clamp the queries to saner values in case the queries fail. |
+ us_elapsed = std::max(1u, us_elapsed); |
+ us_elapsed = std::min(15000u, us_elapsed); |
+ |
+ if (!pending_queries_.front()->is_non_blocking()) |
+ num_blocking_texture_uploads_--; |
+ |
+ // Remove the min and max value from our history and insert the new one. |
+ double textures_per_second = 1.0 / (us_elapsed * 1e-6); |
+ if (textures_per_second_history_.size() >= kUploadHistorySizeMax) { |
+ textures_per_second_history_.erase(textures_per_second_history_.begin()); |
+ textures_per_second_history_.erase(--textures_per_second_history_.end()); |
+ } |
+ textures_per_second_history_.insert(textures_per_second); |
+ |
+ available_queries_.push_back(pending_queries_.take_front()); |
+ } |
+} |
+ |
+} // namespace cc |