| 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
|
|
|