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/metrics/histogram.h"
11 #include "base/trace_event/trace_event.h"
12 #include "cc/base/util.h"
13 #include "cc/resources/resource.h"
14 #include "gpu/GLES2/gl2extchromium.h"
15 #include "gpu/command_buffer/client/gles2_interface.h"
16 #include "third_party/khronos/GLES2/gl2.h"
17 #include "third_party/khronos/GLES2/gl2ext.h"
18 #include "ui/gfx/geometry/rect.h"
19 #include "ui/gfx/geometry/vector2d.h"
21 using gpu::gles2::GLES2Interface
;
25 // How many previous uploads to use when predicting future throughput.
26 static const size_t kUploadHistorySizeMax
= 1000;
27 static const size_t kUploadHistorySizeInitial
= 100;
29 // Global estimated number of textures per second to maintain estimates across
30 // subsequent instances of TextureUploader.
31 // More than one thread will not access this variable, so we do not need to
32 // synchronize access.
33 static const double kDefaultEstimatedTexturesPerSecond
= 48.0 * 60.0;
35 // Flush interval when performing texture uploads.
36 static const size_t kTextureUploadFlushPeriod
= 4;
38 } // anonymous namespace
42 TextureUploader::Query::Query(GLES2Interface
* gl
)
47 is_non_blocking_(false) {
48 gl_
->GenQueriesEXT(1, &query_id_
);
51 TextureUploader::Query::~Query() { gl_
->DeleteQueriesEXT(1, &query_id_
); }
53 void TextureUploader::Query::Begin() {
55 is_non_blocking_
= false;
56 gl_
->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM
, query_id_
);
59 void TextureUploader::Query::End() {
60 gl_
->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM
);
63 bool TextureUploader::Query::IsPending() {
64 unsigned available
= 1;
65 gl_
->GetQueryObjectuivEXT(
66 query_id_
, GL_QUERY_RESULT_AVAILABLE_EXT
, &available
);
70 unsigned TextureUploader::Query::Value() {
72 gl_
->GetQueryObjectuivEXT(query_id_
, GL_QUERY_RESULT_EXT
, &value_
);
78 TextureUploader::TextureUploader(GLES2Interface
* gl
)
80 num_blocking_texture_uploads_(0),
82 num_texture_uploads_since_last_flush_(0) {
83 for (size_t i
= kUploadHistorySizeInitial
; i
> 0; i
--)
84 textures_per_second_history_
.insert(kDefaultEstimatedTexturesPerSecond
);
87 TextureUploader::~TextureUploader() {}
89 size_t TextureUploader::NumBlockingUploads() {
91 return num_blocking_texture_uploads_
;
94 void TextureUploader::MarkPendingUploadsAsNonBlocking() {
95 for (ScopedPtrDeque
<Query
>::iterator it
= pending_queries_
.begin();
96 it
!= pending_queries_
.end();
98 if ((*it
)->is_non_blocking())
101 num_blocking_texture_uploads_
--;
102 (*it
)->mark_as_non_blocking();
105 DCHECK(!num_blocking_texture_uploads_
);
108 double TextureUploader::EstimatedTexturesPerSecond() {
111 // Use the median as our estimate.
112 std::multiset
<double>::iterator median
= textures_per_second_history_
.begin();
113 std::advance(median
, textures_per_second_history_
.size() / 2);
117 void TextureUploader::BeginQuery() {
118 // Check to see if any of the pending queries are free before allocating a
119 // new one. If this is not done, queries may be allocated without bound.
120 // http://crbug.com/398072
121 if (available_queries_
.empty())
124 if (available_queries_
.empty())
125 available_queries_
.push_back(Query::Create(gl_
));
127 available_queries_
.front()->Begin();
130 void TextureUploader::EndQuery() {
131 available_queries_
.front()->End();
132 pending_queries_
.push_back(available_queries_
.take_front());
133 num_blocking_texture_uploads_
++;
136 void TextureUploader::Upload(const uint8
* image
,
137 const gfx::Rect
& image_rect
,
138 const gfx::Rect
& source_rect
,
139 gfx::Vector2d dest_offset
,
140 ResourceFormat format
,
141 const gfx::Size
& size
) {
142 CHECK(image_rect
.Contains(source_rect
));
144 bool is_full_upload
= dest_offset
.IsZero() && source_rect
.size() == size
;
149 UploadWithMapTexSubImage(image
, image_rect
, source_rect
, dest_offset
, format
);
154 num_texture_uploads_since_last_flush_
++;
155 if (num_texture_uploads_since_last_flush_
>= kTextureUploadFlushPeriod
)
159 void TextureUploader::Flush() {
160 if (!num_texture_uploads_since_last_flush_
)
163 gl_
->ShallowFlushCHROMIUM();
165 num_texture_uploads_since_last_flush_
= 0;
168 void TextureUploader::ReleaseCachedQueries() {
170 available_queries_
.clear();
173 void TextureUploader::UploadWithTexSubImage(const uint8
* image
,
174 const gfx::Rect
& image_rect
,
175 const gfx::Rect
& source_rect
,
176 gfx::Vector2d dest_offset
,
177 ResourceFormat format
) {
178 TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage");
180 // Early-out if this is a no-op, and assert that |image| be valid if this is
182 if (source_rect
.IsEmpty())
186 // Offset from image-rect to source-rect.
187 gfx::Vector2d
offset(source_rect
.origin() - image_rect
.origin());
189 const uint8
* pixel_source
;
190 unsigned bytes_per_pixel
= BitsPerPixel(format
) / 8;
191 // Use 4-byte row alignment (OpenGL default) for upload performance.
192 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
193 unsigned upload_image_stride
=
194 RoundUp(bytes_per_pixel
* source_rect
.width(), 4u);
196 if (upload_image_stride
== image_rect
.width() * bytes_per_pixel
&&
198 pixel_source
= &image
[image_rect
.width() * bytes_per_pixel
* offset
.y()];
200 size_t needed_size
= upload_image_stride
* source_rect
.height();
201 if (sub_image_size_
< needed_size
) {
202 sub_image_
.reset(new uint8
[needed_size
]);
203 sub_image_size_
= needed_size
;
205 // Strides not equal, so do a row-by-row memcpy from the
206 // paint results into a temp buffer for uploading.
207 for (int row
= 0; row
< source_rect
.height(); ++row
)
208 memcpy(&sub_image_
[upload_image_stride
* row
],
209 &image
[bytes_per_pixel
*
210 (offset
.x() + (offset
.y() + row
) * image_rect
.width())],
211 source_rect
.width() * bytes_per_pixel
);
213 pixel_source
= &sub_image_
[0];
216 gl_
->TexSubImage2D(GL_TEXTURE_2D
,
221 source_rect
.height(),
222 GLDataFormat(format
),
227 void TextureUploader::UploadWithMapTexSubImage(const uint8
* image
,
228 const gfx::Rect
& image_rect
,
229 const gfx::Rect
& source_rect
,
230 gfx::Vector2d dest_offset
,
231 ResourceFormat format
) {
232 TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage");
234 // Early-out if this is a no-op, and assert that |image| be valid if this is
236 if (source_rect
.IsEmpty())
239 // Compressed textures have no implementation of mapTexSubImage.
240 DCHECK_NE(ETC1
, format
);
242 // Offset from image-rect to source-rect.
243 gfx::Vector2d
offset(source_rect
.origin() - image_rect
.origin());
245 unsigned bytes_per_pixel
= BitsPerPixel(format
) / 8;
246 // Use 4-byte row alignment (OpenGL default) for upload performance.
247 // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
248 unsigned upload_image_stride
=
249 RoundUp(bytes_per_pixel
* source_rect
.width(), 4u);
251 // Upload tile data via a mapped transfer buffer
253 static_cast<uint8
*>(gl_
->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D
,
258 source_rect
.height(),
259 GLDataFormat(format
),
264 UploadWithTexSubImage(image
, image_rect
, source_rect
, dest_offset
, format
);
268 if (upload_image_stride
== image_rect
.width() * bytes_per_pixel
&&
271 &image
[image_rect
.width() * bytes_per_pixel
* offset
.y()],
272 source_rect
.height() * image_rect
.width() * bytes_per_pixel
);
274 // Strides not equal, so do a row-by-row memcpy from the
275 // paint results into the pixel_dest.
276 for (int row
= 0; row
< source_rect
.height(); ++row
) {
277 memcpy(&pixel_dest
[upload_image_stride
* row
],
278 &image
[bytes_per_pixel
*
279 (offset
.x() + (offset
.y() + row
) * image_rect
.width())],
280 source_rect
.width() * bytes_per_pixel
);
284 gl_
->UnmapTexSubImage2DCHROMIUM(pixel_dest
);
287 void TextureUploader::ProcessQueries() {
288 while (!pending_queries_
.empty()) {
289 if (pending_queries_
.front()->IsPending())
292 unsigned us_elapsed
= pending_queries_
.front()->Value();
293 UMA_HISTOGRAM_CUSTOM_COUNTS(
294 "Renderer4.TextureGpuUploadTimeUS", us_elapsed
, 0, 100000, 50);
296 // Clamp the queries to saner values in case the queries fail.
297 us_elapsed
= std::max(1u, us_elapsed
);
298 us_elapsed
= std::min(15000u, us_elapsed
);
300 if (!pending_queries_
.front()->is_non_blocking())
301 num_blocking_texture_uploads_
--;
303 // Remove the min and max value from our history and insert the new one.
304 double textures_per_second
= 1.0 / (us_elapsed
* 1e-6);
305 if (textures_per_second_history_
.size() >= kUploadHistorySizeMax
) {
306 textures_per_second_history_
.erase(textures_per_second_history_
.begin());
307 textures_per_second_history_
.erase(--textures_per_second_history_
.end());
309 textures_per_second_history_
.insert(textures_per_second
);
311 available_queries_
.push_back(pending_queries_
.take_front());