Chromium Code Reviews| Index: gpu/perftests/texture_upload_perftest.cc |
| diff --git a/gpu/perftests/texture_upload_perftest.cc b/gpu/perftests/texture_upload_perftest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4c9331157f1e325d9354f3f9635725893d937f27 |
| --- /dev/null |
| +++ b/gpu/perftests/texture_upload_perftest.cc |
| @@ -0,0 +1,247 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include <map> |
| +#include <vector> |
| + |
| +#include "base/memory/ref_counted.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/time/time.h" |
| +#include "base/timer/elapsed_timer.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "testing/perf/perf_test.h" |
| +#include "ui/gfx/geometry/size.h" |
| +#include "ui/gl/gl_bindings.h" |
| +#include "ui/gl/gl_context.h" |
| +#include "ui/gl/gl_surface.h" |
| +#include "ui/gl/scoped_make_current.h" |
| + |
| +namespace gfx { |
| +namespace { |
| + |
| +const int kUploadPerfWarmupRuns = 10; |
| +const int kUploadPerfTestRuns = 100; |
| + |
| +#define SHADER(Src) #Src |
| + |
| +// clang-format off |
| +const char kVertexShader[] = |
| +SHADER( |
| + attribute vec2 a_position; |
| + varying vec2 v_texCoord; |
| + void main() { |
| + gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0); |
| + v_texCoord = vec2((a_position.x + 1) * 0.5, (a_position.y + 1) * 0.5); |
| + } |
| +); |
| +const char kFragmentShader[] = |
| +SHADER( |
| + uniform sampler2D a_texture; |
| + varying vec2 v_texCoord; |
| + void main() { |
| + gl_FragColor = texture2D(a_texture, v_texCoord.xy); |
| + } |
| +); |
| +// clang-format on |
| + |
| +void CheckNoGlError() { |
| + CHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); |
| +} |
| + |
| +// Utility function to compile a shader from a string. |
| +GLuint LoadShader(const GLenum type, const char* const src) { |
| + GLuint shader = 0; |
| + shader = glCreateShader(type); |
| + CHECK_NE(0u, shader); |
| + glShaderSource(shader, 1, &src, NULL); |
| + glCompileShader(shader); |
| + |
| + GLint compiled = 0; |
| + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); |
| + if (compiled == 0) { |
| + GLint len = 0; |
| + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); |
| + if (len > 1) { |
| + scoped_ptr<char> error_log(new char[len]); |
| + glGetShaderInfoLog(shader, len, NULL, error_log.get()); |
| + LOG(ERROR) << "Error compiling shader: " << error_log.get(); |
| + } |
| + } |
| + CHECK_NE(0, compiled); |
| + return shader; |
| +} |
| + |
| +void GenerateTextureData(const gfx::Size& size, |
| + const int seed, |
| + std::vector<uint8>* const pixels) { |
| + pixels->resize(size.GetArea() * 4); |
| + for (int y = 0; y < size.height(); ++y) { |
| + for (int x = 0; x < size.width(); ++x) { |
| + const size_t offset = (y * size.width() + x) * 4; |
| + pixels->at(offset) = (y + seed) % 256; |
| + pixels->at(offset + 1) = (x + seed) % 256; |
| + pixels->at(offset + 2) = (y + x + seed) % 256; |
| + pixels->at(offset + 3) = 255; |
| + } |
| + } |
| +} |
| + |
| +// PerfTest to check costs of texture upload at different stages |
| +// on different platforms. |
| +class TextureUploadPerfTest : public testing::Test { |
| + public: |
| + TextureUploadPerfTest() : size_(512, 512) {} |
| + |
| + // Overridden from testing::Test |
| + void SetUp() override { |
| + // Initialize an offscreen surface and a gl context. |
| + GLSurface::InitializeOneOff(); |
| + surface_ = GLSurface::CreateOffscreenGLSurface(size_); |
| + gl_context_ = |
| + GLContext::CreateGLContext(NULL, // share_group |
| + surface_.get(), gfx::PreferIntegratedGpu); |
| + |
| + // Prepare a simple program and a vertex buffer that will be |
| + // used to draw a quad on the offscreen surface. |
| + ui::ScopedMakeCurrent smc(gl_context_.get(), surface_.get()); |
| + vertex_shader_ = LoadShader(GL_VERTEX_SHADER, kVertexShader); |
| + fragment_shader_ = LoadShader(GL_FRAGMENT_SHADER, kFragmentShader); |
| + program_object_ = glCreateProgram(); |
| + CHECK_NE(0u, program_object_); |
| + |
| + glAttachShader(program_object_, vertex_shader_); |
| + glAttachShader(program_object_, fragment_shader_); |
| + glBindAttribLocation(program_object_, 0, "a_position"); |
| + glLinkProgram(program_object_); |
| + |
| + GLint linked = -1; |
| + glGetProgramiv(program_object_, GL_LINK_STATUS, &linked); |
| + CHECK_NE(0, linked); |
| + |
| + sampler_location_ = glGetUniformLocation(program_object_, "a_texture"); |
| + CHECK_NE(-1, sampler_location_); |
| + |
| + glGenBuffersARB(1, &vertex_buffer_); |
| + CHECK_NE(0u, vertex_buffer_); |
| + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); |
| + static GLfloat positions[] = { |
| + -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, |
| + }; |
| + glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); |
| + CheckNoGlError(); |
| + } |
| + |
| + void TearDown() override { |
| + ui::ScopedMakeCurrent smc(gl_context_.get(), surface_.get()); |
| + if (program_object_ != 0) { |
| + glDeleteProgram(program_object_); |
| + } |
| + if (vertex_shader_ != 0) { |
| + glDeleteShader(vertex_shader_); |
| + } |
| + if (fragment_shader_ != 0) { |
| + glDeleteShader(fragment_shader_); |
| + } |
| + if (vertex_buffer_ != 0) { |
| + glDeleteShader(vertex_buffer_); |
| + } |
| + |
| + gl_context_ = nullptr; |
| + surface_ = nullptr; |
| + } |
| + |
| + protected: |
| + // Upload and draw on the offscren surface. |
| + // Return a list of pair. Each pair describe a gl operation and the wall |
| + // time elapsed in milliseconds. |
| + std::vector<std::pair<std::string, float>> UploadAndDraw( |
| + const std::vector<uint8>& pixels) { |
| + std::vector<std::pair<std::string, float>> timings; |
| + ui::ScopedMakeCurrent smc(gl_context_.get(), surface_.get()); |
| + |
| + base::ElapsedTimer total_timer; |
| + GLuint texture_id = 0; |
| + |
| + base::ElapsedTimer upload_timer; |
| + glActiveTexture(GL_TEXTURE0); |
| + glGenTextures(1, &texture_id); |
| + glBindTexture(GL_TEXTURE_2D, texture_id); |
| + |
| + const GLenum format = GL_RGBA; |
| + const GLenum type = GL_UNSIGNED_BYTE; |
|
reveman
2015/01/27 13:33:43
Should format, type be passed as parameters instea
Daniele Castagna
2015/01/27 19:00:42
Done.
|
| + glTexImage2D(GL_TEXTURE_2D, 0, format, size_.width(), size_.height(), 0, |
| + format, type, &pixels[0]); |
| + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| + CheckNoGlError(); |
| + timings.push_back(std::make_pair("upload_image", |
|
reveman
2015/01/27 13:33:42
Let's name this "teximage2d" instead as discussed.
Daniele Castagna
2015/01/27 19:00:42
Done.
|
| + upload_timer.Elapsed().InMillisecondsF())); |
| + |
| + base::ElapsedTimer draw_timer; |
| + glUseProgram(program_object_); |
| + glUniform1i(sampler_location_, 0); |
| + |
| + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); |
| + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); |
| + glEnableVertexAttribArray(0); |
| + |
| + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| + timings.push_back( |
| + std::make_pair("draw_image", draw_timer.Elapsed().InMillisecondsF())); |
|
reveman
2015/01/27 13:33:42
nit: "drawarrays"
Daniele Castagna
2015/01/27 19:00:42
Done.
|
| + |
| + base::ElapsedTimer finish_timer; |
| + glFinish(); |
| + CheckNoGlError(); |
| + timings.push_back( |
| + std::make_pair("finish", finish_timer.Elapsed().InMillisecondsF())); |
| + timings.push_back( |
| + std::make_pair("total", total_timer.Elapsed().InMillisecondsF())); |
| + |
| + glDeleteTextures(1, &texture_id); |
| + |
| + std::vector<uint8> pixels_rendered(size_.GetArea() * 4); |
| + glReadPixels(0, 0, size_.width(), size_.height(), format, type, |
|
reveman
2015/01/27 13:33:43
Is the format passed to read pixels really suppose
Daniele Castagna
2015/01/27 19:00:42
You're right, it's not supposed to be the same as
|
| + &pixels_rendered[0]); |
| + CheckNoGlError(); |
| + |
| + EXPECT_EQ(pixels, pixels_rendered); |
| + return timings; |
| + } |
| + |
| + const gfx::Size size_; // for the offscreen surface and the texture |
|
reveman
2015/01/27 13:33:43
nit: s/size_/kSize/
Daniele Castagna
2015/01/27 19:00:42
Are you sure? This is not a compile time constant:
|
| + scoped_refptr<GLContext> gl_context_; |
| + scoped_refptr<GLSurface> surface_; |
| + |
| + GLuint vertex_shader_ = 0; |
| + GLuint fragment_shader_ = 0; |
| + GLuint program_object_ = 0; |
| + GLint sampler_location_ = -1; |
| + GLuint vertex_buffer_ = 0; |
| +}; |
| + |
| +// Perf test that generates, uploads and draws a texture on a surface repeatedly |
| +// and prints out aggregated timings for all the runs. |
| +TEST_F(TextureUploadPerfTest, glTexImage2d) { |
| + std::vector<uint8> pixels; |
| + std::map<std::string, float> timings; |
|
reveman
2015/01/27 13:33:43
nit: s/timings/measurements/
and maybe add a "Mea
Daniele Castagna
2015/01/27 19:00:42
Done.
|
| + for (int i = 0; i < kUploadPerfWarmupRuns + kUploadPerfTestRuns; ++i) { |
| + GenerateTextureData(size_, i + 1, &pixels); |
| + auto run = UploadAndDraw(pixels); |
| + if (i >= kUploadPerfWarmupRuns) { |
| + for (const std::pair<std::string, float>& t : run) { |
| + timings[t.first] += t.second; |
| + } |
| + } |
| + } |
| + |
| + for (const std::pair<std::string, float>& t : timings) { |
| + perf_test::PrintResult(t.first, "", "", t.second / kUploadPerfTestRuns, |
| + "ms", true); |
| + } |
| +} |
| + |
| +} // namespace |
| +} // namespace cc |