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.
5 #include "cc/resources/texture_uploader.h"
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"
22 using gpu::gles2::GLES2Interface
;
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;
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;
36 // Flush interval when performing texture uploads.
37 static const size_t kTextureUploadFlushPeriod
= 4;
39 } // anonymous namespace
43 TextureUploader::Query::Query(GLES2Interface
* gl
)
48 is_non_blocking_(false) {
49 gl_
->GenQueriesEXT(1, &query_id_
);
52 TextureUploader::Query::~Query() { gl_
->DeleteQueriesEXT(1, &query_id_
); }
54 void TextureUploader::Query::Begin() {
56 is_non_blocking_
= false;
57 gl_
->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM
, query_id_
);
60 void TextureUploader::Query::End() {
61 gl_
->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM
);
64 bool TextureUploader::Query::IsPending() {
65 unsigned available
= 1;
66 gl_
->GetQueryObjectuivEXT(
67 query_id_
, GL_QUERY_RESULT_AVAILABLE_EXT
, &available
);
71 unsigned TextureUploader::Query::Value() {
73 gl_
->GetQueryObjectuivEXT(query_id_
, GL_QUERY_RESULT_EXT
, &value_
);
79 TextureUploader::TextureUploader(GLES2Interface
* gl
)
81 num_blocking_texture_uploads_(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
);
88 TextureUploader::~TextureUploader() {}
90 size_t TextureUploader::NumBlockingUploads() {
92 return num_blocking_texture_uploads_
;
95 void TextureUploader::MarkPendingUploadsAsNonBlocking() {
96 for (ScopedPtrDeque
<Query
>::iterator it
= pending_queries_
.begin();
97 it
!= pending_queries_
.end();
99 if ((*it
)->is_non_blocking())
102 num_blocking_texture_uploads_
--;
103 (*it
)->mark_as_non_blocking();
106 DCHECK(!num_blocking_texture_uploads_
);
109 double TextureUploader::EstimatedTexturesPerSecond() {
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);
118 void TextureUploader::BeginQuery() {
119 if (available_queries_
.empty())
120 available_queries_
.push_back(Query::Create(gl_
));
122 available_queries_
.front()->Begin();
125 void TextureUploader::EndQuery() {
126 available_queries_
.front()->End();
127 pending_queries_
.push_back(available_queries_
.take_front());
128 num_blocking_texture_uploads_
++;
131 void TextureUploader::Upload(const uint8
* image
,
132 const gfx::Rect
& image_rect
,
133 const gfx::Rect
& source_rect
,
134 gfx::Vector2d dest_offset
,
135 ResourceFormat format
,
136 const gfx::Size
& size
) {
137 CHECK(image_rect
.Contains(source_rect
));
139 bool is_full_upload
= dest_offset
.IsZero() && source_rect
.size() == size
;
144 if (format
== ETC1
) {
145 // ETC1 does not support subimage uploads.
146 DCHECK(is_full_upload
);
147 UploadWithTexImageETC1(image
, size
);
149 UploadWithMapTexSubImage(
150 image
, image_rect
, source_rect
, dest_offset
, format
);
156 num_texture_uploads_since_last_flush_
++;
157 if (num_texture_uploads_since_last_flush_
>= kTextureUploadFlushPeriod
)
161 void TextureUploader::Flush() {
162 if (!num_texture_uploads_since_last_flush_
)
165 gl_
->ShallowFlushCHROMIUM();
167 num_texture_uploads_since_last_flush_
= 0;
170 void TextureUploader::ReleaseCachedQueries() {
172 available_queries_
.clear();
175 void TextureUploader::UploadWithTexSubImage(const uint8
* image
,
176 const gfx::Rect
& image_rect
,
177 const gfx::Rect
& source_rect
,
178 gfx::Vector2d dest_offset
,
179 ResourceFormat format
) {
180 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage");
182 // Early-out if this is a no-op, and assert that |image| be valid if this is
184 if (source_rect
.IsEmpty())
188 // Offset from image-rect to source-rect.
189 gfx::Vector2d
offset(source_rect
.origin() - image_rect
.origin());
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);
198 if (upload_image_stride
== image_rect
.width() * bytes_per_pixel
&&
200 pixel_source
= &image
[image_rect
.width() * bytes_per_pixel
* offset
.y()];
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
;
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
);
215 pixel_source
= &sub_image_
[0];
218 gl_
->TexSubImage2D(GL_TEXTURE_2D
,
223 source_rect
.height(),
224 GLDataFormat(format
),
229 void TextureUploader::UploadWithMapTexSubImage(const uint8
* image
,
230 const gfx::Rect
& image_rect
,
231 const gfx::Rect
& source_rect
,
232 gfx::Vector2d dest_offset
,
233 ResourceFormat format
) {
234 TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage");
236 // Early-out if this is a no-op, and assert that |image| be valid if this is
238 if (source_rect
.IsEmpty())
241 // Compressed textures have no implementation of mapTexSubImage.
242 DCHECK_NE(ETC1
, format
);
244 // Offset from image-rect to source-rect.
245 gfx::Vector2d
offset(source_rect
.origin() - image_rect
.origin());
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);
253 // Upload tile data via a mapped transfer buffer
255 static_cast<uint8
*>(gl_
->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D
,
260 source_rect
.height(),
261 GLDataFormat(format
),
266 UploadWithTexSubImage(image
, image_rect
, source_rect
, dest_offset
, format
);
270 if (upload_image_stride
== image_rect
.width() * bytes_per_pixel
&&
273 &image
[image_rect
.width() * bytes_per_pixel
* offset
.y()],
274 source_rect
.height() * image_rect
.width() * bytes_per_pixel
);
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
);
286 gl_
->UnmapTexSubImage2DCHROMIUM(pixel_dest
);
289 void TextureUploader::UploadWithTexImageETC1(const uint8
* image
,
290 const gfx::Size
& size
) {
291 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexImageETC1");
292 DCHECK_EQ(0, size
.width() % 4);
293 DCHECK_EQ(0, size
.height() % 4);
295 gl_
->CompressedTexImage2D(GL_TEXTURE_2D
,
297 GLInternalFormat(ETC1
),
301 Resource::MemorySizeBytes(size
, ETC1
),
305 void TextureUploader::ProcessQueries() {
306 while (!pending_queries_
.empty()) {
307 if (pending_queries_
.front()->IsPending())
310 unsigned us_elapsed
= pending_queries_
.front()->Value();
311 UMA_HISTOGRAM_CUSTOM_COUNTS(
312 "Renderer4.TextureGpuUploadTimeUS", us_elapsed
, 0, 100000, 50);
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
);
318 if (!pending_queries_
.front()->is_non_blocking())
319 num_blocking_texture_uploads_
--;
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());
327 textures_per_second_history_
.insert(textures_per_second
);
329 available_queries_
.push_back(pending_queries_
.take_front());