1 // Copyright 2012 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/scheduler/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
22 using gpu::gles2::GLES2Interface;
23
24 namespace {
25
26 // How many previous uploads to use when predicting future throughput.
27 static const size_t kUploadHistorySizeMax = 1000;
28 static 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.
34 static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0;
35
36 // Flush interval when performing texture uploads.
37 static const size_t kTextureUploadFlushPeriod = 4;
38
39 } // anonymous namespace
40
41 namespace cc {
42
Query(GLES2Interface * gl)43 TextureUploader::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
~Query()52 TextureUploader::Query::~Query() { gl_->DeleteQueriesEXT(1, &query_id_); }
53
Begin()54 void TextureUploader::Query::Begin() {
55 has_value_ = false;
56 is_non_blocking_ = false;
57 gl_->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_);
58 }
59
End()60 void TextureUploader::Query::End() {
61 gl_->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM);
62 }
63
IsPending()64 bool TextureUploader::Query::IsPending() {
65 unsigned available = 1;
66 gl_->GetQueryObjectuivEXT(
67 query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
68 return !available;
69 }
70
Value()71 unsigned 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
TextureUploader(GLES2Interface * gl)79 TextureUploader::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
~TextureUploader()88 TextureUploader::~TextureUploader() {}
89
NumBlockingUploads()90 size_t TextureUploader::NumBlockingUploads() {
91 ProcessQueries();
92 return num_blocking_texture_uploads_;
93 }
94
MarkPendingUploadsAsNonBlocking()95 void 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
EstimatedTexturesPerSecond()109 double 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
BeginQuery()118 void TextureUploader::BeginQuery() {
119 if (available_queries_.empty())
120 available_queries_.push_back(Query::Create(gl_));
121
122 available_queries_.front()->Begin();
123 }
124
EndQuery()125 void TextureUploader::EndQuery() {
126 available_queries_.front()->End();
127 pending_queries_.push_back(available_queries_.take_front());
128 num_blocking_texture_uploads_++;
129 }
130
Upload(const uint8 * image,gfx::Rect image_rect,gfx::Rect source_rect,gfx::Vector2d dest_offset,ResourceFormat format,gfx::Size size)131 void TextureUploader::Upload(const uint8* image,
132 gfx::Rect image_rect,
133 gfx::Rect source_rect,
134 gfx::Vector2d dest_offset,
135 ResourceFormat format,
136 gfx::Size size) {
137 CHECK(image_rect.Contains(source_rect));
138
139 bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size;
140
141 if (is_full_upload)
142 BeginQuery();
143
144 if (format == ETC1) {
145 // ETC1 does not support subimage uploads.
146 DCHECK(is_full_upload);
147 UploadWithTexImageETC1(image, size);
148 } else {
149 UploadWithMapTexSubImage(
150 image, image_rect, source_rect, dest_offset, format);
151 }
152
153 if (is_full_upload)
154 EndQuery();
155
156 num_texture_uploads_since_last_flush_++;
157 if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod)
158 Flush();
159 }
160
Flush()161 void TextureUploader::Flush() {
162 if (!num_texture_uploads_since_last_flush_)
163 return;
164
165 gl_->ShallowFlushCHROMIUM();
166
167 num_texture_uploads_since_last_flush_ = 0;
168 }
169
ReleaseCachedQueries()170 void TextureUploader::ReleaseCachedQueries() {
171 ProcessQueries();
172 available_queries_.clear();
173 }
174
UploadWithTexSubImage(const uint8 * image,gfx::Rect image_rect,gfx::Rect source_rect,gfx::Vector2d dest_offset,ResourceFormat format)175 void TextureUploader::UploadWithTexSubImage(const uint8* image,
176 gfx::Rect image_rect,
177 gfx::Rect source_rect,
178 gfx::Vector2d dest_offset,
179 ResourceFormat format) {
180 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage");
181
182 // Early-out if this is a no-op, and assert that |image| be valid if this is
183 // not a no-op.
184 if (source_rect.IsEmpty())
185 return;
186 DCHECK(image);
187
188 // Offset from image-rect to source-rect.
189 gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
190
191 const uint8* pixel_source;
192 unsigned bytes_per_pixel = BitsPerPixel(format) / 8;
193 // Use 4-byte row alignment (OpenGL default) for upload performance.
194 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
195 unsigned upload_image_stride =
196 RoundUp(bytes_per_pixel * source_rect.width(), 4u);
197
198 if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
199 !offset.x()) {
200 pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()];
201 } else {
202 size_t needed_size = upload_image_stride * source_rect.height();
203 if (sub_image_size_ < needed_size) {
204 sub_image_.reset(new uint8[needed_size]);
205 sub_image_size_ = needed_size;
206 }
207 // Strides not equal, so do a row-by-row memcpy from the
208 // paint results into a temp buffer for uploading.
209 for (int row = 0; row < source_rect.height(); ++row)
210 memcpy(&sub_image_[upload_image_stride * row],
211 &image[bytes_per_pixel *
212 (offset.x() + (offset.y() + row) * image_rect.width())],
213 source_rect.width() * bytes_per_pixel);
214
215 pixel_source = &sub_image_[0];
216 }
217
218 gl_->TexSubImage2D(GL_TEXTURE_2D,
219 0,
220 dest_offset.x(),
221 dest_offset.y(),
222 source_rect.width(),
223 source_rect.height(),
224 GLDataFormat(format),
225 GLDataType(format),
226 pixel_source);
227 }
228
UploadWithMapTexSubImage(const uint8 * image,gfx::Rect image_rect,gfx::Rect source_rect,gfx::Vector2d dest_offset,ResourceFormat format)229 void TextureUploader::UploadWithMapTexSubImage(const uint8* image,
230 gfx::Rect image_rect,
231 gfx::Rect source_rect,
232 gfx::Vector2d dest_offset,
233 ResourceFormat format) {
234 TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage");
235
236 // Early-out if this is a no-op, and assert that |image| be valid if this is
237 // not a no-op.
238 if (source_rect.IsEmpty())
239 return;
240 DCHECK(image);
241 // Compressed textures have no implementation of mapTexSubImage.
242 DCHECK_NE(ETC1, format);
243
244 // Offset from image-rect to source-rect.
245 gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
246
247 unsigned bytes_per_pixel = BitsPerPixel(format) / 8;
248 // Use 4-byte row alignment (OpenGL default) for upload performance.
249 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
250 unsigned upload_image_stride =
251 RoundUp(bytes_per_pixel * source_rect.width(), 4u);
252
253 // Upload tile data via a mapped transfer buffer
254 uint8* pixel_dest =
255 static_cast<uint8*>(gl_->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
256 0,
257 dest_offset.x(),
258 dest_offset.y(),
259 source_rect.width(),
260 source_rect.height(),
261 GLDataFormat(format),
262 GLDataType(format),
263 GL_WRITE_ONLY));
264
265 if (!pixel_dest) {
266 UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
267 return;
268 }
269
270 if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
271 !offset.x()) {
272 memcpy(pixel_dest,
273 &image[image_rect.width() * bytes_per_pixel * offset.y()],
274 source_rect.height() * image_rect.width() * bytes_per_pixel);
275 } else {
276 // Strides not equal, so do a row-by-row memcpy from the
277 // paint results into the pixel_dest.
278 for (int row = 0; row < source_rect.height(); ++row) {
279 memcpy(&pixel_dest[upload_image_stride * row],
280 &image[bytes_per_pixel *
281 (offset.x() + (offset.y() + row) * image_rect.width())],
282 source_rect.width() * bytes_per_pixel);
283 }
284 }
285
286 gl_->UnmapTexSubImage2DCHROMIUM(pixel_dest);
287 }
288
UploadWithTexImageETC1(const uint8 * image,gfx::Size size)289 void TextureUploader::UploadWithTexImageETC1(const uint8* image,
290 gfx::Size size) {
291 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexImageETC1");
292 DCHECK_EQ(0, size.width() % 4);
293 DCHECK_EQ(0, size.height() % 4);
294
295 gl_->CompressedTexImage2D(GL_TEXTURE_2D,
296 0,
297 GLInternalFormat(ETC1),
298 size.width(),
299 size.height(),
300 0,
301 Resource::MemorySizeBytes(size, ETC1),
302 image);
303 }
304
ProcessQueries()305 void TextureUploader::ProcessQueries() {
306 while (!pending_queries_.empty()) {
307 if (pending_queries_.front()->IsPending())
308 break;
309
310 unsigned us_elapsed = pending_queries_.front()->Value();
311 UMA_HISTOGRAM_CUSTOM_COUNTS(
312 "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50);
313
314 // Clamp the queries to saner values in case the queries fail.
315 us_elapsed = std::max(1u, us_elapsed);
316 us_elapsed = std::min(15000u, us_elapsed);
317
318 if (!pending_queries_.front()->is_non_blocking())
319 num_blocking_texture_uploads_--;
320
321 // Remove the min and max value from our history and insert the new one.
322 double textures_per_second = 1.0 / (us_elapsed * 1e-6);
323 if (textures_per_second_history_.size() >= kUploadHistorySizeMax) {
324 textures_per_second_history_.erase(textures_per_second_history_.begin());
325 textures_per_second_history_.erase(--textures_per_second_history_.end());
326 }
327 textures_per_second_history_.insert(textures_per_second);
328
329 available_queries_.push_back(pending_queries_.take_front());
330 }
331 }
332
333 } // namespace cc
334