blob: f25764bb837cc795937927cc412b1e8179b6ff4e [file] [log] [blame]
// 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 "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;
unsigned int RoundUp(unsigned int n, unsigned int mul)
{
return (((n - 1) / mul) * mul) + mul;
}
} // 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(), 4);
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(), 4);
// 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);
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