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