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/gtest/include/gtest/gtest.h"
15 #include "testing/perf/perf_test.h"
16 #include "ui/gfx/geometry/size.h"
17 #include "ui/gl/gl_bindings.h"
18 #include "ui/gl/gl_context.h"
19 #include "ui/gl/gl_surface.h"
20 #include "ui/gl/gpu_timing.h"
21 #include "ui/gl/scoped_make_current.h"
26 const int kUploadPerfWarmupRuns
= 10;
27 const int kUploadPerfTestRuns
= 100;
29 #define SHADER(Src) #Src
32 const char kVertexShader
[] =
34 attribute vec2 a_position
;
35 attribute vec2 a_texCoord
;
36 varying vec2 v_texCoord
;
38 gl_Position
= vec4(a_position
.x
, a_position
.y
, 0.0, 1.0);
39 v_texCoord
= a_texCoord
;
42 const char kFragmentShader
[] =
44 uniform sampler2D a_texture
;
45 varying vec2 v_texCoord
;
47 gl_FragColor
= texture2D(a_texture
, v_texCoord
);
52 void CheckNoGlError() {
53 CHECK_EQ(static_cast<GLenum
>(GL_NO_ERROR
), glGetError());
56 // Utility function to compile a shader from a string.
57 GLuint
LoadShader(const GLenum type
, const char* const src
) {
59 shader
= glCreateShader(type
);
61 glShaderSource(shader
, 1, &src
, NULL
);
62 glCompileShader(shader
);
65 glGetShaderiv(shader
, GL_COMPILE_STATUS
, &compiled
);
68 glGetShaderiv(shader
, GL_INFO_LOG_LENGTH
, &len
);
70 scoped_ptr
<char> error_log(new char[len
]);
71 glGetShaderInfoLog(shader
, len
, NULL
, error_log
.get());
72 LOG(ERROR
) << "Error compiling shader: " << error_log
.get();
75 CHECK_NE(0, compiled
);
79 void GenerateTextureData(const gfx::Size
& size
,
81 std::vector
<uint8
>* const pixels
) {
82 pixels
->resize(size
.GetArea() * 4);
83 for (int y
= 0; y
< size
.height(); ++y
) {
84 for (int x
= 0; x
< size
.width(); ++x
) {
85 const size_t offset
= (y
* size
.width() + x
) * 4;
86 pixels
->at(offset
) = (y
+ seed
) % 64;
87 pixels
->at(offset
+ 1) = (x
+ seed
) % 128;
88 pixels
->at(offset
+ 2) = (y
+ x
+ seed
) % 256;
89 pixels
->at(offset
+ 3) = 255;
94 // PerfTest to check costs of texture upload at different stages
95 // on different platforms.
96 class TextureUploadPerfTest
: public testing::Test
{
98 TextureUploadPerfTest() : fbo_size_(1024, 1024) {}
100 // Overridden from testing::Test
101 void SetUp() override
{
102 // Initialize an offscreen surface and a gl context.
103 gfx::GLSurface::InitializeOneOff();
104 surface_
= gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(4, 4));
105 gl_context_
= gfx::GLContext::CreateGLContext(NULL
, // share_group
107 gfx::PreferIntegratedGpu
);
108 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
109 glGenTextures(1, &color_texture_
);
110 glBindTexture(GL_TEXTURE_2D
, color_texture_
);
111 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
112 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
113 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
114 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
115 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, fbo_size_
.width(),
116 fbo_size_
.height(), 0, GL_RGBA
, GL_UNSIGNED_BYTE
, nullptr);
118 glGenFramebuffersEXT(1, &framebuffer_object_
);
119 glBindFramebufferEXT(GL_FRAMEBUFFER
, framebuffer_object_
);
121 glFramebufferTexture2DEXT(GL_FRAMEBUFFER
, GL_COLOR_ATTACHMENT0
,
122 GL_TEXTURE_2D
, color_texture_
, 0);
123 DCHECK_EQ(static_cast<GLenum
>(GL_FRAMEBUFFER_COMPLETE
),
124 glCheckFramebufferStatusEXT(GL_FRAMEBUFFER
));
126 glViewport(0, 0, fbo_size_
.width(), fbo_size_
.height());
127 gpu_timing_client_
= gl_context_
->CreateGPUTimingClient();
129 if (gpu_timing_client_
->IsAvailable()) {
130 LOG(INFO
) << "Gpu timing initialized with timer type: "
131 << gpu_timing_client_
->GetTimerTypeName();
132 gpu_timing_client_
->InvalidateTimerOffset();
134 LOG(WARNING
) << "Can't initialize gpu timing";
136 // Prepare a simple program and a vertex buffer that will be
137 // used to draw a quad on the offscreen surface.
138 vertex_shader_
= LoadShader(GL_VERTEX_SHADER
, kVertexShader
);
139 fragment_shader_
= LoadShader(GL_FRAGMENT_SHADER
, kFragmentShader
);
140 program_object_
= glCreateProgram();
141 CHECK_NE(0u, program_object_
);
143 glAttachShader(program_object_
, vertex_shader_
);
144 glAttachShader(program_object_
, fragment_shader_
);
145 glBindAttribLocation(program_object_
, 0, "a_position");
146 glBindAttribLocation(program_object_
, 1, "a_texCoord");
147 glLinkProgram(program_object_
);
150 glGetProgramiv(program_object_
, GL_LINK_STATUS
, &linked
);
153 sampler_location_
= glGetUniformLocation(program_object_
, "a_texture");
154 CHECK_NE(-1, sampler_location_
);
156 glGenBuffersARB(1, &vertex_buffer_
);
157 CHECK_NE(0u, vertex_buffer_
);
161 void GenerateVertexBuffer(const gfx::Size
& size
) {
162 DCHECK_NE(0u, vertex_buffer_
);
163 glBindBuffer(GL_ARRAY_BUFFER
, vertex_buffer_
);
164 // right and top are in clipspace
165 float right
= -1.f
+ 2.f
* size
.width() / fbo_size_
.width();
166 float top
= -1.f
+ 2.f
* size
.height() / fbo_size_
.height();
167 // Four vertexes, one per line. Each vertex has two components per
168 // position and two per texcoord.
169 // It represents a quad formed by two triangles if interpreted
174 -1.f
, -1.f
, 0.f
, 0.f
,
175 right
, -1.f
, 1.f
, 0.f
,
177 right
, top
, 1.f
, 1.f
};
180 glBufferData(GL_ARRAY_BUFFER
, sizeof(data
), data
, GL_STATIC_DRAW
);
184 void TearDown() override
{
185 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
186 glDeleteProgram(program_object_
);
187 glDeleteShader(vertex_shader_
);
188 glDeleteShader(fragment_shader_
);
189 glDeleteBuffersARB(1, &vertex_buffer_
);
191 glBindFramebufferEXT(GL_FRAMEBUFFER
, 0);
192 glDeleteFramebuffersEXT(1, &framebuffer_object_
);
193 glDeleteTextures(1, &color_texture_
);
196 gl_context_
= nullptr;
201 // Upload and draw on the offscren surface.
202 // Return a list of pair. Each pair describe a gl operation and the wall
203 // time elapsed in milliseconds.
204 std::vector
<Measurement
> UploadAndDraw(const gfx::Size
& size
,
205 const std::vector
<uint8
>& pixels
,
208 MeasurementTimers
total_timers(gpu_timing_client_
.get());
209 GLuint texture_id
= 0;
211 MeasurementTimers
tex_timers(gpu_timing_client_
.get());
212 glActiveTexture(GL_TEXTURE0
);
213 glGenTextures(1, &texture_id
);
214 glBindTexture(GL_TEXTURE_2D
, texture_id
);
216 glTexImage2D(GL_TEXTURE_2D
, 0, format
, size
.width(), size
.height(), 0,
217 format
, type
, &pixels
[0]);
218 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
219 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
220 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
221 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
225 MeasurementTimers
draw_timers(gpu_timing_client_
.get());
226 glUseProgram(program_object_
);
227 glUniform1i(sampler_location_
, 0);
229 DCHECK_NE(0u, vertex_buffer_
);
230 glBindBuffer(GL_ARRAY_BUFFER
, vertex_buffer_
);
231 glVertexAttribPointer(0, 2, GL_FLOAT
, GL_FALSE
, sizeof(GLfloat
) * 4, 0);
232 glVertexAttribPointer(1, 2, GL_FLOAT
, GL_FALSE
, sizeof(GLfloat
) * 4,
233 reinterpret_cast<void*>(sizeof(GLfloat
) * 2));
234 glEnableVertexAttribArray(0);
235 glEnableVertexAttribArray(1);
237 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
238 draw_timers
.Record();
240 MeasurementTimers
finish_timers(gpu_timing_client_
.get());
243 finish_timers
.Record();
244 total_timers
.Record();
246 glDeleteTextures(1, &texture_id
);
248 std::vector
<uint8
> pixels_rendered(size
.GetArea() * 4);
249 glReadPixels(0, 0, size
.width(), size
.height(), GL_RGBA
, type
,
250 &pixels_rendered
[0]);
253 // TODO(dcastagna): don't assume the format of the texture and do
254 // the appropriate format conversion.
255 EXPECT_EQ(static_cast<GLenum
>(GL_RGBA
), format
);
256 EXPECT_EQ(pixels
, pixels_rendered
);
258 std::vector
<Measurement
> measurements
;
259 bool gpu_timer_errors
=
260 gpu_timing_client_
->IsAvailable() &&
261 gpu_timing_client_
->CheckAndResetTimerErrors();
262 if (!gpu_timer_errors
) {
263 measurements
.push_back(total_timers
.GetAsMeasurement("total"));
264 measurements
.push_back(tex_timers
.GetAsMeasurement("teximage2d"));
265 measurements
.push_back(draw_timers
.GetAsMeasurement("drawarrays"));
266 measurements
.push_back(finish_timers
.GetAsMeasurement("finish"));
271 void RunUploadAndDrawMultipleTimes(const gfx::Size
& size
) {
272 std::vector
<uint8
> pixels
;
273 base::SmallMap
<std::map
<std::string
, Measurement
>>
274 aggregates
; // indexed by name
275 int successful_runs
= 0;
276 for (int i
= 0; i
< kUploadPerfWarmupRuns
+ kUploadPerfTestRuns
; ++i
) {
277 GenerateTextureData(size
, i
+ 1, &pixels
);
278 auto run
= UploadAndDraw(size
, pixels
, GL_RGBA
, GL_UNSIGNED_BYTE
);
279 if (i
< kUploadPerfWarmupRuns
|| !run
.size()) {
283 for (const Measurement
& measurement
: run
) {
284 auto& aggregate
= aggregates
[measurement
.name
];
285 aggregate
.name
= measurement
.name
;
286 aggregate
.Increment(measurement
);
289 if (successful_runs
) {
290 for (const auto& entry
: aggregates
) {
291 const auto m
= entry
.second
.Divide(successful_runs
);
292 m
.PrintResult(base::StringPrintf("_%d", size
.width()));
295 perf_test::PrintResult("sample_runs", "", "",
296 static_cast<size_t>(successful_runs
), "laps", true);
299 const gfx::Size fbo_size_
; // for the fbo
300 scoped_refptr
<gfx::GLContext
> gl_context_
;
301 scoped_refptr
<gfx::GLSurface
> surface_
;
302 scoped_refptr
<gfx::GPUTimingClient
> gpu_timing_client_
;
304 GLuint color_texture_
= 0;
305 GLuint framebuffer_object_
= 0;
306 GLuint vertex_shader_
= 0;
307 GLuint fragment_shader_
= 0;
308 GLuint program_object_
= 0;
309 GLint sampler_location_
= -1;
310 GLuint vertex_buffer_
= 0;
313 // Perf test that generates, uploads and draws a texture on a surface repeatedly
314 // and prints out aggregated measurements for all the runs.
315 TEST_F(TextureUploadPerfTest
, glTexImage2d
) {
316 int sizes
[] = {128, 256, 512, 1024};
317 for (int side
: sizes
) {
318 ASSERT_GE(fbo_size_
.width(), side
);
319 ASSERT_GE(fbo_size_
.height(), side
);
321 gfx::Size
size(side
, side
);
322 ui::ScopedMakeCurrent
smc(gl_context_
.get(), surface_
.get());
323 GenerateVertexBuffer(size
);
325 DCHECK_NE(0u, framebuffer_object_
);
326 glBindFramebufferEXT(GL_FRAMEBUFFER
, framebuffer_object_
);
328 RunUploadAndDrawMultipleTimes(size
);