| // Copyright 2012 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/texture_uploader.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/debug/alias.h" |
| #include "base/debug/trace_event.h" |
| #include "base/metrics/histogram.h" |
| #include "cc/prioritized_resource.h" |
| #include "cc/resource.h" |
| #include "cc/util.h" |
| #include "third_party/khronos/GLES2/gl2.h" |
| #include "third_party/khronos/GLES2/gl2ext.h" |
| #include "ui/gfx/rect.h" |
| #include "ui/gfx/vector2d.h" |
| #include <public/WebGraphicsContext3D.h> |
| |
| namespace { |
| |
| // How many previous uploads to use when predicting future throughput. |
| static const size_t uploadHistorySizeMax = 1000; |
| static const size_t uploadHistorySizeInitial = 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 defaultEstimatedTexturesPerSecond = 48.0 * 60.0; |
| |
| // Flush interval when performing texture uploads. |
| const int textureUploadFlushPeriod = 4; |
| |
| } // anonymous namespace |
| |
| namespace cc { |
| |
| TextureUploader::Query::Query(WebKit::WebGraphicsContext3D* context) |
| : m_context(context) |
| , m_queryId(0) |
| , m_value(0) |
| , m_hasValue(false) |
| , m_isNonBlocking(false) |
| { |
| m_queryId = m_context->createQueryEXT(); |
| } |
| |
| TextureUploader::Query::~Query() |
| { |
| m_context->deleteQueryEXT(m_queryId); |
| } |
| |
| void TextureUploader::Query::begin() |
| { |
| m_hasValue = false; |
| m_isNonBlocking = false; |
| m_context->beginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, m_queryId); |
| } |
| |
| void TextureUploader::Query::end() |
| { |
| m_context->endQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM); |
| } |
| |
| bool TextureUploader::Query::isPending() |
| { |
| unsigned available = 1; |
| m_context->getQueryObjectuivEXT(m_queryId, GL_QUERY_RESULT_AVAILABLE_EXT, &available); |
| return !available; |
| } |
| |
| unsigned TextureUploader::Query::value() |
| { |
| if (!m_hasValue) { |
| m_context->getQueryObjectuivEXT(m_queryId, GL_QUERY_RESULT_EXT, &m_value); |
| m_hasValue = true; |
| } |
| return m_value; |
| } |
| |
| void TextureUploader::Query::markAsNonBlocking() |
| { |
| m_isNonBlocking = true; |
| } |
| |
| bool TextureUploader::Query::isNonBlocking() |
| { |
| return m_isNonBlocking; |
| } |
| |
| TextureUploader::TextureUploader( |
| WebKit::WebGraphicsContext3D* context, |
| bool useMapTexSubImage, |
| bool useShallowFlush) |
| : m_context(context) |
| , m_numBlockingTextureUploads(0) |
| , m_useMapTexSubImage(useMapTexSubImage) |
| , m_subImageSize(0) |
| , m_useShallowFlush(useShallowFlush) |
| , m_numTextureUploadsSinceLastFlush(0) |
| { |
| for (size_t i = uploadHistorySizeInitial; i > 0; i--) |
| m_texturesPerSecondHistory.insert(defaultEstimatedTexturesPerSecond); |
| } |
| |
| TextureUploader::~TextureUploader() |
| { |
| } |
| |
| size_t TextureUploader::numBlockingUploads() |
| { |
| processQueries(); |
| return m_numBlockingTextureUploads; |
| } |
| |
| void TextureUploader::markPendingUploadsAsNonBlocking() |
| { |
| for (ScopedPtrDeque<Query>::iterator it = m_pendingQueries.begin(); |
| it != m_pendingQueries.end(); ++it) { |
| if ((*it)->isNonBlocking()) |
| continue; |
| |
| m_numBlockingTextureUploads--; |
| (*it)->markAsNonBlocking(); |
| } |
| |
| DCHECK(!m_numBlockingTextureUploads); |
| } |
| |
| double TextureUploader::estimatedTexturesPerSecond() |
| { |
| processQueries(); |
| |
| // Use the median as our estimate. |
| std::multiset<double>::iterator median = m_texturesPerSecondHistory.begin(); |
| std::advance(median, m_texturesPerSecondHistory.size() / 2); |
| TRACE_COUNTER_ID1("cc", "EstimatedTexturesPerSecond", m_context, *median); |
| return *median; |
| } |
| |
| void TextureUploader::beginQuery() |
| { |
| if (m_availableQueries.isEmpty()) |
| m_availableQueries.append(Query::create(m_context)); |
| |
| m_availableQueries.first()->begin(); |
| } |
| |
| void TextureUploader::endQuery() |
| { |
| m_availableQueries.first()->end(); |
| m_pendingQueries.append(m_availableQueries.takeFirst()); |
| m_numBlockingTextureUploads++; |
| } |
| |
| void TextureUploader::upload(const uint8* image, |
| const gfx::Rect& image_rect, |
| const gfx::Rect& source_rect, |
| const gfx::Vector2d& dest_offset, |
| GLenum format, |
| const gfx::Size& size) |
| { |
| CHECK(image_rect.Contains(source_rect)); |
| |
| bool isFullUpload = dest_offset.IsZero() && source_rect.size() == size; |
| |
| if (isFullUpload) |
| beginQuery(); |
| |
| if (m_useMapTexSubImage) { |
| uploadWithMapTexSubImage( |
| image, image_rect, source_rect, dest_offset, format); |
| } else { |
| uploadWithTexSubImage( |
| image, image_rect, source_rect, dest_offset, format); |
| } |
| |
| if (isFullUpload) |
| endQuery(); |
| |
| m_numTextureUploadsSinceLastFlush++; |
| if (m_numTextureUploadsSinceLastFlush >= textureUploadFlushPeriod) |
| flush(); |
| } |
| |
| void TextureUploader::flush() { |
| if (!m_numTextureUploadsSinceLastFlush) |
| return; |
| |
| if (m_useShallowFlush) |
| m_context->shallowFlushCHROMIUM(); |
| |
| m_numTextureUploadsSinceLastFlush = 0; |
| } |
| |
| void TextureUploader::uploadWithTexSubImage(const uint8* image, |
| const gfx::Rect& image_rect, |
| const gfx::Rect& source_rect, |
| const gfx::Vector2d& dest_offset, |
| GLenum format) |
| { |
| // Instrumentation to debug issue 156107 |
| int source_rect_x = source_rect.x(); |
| int source_rect_y = source_rect.y(); |
| int source_rect_width = source_rect.width(); |
| int source_rect_height = source_rect.height(); |
| int image_rect_x = image_rect.x(); |
| int image_rect_y = image_rect.y(); |
| int image_rect_width = image_rect.width(); |
| int image_rect_height = image_rect.height(); |
| int dest_offset_x = dest_offset.x(); |
| int dest_offset_y = dest_offset.y(); |
| base::debug::Alias(&image); |
| base::debug::Alias(&source_rect_x); |
| base::debug::Alias(&source_rect_y); |
| base::debug::Alias(&source_rect_width); |
| base::debug::Alias(&source_rect_height); |
| base::debug::Alias(&image_rect_x); |
| base::debug::Alias(&image_rect_y); |
| base::debug::Alias(&image_rect_width); |
| base::debug::Alias(&image_rect_height); |
| base::debug::Alias(&dest_offset_x); |
| base::debug::Alias(&dest_offset_y); |
| TRACE_EVENT0("cc", "TextureUploader::uploadWithTexSubImage"); |
| |
| // Offset from image-rect to source-rect. |
| gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); |
| |
| const uint8* pixel_source; |
| unsigned int bytes_per_pixel = Resource::BytesPerPixel(format); |
| // Use 4-byte row alignment (OpenGL default) for upload performance. |
| // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. |
| unsigned int 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 (m_subImageSize < needed_size) { |
| m_subImage.reset(new uint8[needed_size]); |
| m_subImageSize = 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(&m_subImage[upload_image_stride * row], |
| &image[bytes_per_pixel * (offset.x() + |
| (offset.y() + row) * image_rect.width())], |
| source_rect.width() * bytes_per_pixel); |
| |
| pixel_source = &m_subImage[0]; |
| } |
| |
| m_context->texSubImage2D(GL_TEXTURE_2D, |
| 0, |
| dest_offset.x(), |
| dest_offset.y(), |
| source_rect.width(), |
| source_rect.height(), |
| format, |
| GL_UNSIGNED_BYTE, |
| pixel_source); |
| } |
| |
| void TextureUploader::uploadWithMapTexSubImage(const uint8* image, |
| const gfx::Rect& image_rect, |
| const gfx::Rect& source_rect, |
| const gfx::Vector2d& dest_offset, |
| GLenum format) |
| { |
| // Instrumentation to debug issue 156107 |
| int source_rect_x = source_rect.x(); |
| int source_rect_y = source_rect.y(); |
| int source_rect_width = source_rect.width(); |
| int source_rect_height = source_rect.height(); |
| int image_rect_x = image_rect.x(); |
| int image_rect_y = image_rect.y(); |
| int image_rect_width = image_rect.width(); |
| int image_rect_height = image_rect.height(); |
| int dest_offset_x = dest_offset.x(); |
| int dest_offset_y = dest_offset.y(); |
| base::debug::Alias(&image); |
| base::debug::Alias(&source_rect_x); |
| base::debug::Alias(&source_rect_y); |
| base::debug::Alias(&source_rect_width); |
| base::debug::Alias(&source_rect_height); |
| base::debug::Alias(&image_rect_x); |
| base::debug::Alias(&image_rect_y); |
| base::debug::Alias(&image_rect_width); |
| base::debug::Alias(&image_rect_height); |
| base::debug::Alias(&dest_offset_x); |
| base::debug::Alias(&dest_offset_y); |
| |
| TRACE_EVENT0("cc", "TextureUploader::uploadWithMapTexSubImage"); |
| |
| // Offset from image-rect to source-rect. |
| gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); |
| |
| unsigned int bytes_per_pixel = Resource::BytesPerPixel(format); |
| // Use 4-byte row alignment (OpenGL default) for upload performance. |
| // Assuming that GL_UNPACK_ALIGNMENT has not changed from default. |
| unsigned int 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*>( |
| m_context->mapTexSubImage2DCHROMIUM(GL_TEXTURE_2D, |
| 0, |
| dest_offset.x(), |
| dest_offset.y(), |
| source_rect.width(), |
| source_rect.height(), |
| format, |
| GL_UNSIGNED_BYTE, |
| 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 pixelDest |
| 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); |
| } |
| |
| m_context->unmapTexSubImage2DCHROMIUM(pixel_dest); |
| } |
| |
| void TextureUploader::processQueries() |
| { |
| while (!m_pendingQueries.isEmpty()) { |
| if (m_pendingQueries.first()->isPending()) |
| break; |
| |
| unsigned usElapsed = m_pendingQueries.first()->value(); |
| HISTOGRAM_CUSTOM_COUNTS("Renderer4.TextureGpuUploadTimeUS", usElapsed, 0, 100000, 50); |
| |
| // Clamp the queries to saner values in case the queries fail. |
| usElapsed = std::max(1u, usElapsed); |
| usElapsed = std::min(15000u, usElapsed); |
| |
| if (!m_pendingQueries.first()->isNonBlocking()) |
| m_numBlockingTextureUploads--; |
| |
| // Remove the min and max value from our history and insert the new one. |
| double texturesPerSecond = 1.0 / (usElapsed * 1e-6); |
| if (m_texturesPerSecondHistory.size() >= uploadHistorySizeMax) { |
| m_texturesPerSecondHistory.erase(m_texturesPerSecondHistory.begin()); |
| m_texturesPerSecondHistory.erase(--m_texturesPerSecondHistory.end()); |
| } |
| m_texturesPerSecondHistory.insert(texturesPerSecond); |
| |
| m_availableQueries.append(m_pendingQueries.takeFirst()); |
| } |
| } |
| |
| } // namespace cc |