1 // Copyright 2015 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.
8 #include "base/containers/small_map.h"
9 #include "base/logging.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/stringprintf.h"
13 #include "gpu/perftests/measurements.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "testing/perf/perf_test.h"
17 #include "ui/gfx/geometry/size.h"
18 #include "ui/gfx/geometry/vector2d_f.h"
19 #include "ui/gl/gl_bindings.h"
20 #include "ui/gl/gl_context.h"
21 #include "ui/gl/gl_enums.h"
22 #include "ui/gl/gl_surface.h"
23 #include "ui/gl/gl_version_info.h"
24 #include "ui/gl/gpu_timing.h"
25 #include "ui/gl/scoped_make_current.h"
27 #if defined(USE_OZONE)
28 #include "base/message_loop/message_loop.h"
34 const int kUploadPerfWarmupRuns
= 5;
35 const int kUploadPerfTestRuns
= 30;
37 #define SHADER(Src) #Src
40 const char kVertexShader
[] =
42 uniform vec2 translation
;
43 attribute vec2 a_position
;
44 attribute vec2 a_texCoord
;
45 varying vec2 v_texCoord
;
48 translation
.x
+ a_position
.x
, translation
.y
+ a_position
.y
, 0.0, 1.0);
49 v_texCoord
= a_texCoord
;
52 const char kShaderDefaultFloatPrecision
[] =
54 precision mediump
float;
56 const char kFragmentShader
[] =
58 uniform sampler2D a_texture
;
59 varying vec2 v_texCoord
;
61 gl_FragColor
= texture2D(a_texture
, v_texCoord
);
66 void CheckNoGlError(const std::string
& msg
) {
67 CHECK_EQ(static_cast<GLenum
>(GL_NO_ERROR
), glGetError()) << " " << msg
;
70 // Utility function to compile a shader from a string.
71 GLuint
LoadShader(const GLenum type
, const char* const src
) {
73 shader
= glCreateShader(type
);
75 glShaderSource(shader
, 1, &src
, NULL
);
76 glCompileShader(shader
);
79 glGetShaderiv(shader
, GL_COMPILE_STATUS
, &compiled
);
82 glGetShaderiv(shader
, GL_INFO_LOG_LENGTH
, &len
);
84 scoped_ptr
<char[]> error_log(new char[len
]);
85 glGetShaderInfoLog(shader
, len
, NULL
, error_log
.get());
86 LOG(ERROR
) << "Error compiling shader: " << error_log
.get();
89 CHECK_NE(0, compiled
);
93 int GLFormatBytePerPixel(GLenum format
) {
94 DCHECK(format
== GL_RGBA
|| format
== GL_LUMINANCE
|| format
== GL_RED_EXT
);
95 return format
== GL_RGBA
? 4 : 1;
98 GLenum
GLFormatToInternalFormat(GLenum format
) {
99 return format
== GL_RED
? GL_R8
: format
;
102 GLenum
GLFormatToStorageFormat(GLenum format
) {
107 return GL_LUMINANCE8
;
116 void GenerateTextureData(const gfx::Size
& size
,
119 std::vector
<uint8
>* const pixels
) {
120 // Row bytes has to be multiple of 4 (GL_PACK_ALIGNMENT defaults to 4).
121 int stride
= ((size
.width() * bytes_per_pixel
) + 3) & ~0x3;
122 pixels
->resize(size
.height() * stride
);
123 for (int y
= 0; y
< size
.height(); ++y
) {
124 for (int x
= 0; x
< size
.width(); ++x
) {
125 for (int channel
= 0; channel
< bytes_per_pixel
; ++channel
) {
126 int index
= y
* stride
+ x
* bytes_per_pixel
;
127 pixels
->at(index
) = (index
+ (seed
<< 2)) % (0x20 << channel
);
133 // Compare a buffer containing pixels in a specified format to GL_RGBA buffer
134 // where the former buffer have been uploaded as a texture and drawn on the
136 bool CompareBufferToRGBABuffer(GLenum format
,
137 const gfx::Size
& size
,
138 const std::vector
<uint8
>& pixels
,
139 const std::vector
<uint8
>& rgba
) {
140 int bytes_per_pixel
= GLFormatBytePerPixel(format
);
141 int pixels_stride
= ((size
.width() * bytes_per_pixel
) + 3) & ~0x3;
142 int rgba_stride
= size
.width() * GLFormatBytePerPixel(GL_RGBA
);
143 for (int y
= 0; y
< size
.height(); ++y
) {
144 for (int x
= 0; x
< size
.width(); ++x
) {
145 int rgba_index
= y
* rgba_stride
+ x
* GLFormatBytePerPixel(GL_RGBA
);
146 int pixels_index
= y
* pixels_stride
+ x
* bytes_per_pixel
;
147 uint8 expected
[4] = {0};
149 case GL_LUMINANCE
: // (L_t, L_t, L_t, 1)
150 expected
[1] = pixels
[pixels_index
];
151 expected
[2] = pixels
[pixels_index
];
152 case GL_RED
: // (R_t, 0, 0, 1)
153 expected
[0] = pixels
[pixels_index
];
156 case GL_RGBA
: // (R_t, G_t, B_t, A_t)
157 memcpy(expected
, &pixels
[pixels_index
], 4);
162 if (memcmp(&rgba
[rgba_index
], expected
, 4)) {
170 // PerfTest to check costs of texture upload at different stages
171 // on different platforms.
172 class TextureUploadPerfTest
: public testing::Test
{
174 TextureUploadPerfTest() : fbo_size_(1024, 1024) {}
176 // Overridden from testing::Test
177 void SetUp() override
{
178 #if defined(USE_OZONE)
179 // On Ozone, the backend initializes the event system using a UI
181 base::MessageLoopForUI main_loop
;
183 static bool gl_initialized
= gfx::GLSurface::InitializeOneOff();
184 DCHECK(gl_initialized
);
185 // Initialize an offscreen surface and a gl context.
186 surface_
= gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size());
187 gl_context_
= gfx::GLContext::CreateGLContext(NULL
, // share_group
189 gfx::PreferIntegratedGpu
);
190 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
191 glGenTextures(1, &color_texture_
);
192 glBindTexture(GL_TEXTURE_2D
, color_texture_
);
193 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
194 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
195 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
196 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
197 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, fbo_size_
.width(),
198 fbo_size_
.height(), 0, GL_RGBA
, GL_UNSIGNED_BYTE
, nullptr);
200 glGenFramebuffersEXT(1, &framebuffer_object_
);
201 glBindFramebufferEXT(GL_FRAMEBUFFER
, framebuffer_object_
);
203 glFramebufferTexture2DEXT(GL_FRAMEBUFFER
, GL_COLOR_ATTACHMENT0
,
204 GL_TEXTURE_2D
, color_texture_
, 0);
205 DCHECK_EQ(static_cast<GLenum
>(GL_FRAMEBUFFER_COMPLETE
),
206 glCheckFramebufferStatusEXT(GL_FRAMEBUFFER
));
208 glViewport(0, 0, fbo_size_
.width(), fbo_size_
.height());
209 gpu_timing_client_
= gl_context_
->CreateGPUTimingClient();
211 if (gpu_timing_client_
->IsAvailable()) {
212 LOG(INFO
) << "Gpu timing initialized with timer type: "
213 << gpu_timing_client_
->GetTimerTypeName();
214 gpu_timing_client_
->InvalidateTimerOffset();
216 LOG(WARNING
) << "Can't initialize gpu timing";
218 // Prepare a simple program and a vertex buffer that will be
219 // used to draw a quad on the offscreen surface.
220 vertex_shader_
= LoadShader(GL_VERTEX_SHADER
, kVertexShader
);
222 bool is_gles
= gfx::GetGLImplementation() == gfx::kGLImplementationEGLGLES2
;
223 fragment_shader_
= LoadShader(
225 base::StringPrintf("%s%s", is_gles
? kShaderDefaultFloatPrecision
: "",
226 kFragmentShader
).c_str());
227 program_object_
= glCreateProgram();
228 CHECK_NE(0u, program_object_
);
230 glAttachShader(program_object_
, vertex_shader_
);
231 glAttachShader(program_object_
, fragment_shader_
);
232 glBindAttribLocation(program_object_
, 0, "a_position");
233 glBindAttribLocation(program_object_
, 1, "a_texCoord");
234 glLinkProgram(program_object_
);
237 glGetProgramiv(program_object_
, GL_LINK_STATUS
, &linked
);
239 glUseProgram(program_object_
);
240 glUniform1i(sampler_location_
, 0);
241 translation_location_
=
242 glGetUniformLocation(program_object_
, "translation");
243 DCHECK_NE(-1, translation_location_
);
244 glUniform2f(translation_location_
, 0.0f
, 0.0f
);
246 sampler_location_
= glGetUniformLocation(program_object_
, "a_texture");
247 CHECK_NE(-1, sampler_location_
);
249 glGenBuffersARB(1, &vertex_buffer_
);
250 CHECK_NE(0u, vertex_buffer_
);
251 DCHECK_NE(0u, vertex_buffer_
);
252 glBindBuffer(GL_ARRAY_BUFFER
, vertex_buffer_
);
253 glVertexAttribPointer(0, 2, GL_FLOAT
, GL_FALSE
, sizeof(GLfloat
) * 4, 0);
254 glVertexAttribPointer(1, 2, GL_FLOAT
, GL_FALSE
, sizeof(GLfloat
) * 4,
255 reinterpret_cast<void*>(sizeof(GLfloat
) * 2));
256 glEnableVertexAttribArray(0);
257 glEnableVertexAttribArray(1);
258 CheckNoGlError("glEnableVertexAttribArray");
260 has_texture_storage_
=
261 gl_context_
->GetVersionInfo()->is_es3
||
262 gl_context_
->HasExtension("GL_EXT_texture_storage") ||
263 gl_context_
->HasExtension("GL_ARB_texture_storage");
266 void GenerateVertexBuffer(const gfx::Size
& size
) {
267 DCHECK_NE(0u, vertex_buffer_
);
268 glBindBuffer(GL_ARRAY_BUFFER
, vertex_buffer_
);
269 // right and top are in clipspace
270 float right
= -1.f
+ 2.f
* size
.width() / fbo_size_
.width();
271 float top
= -1.f
+ 2.f
* size
.height() / fbo_size_
.height();
272 // Four vertexes, one per line. Each vertex has two components per
273 // position and two per texcoord.
274 // It represents a quad formed by two triangles if interpreted
279 -1.f
, -1.f
, 0.f
, 0.f
,
280 right
, -1.f
, 1.f
, 0.f
,
282 right
, top
, 1.f
, 1.f
};
284 glBufferData(GL_ARRAY_BUFFER
, sizeof(data
), data
, GL_STATIC_DRAW
);
285 CheckNoGlError("glBufferData");
288 void TearDown() override
{
289 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
290 glDeleteProgram(program_object_
);
291 glDeleteShader(vertex_shader_
);
292 glDeleteShader(fragment_shader_
);
293 glDeleteBuffersARB(1, &vertex_buffer_
);
295 glBindFramebufferEXT(GL_FRAMEBUFFER
, 0);
296 glDeleteFramebuffersEXT(1, &framebuffer_object_
);
297 glDeleteTextures(1, &color_texture_
);
298 CheckNoGlError("glDeleteTextures");
300 gpu_timing_client_
= nullptr;
301 gl_context_
= nullptr;
306 GLuint
CreateGLTexture(const GLenum format
,
307 const gfx::Size
& size
,
308 const bool specify_storage
) {
309 GLuint texture_id
= 0;
310 glActiveTexture(GL_TEXTURE0
);
311 glGenTextures(1, &texture_id
);
312 glBindTexture(GL_TEXTURE_2D
, texture_id
);
313 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
314 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
315 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
316 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
317 if (specify_storage
) {
318 if (has_texture_storage_
) {
319 glTexStorage2DEXT(GL_TEXTURE_2D
, 1, GLFormatToStorageFormat(format
),
320 size
.width(), size
.height());
321 CheckNoGlError("glTexStorage2DEXT");
323 glTexImage2D(GL_TEXTURE_2D
, 0, GLFormatToInternalFormat(format
),
324 size
.width(), size
.height(), 0, format
, GL_UNSIGNED_BYTE
,
326 CheckNoGlError("glTexImage2D");
332 void UploadTexture(GLuint texture_id
,
333 const gfx::Size
& size
,
334 const std::vector
<uint8
>& pixels
,
336 const bool subimage
) {
338 glTexSubImage2D(GL_TEXTURE_2D
, 0, 0, 0, size
.width(), size
.height(),
339 format
, GL_UNSIGNED_BYTE
, &pixels
[0]);
340 CheckNoGlError("glTexSubImage2D");
342 glTexImage2D(GL_TEXTURE_2D
, 0, GLFormatToInternalFormat(format
),
343 size
.width(), size
.height(), 0, format
, GL_UNSIGNED_BYTE
,
345 CheckNoGlError("glTexImage2D");
349 // Upload and draw on the offscren surface.
350 // Return a list of pair. Each pair describe a gl operation and the wall
351 // time elapsed in milliseconds.
352 std::vector
<Measurement
> UploadAndDraw(GLuint texture_id
,
353 const gfx::Size
& size
,
354 const std::vector
<uint8
>& pixels
,
356 const bool subimage
) {
357 MeasurementTimers
tex_timers(gpu_timing_client_
.get());
358 UploadTexture(texture_id
, size
, pixels
, format
, subimage
);
361 MeasurementTimers
first_draw_timers(gpu_timing_client_
.get());
362 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
363 first_draw_timers
.Record();
365 MeasurementTimers
draw_timers(gpu_timing_client_
.get());
366 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
367 draw_timers
.Record();
369 MeasurementTimers
finish_timers(gpu_timing_client_
.get());
371 CheckNoGlError("glFinish");
372 finish_timers
.Record();
374 std::vector
<uint8
> pixels_rendered(size
.GetArea() * 4);
375 glReadPixels(0, 0, size
.width(), size
.height(), GL_RGBA
, GL_UNSIGNED_BYTE
,
376 &pixels_rendered
[0]);
377 CheckNoGlError("glReadPixels");
379 CompareBufferToRGBABuffer(format
, size
, pixels
, pixels_rendered
))
380 << "Format is: " << gfx::GLEnums::GetStringEnum(format
);
382 std::vector
<Measurement
> measurements
;
383 bool gpu_timer_errors
=
384 gpu_timing_client_
->IsAvailable() &&
385 gpu_timing_client_
->CheckAndResetTimerErrors();
386 if (!gpu_timer_errors
) {
387 measurements
.push_back(tex_timers
.GetAsMeasurement(
388 subimage
? "texsubimage2d" : "teximage2d"));
389 measurements
.push_back(
390 first_draw_timers
.GetAsMeasurement("firstdrawarrays"));
391 measurements
.push_back(draw_timers
.GetAsMeasurement("drawarrays"));
392 measurements
.push_back(finish_timers
.GetAsMeasurement("finish"));
397 void RunUploadAndDrawMultipleTimes(const gfx::Size
& size
,
399 const bool subimage
) {
400 std::vector
<uint8
> pixels
;
401 base::SmallMap
<std::map
<std::string
, Measurement
>>
402 aggregates
; // indexed by name
403 int successful_runs
= 0;
404 GLuint texture_id
= CreateGLTexture(format
, size
, subimage
);
405 for (int i
= 0; i
< kUploadPerfWarmupRuns
+ kUploadPerfTestRuns
; ++i
) {
406 GenerateTextureData(size
, GLFormatBytePerPixel(format
), i
+ 1, &pixels
);
407 auto run
= UploadAndDraw(texture_id
, size
, pixels
, format
, subimage
);
408 if (i
< kUploadPerfWarmupRuns
|| !run
.size()) {
412 for (const Measurement
& measurement
: run
) {
413 auto& aggregate
= aggregates
[measurement
.name
];
414 aggregate
.name
= measurement
.name
;
415 aggregate
.Increment(measurement
);
418 glDeleteTextures(1, &texture_id
);
420 std::string graph_name
= base::StringPrintf(
421 "%d_%s", size
.width(), gfx::GLEnums::GetStringEnum(format
).c_str());
423 graph_name
+= "_sub";
426 if (successful_runs
) {
427 for (const auto& entry
: aggregates
) {
428 const auto m
= entry
.second
.Divide(successful_runs
);
429 m
.PrintResult(graph_name
);
432 perf_test::PrintResult("sample_runs", "", graph_name
,
433 static_cast<size_t>(successful_runs
), "laps", true);
436 const gfx::Size fbo_size_
; // for the fbo
437 scoped_refptr
<gfx::GLContext
> gl_context_
;
438 scoped_refptr
<gfx::GLSurface
> surface_
;
439 scoped_refptr
<gfx::GPUTimingClient
> gpu_timing_client_
;
441 GLuint color_texture_
= 0;
442 GLuint framebuffer_object_
= 0;
443 GLuint vertex_shader_
= 0;
444 GLuint fragment_shader_
= 0;
445 GLuint program_object_
= 0;
446 GLint sampler_location_
= -1;
447 GLint translation_location_
= -1;
448 GLuint vertex_buffer_
= 0;
450 bool has_texture_storage_
= false;
453 // Perf test that generates, uploads and draws a texture on a surface repeatedly
454 // and prints out aggregated measurements for all the runs.
455 TEST_F(TextureUploadPerfTest
, upload
) {
456 int sizes
[] = {21, 128, 256, 512, 1024};
457 std::vector
<GLenum
> formats
;
458 formats
.push_back(GL_RGBA
);
460 if (!gl_context_
->GetVersionInfo()->is_es3
) {
461 // Used by default for ResourceProvider::yuv_resource_format_.
462 formats
.push_back(GL_LUMINANCE
);
465 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
466 const bool has_texture_rg
= gl_context_
->GetVersionInfo()->is_es3
||
467 gl_context_
->HasExtension("GL_EXT_texture_rg") ||
468 gl_context_
->HasExtension("GL_ARB_texture_rg");
470 if (has_texture_rg
) {
471 // Used as ResourceProvider::yuv_resource_format_ if
472 // {ARB,EXT}_texture_rg are available.
473 formats
.push_back(GL_RED
);
476 for (int side
: sizes
) {
477 ASSERT_GE(fbo_size_
.width(), side
);
478 ASSERT_GE(fbo_size_
.height(), side
);
479 gfx::Size
size(side
, side
);
480 GenerateVertexBuffer(size
);
481 for (GLenum format
: formats
) {
482 RunUploadAndDrawMultipleTimes(size
, format
, true); // use glTexSubImage2D
483 RunUploadAndDrawMultipleTimes(size
, format
, false); // use glTexImage2D
488 // Perf test to check if the driver is doing texture renaming.
489 // This test creates one GL texture_id and four different images. For
490 // every image it uploads it using texture_id and it draws multiple
491 // times. The cpu/wall time and the gpu time for all the uploads and
492 // draws, but before glFinish, is computed and is printed out at the end as
493 // "upload_and_draw". If the gpu time is >> than the cpu/wall time we expect the
494 // driver to do texture renaming: this means that while the gpu is drawing using
495 // texture_id it didn't block cpu side the texture upload using the same
497 TEST_F(TextureUploadPerfTest
, renaming
) {
498 gfx::Size
texture_size(fbo_size_
.width() / 2, fbo_size_
.height() / 2);
500 std::vector
<uint8
> pixels
[4];
501 for (int i
= 0; i
< 4; ++i
) {
502 GenerateTextureData(texture_size
, 4, i
+ 1, &pixels
[i
]);
505 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
506 GenerateVertexBuffer(texture_size
);
508 gfx::Vector2dF positions
[] = {gfx::Vector2dF(0.f
, 0.f
),
509 gfx::Vector2dF(1.f
, 0.f
),
510 gfx::Vector2dF(0.f
, 1.f
),
511 gfx::Vector2dF(1.f
, 1.f
)};
512 GLuint texture_id
= CreateGLTexture(GL_RGBA
, texture_size
, true);
514 MeasurementTimers
upload_and_draw_timers(gpu_timing_client_
.get());
516 for (int i
= 0; i
< 4; ++i
) {
517 UploadTexture(texture_id
, texture_size
, pixels
[i
% 4], GL_RGBA
, true);
518 DCHECK_NE(-1, translation_location_
);
519 glUniform2f(translation_location_
, positions
[i
% 4].x(),
520 positions
[i
% 4].y());
521 // Draw the same quad multiple times to make sure that the time spent on the
522 // gpu is more than the cpu time.
523 for (int draw
= 0; draw
< 128; ++draw
) {
524 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
528 upload_and_draw_timers
.Record();
529 MeasurementTimers
finish_timers(gpu_timing_client_
.get());
531 CheckNoGlError("glFinish");
532 finish_timers
.Record();
534 glDeleteTextures(1, &texture_id
);
536 for (int i
= 0; i
< 4; ++i
) {
537 std::vector
<uint8
> pixels_rendered(texture_size
.GetArea() * 4);
538 glReadPixels(texture_size
.width() * positions
[i
].x(),
539 texture_size
.height() * positions
[i
].y(), texture_size
.width(),
540 texture_size
.height(), GL_RGBA
, GL_UNSIGNED_BYTE
,
541 &pixels_rendered
[0]);
542 CheckNoGlError("glReadPixels");
543 ASSERT_EQ(pixels
[i
].size(), pixels_rendered
.size());
544 EXPECT_EQ(pixels
[i
], pixels_rendered
);
547 bool gpu_timer_errors
= gpu_timing_client_
->IsAvailable() &&
548 gpu_timing_client_
->CheckAndResetTimerErrors();
549 if (!gpu_timer_errors
) {
550 upload_and_draw_timers
.GetAsMeasurement("upload_and_draw")
551 .PrintResult("renaming");
552 finish_timers
.GetAsMeasurement("finish").PrintResult("renaming");