OLD | NEW |
| (Empty) |
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/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" | |
20 | |
21 using gpu::gles2::GLES2Interface; | |
22 | |
23 namespace { | |
24 | |
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; | |
28 | |
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; | |
34 | |
35 // Flush interval when performing texture uploads. | |
36 static const size_t kTextureUploadFlushPeriod = 4; | |
37 | |
38 } // anonymous namespace | |
39 | |
40 namespace cc { | |
41 | |
42 TextureUploader::Query::Query(GLES2Interface* gl) | |
43 : gl_(gl), | |
44 query_id_(0), | |
45 value_(0), | |
46 has_value_(false), | |
47 is_non_blocking_(false) { | |
48 gl_->GenQueriesEXT(1, &query_id_); | |
49 } | |
50 | |
51 TextureUploader::Query::~Query() { gl_->DeleteQueriesEXT(1, &query_id_); } | |
52 | |
53 void TextureUploader::Query::Begin() { | |
54 has_value_ = false; | |
55 is_non_blocking_ = false; | |
56 gl_->BeginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_); | |
57 } | |
58 | |
59 void TextureUploader::Query::End() { | |
60 gl_->EndQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM); | |
61 } | |
62 | |
63 bool TextureUploader::Query::IsPending() { | |
64 unsigned available = 1; | |
65 gl_->GetQueryObjectuivEXT( | |
66 query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available); | |
67 return !available; | |
68 } | |
69 | |
70 unsigned TextureUploader::Query::Value() { | |
71 if (!has_value_) { | |
72 gl_->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &value_); | |
73 has_value_ = true; | |
74 } | |
75 return value_; | |
76 } | |
77 | |
78 TextureUploader::TextureUploader(GLES2Interface* gl) | |
79 : gl_(gl), | |
80 num_blocking_texture_uploads_(0), | |
81 sub_image_size_(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); | |
85 } | |
86 | |
87 TextureUploader::~TextureUploader() {} | |
88 | |
89 size_t TextureUploader::NumBlockingUploads() { | |
90 ProcessQueries(); | |
91 return num_blocking_texture_uploads_; | |
92 } | |
93 | |
94 void TextureUploader::MarkPendingUploadsAsNonBlocking() { | |
95 for (ScopedPtrDeque<Query>::iterator it = pending_queries_.begin(); | |
96 it != pending_queries_.end(); | |
97 ++it) { | |
98 if ((*it)->is_non_blocking()) | |
99 continue; | |
100 | |
101 num_blocking_texture_uploads_--; | |
102 (*it)->mark_as_non_blocking(); | |
103 } | |
104 | |
105 DCHECK(!num_blocking_texture_uploads_); | |
106 } | |
107 | |
108 double TextureUploader::EstimatedTexturesPerSecond() { | |
109 ProcessQueries(); | |
110 | |
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); | |
114 return *median; | |
115 } | |
116 | |
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()) | |
122 ProcessQueries(); | |
123 | |
124 if (available_queries_.empty()) | |
125 available_queries_.push_back(Query::Create(gl_)); | |
126 | |
127 available_queries_.front()->Begin(); | |
128 } | |
129 | |
130 void TextureUploader::EndQuery() { | |
131 available_queries_.front()->End(); | |
132 pending_queries_.push_back(available_queries_.take_front()); | |
133 num_blocking_texture_uploads_++; | |
134 } | |
135 | |
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)); | |
143 | |
144 bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size; | |
145 | |
146 if (is_full_upload) | |
147 BeginQuery(); | |
148 | |
149 UploadWithMapTexSubImage(image, image_rect, source_rect, dest_offset, format); | |
150 | |
151 if (is_full_upload) | |
152 EndQuery(); | |
153 | |
154 num_texture_uploads_since_last_flush_++; | |
155 if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod) | |
156 Flush(); | |
157 } | |
158 | |
159 void TextureUploader::Flush() { | |
160 if (!num_texture_uploads_since_last_flush_) | |
161 return; | |
162 | |
163 gl_->ShallowFlushCHROMIUM(); | |
164 | |
165 num_texture_uploads_since_last_flush_ = 0; | |
166 } | |
167 | |
168 void TextureUploader::ReleaseCachedQueries() { | |
169 ProcessQueries(); | |
170 available_queries_.clear(); | |
171 } | |
172 | |
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"); | |
179 | |
180 // Early-out if this is a no-op, and assert that |image| be valid if this is | |
181 // not a no-op. | |
182 if (source_rect.IsEmpty()) | |
183 return; | |
184 DCHECK(image); | |
185 | |
186 // Offset from image-rect to source-rect. | |
187 gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); | |
188 | |
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); | |
195 | |
196 if (upload_image_stride == image_rect.width() * bytes_per_pixel && | |
197 !offset.x()) { | |
198 pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()]; | |
199 } else { | |
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; | |
204 } | |
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); | |
212 | |
213 pixel_source = &sub_image_[0]; | |
214 } | |
215 | |
216 gl_->TexSubImage2D(GL_TEXTURE_2D, | |
217 0, | |
218 dest_offset.x(), | |
219 dest_offset.y(), | |
220 source_rect.width(), | |
221 source_rect.height(), | |
222 GLDataFormat(format), | |
223 GLDataType(format), | |
224 pixel_source); | |
225 } | |
226 | |
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"); | |
233 | |
234 // Early-out if this is a no-op, and assert that |image| be valid if this is | |
235 // not a no-op. | |
236 if (source_rect.IsEmpty()) | |
237 return; | |
238 DCHECK(image); | |
239 // Compressed textures have no implementation of mapTexSubImage. | |
240 DCHECK_NE(ETC1, format); | |
241 | |
242 // Offset from image-rect to source-rect. | |
243 gfx::Vector2d offset(source_rect.origin() - image_rect.origin()); | |
244 | |
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); | |
250 | |
251 // Upload tile data via a mapped transfer buffer | |
252 uint8* pixel_dest = | |
253 static_cast<uint8*>(gl_->MapTexSubImage2DCHROMIUM(GL_TEXTURE_2D, | |
254 0, | |
255 dest_offset.x(), | |
256 dest_offset.y(), | |
257 source_rect.width(), | |
258 source_rect.height(), | |
259 GLDataFormat(format), | |
260 GLDataType(format), | |
261 GL_WRITE_ONLY)); | |
262 | |
263 if (!pixel_dest) { | |
264 UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format); | |
265 return; | |
266 } | |
267 | |
268 if (upload_image_stride == image_rect.width() * bytes_per_pixel && | |
269 !offset.x()) { | |
270 memcpy(pixel_dest, | |
271 &image[image_rect.width() * bytes_per_pixel * offset.y()], | |
272 source_rect.height() * image_rect.width() * bytes_per_pixel); | |
273 } else { | |
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); | |
281 } | |
282 } | |
283 | |
284 gl_->UnmapTexSubImage2DCHROMIUM(pixel_dest); | |
285 } | |
286 | |
287 void TextureUploader::ProcessQueries() { | |
288 while (!pending_queries_.empty()) { | |
289 if (pending_queries_.front()->IsPending()) | |
290 break; | |
291 | |
292 unsigned us_elapsed = pending_queries_.front()->Value(); | |
293 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
294 "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50); | |
295 | |
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); | |
299 | |
300 if (!pending_queries_.front()->is_non_blocking()) | |
301 num_blocking_texture_uploads_--; | |
302 | |
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()); | |
308 } | |
309 textures_per_second_history_.insert(textures_per_second); | |
310 | |
311 available_queries_.push_back(pending_queries_.take_front()); | |
312 } | |
313 } | |
314 | |
315 } // namespace cc | |
OLD | NEW |