| // Copyright (c) 2012 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 "gpu/command_buffer/tests/gl_test_utils.h" | |
| #include <GLES2/gl2extchromium.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <memory> | |
| #include <string> | |
| #include "base/command_line.h" | |
| #include "base/stl_util.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_utils.h" | |
| #include "gpu/config/gpu_driver_bug_workarounds.h" | |
| #include "gpu/config/gpu_info_collector.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "gpu/config/gpu_util.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "ui/gfx/geometry/size.h" | |
| #include "ui/gl/init/gl_factory.h" | |
| #if defined(OS_LINUX) | |
| #include "ui/gl/gl_image_native_pixmap.h" | |
| #endif | |
| namespace gpu { | |
| // GCC requires these declarations, but MSVC requires they not be present. | |
| #ifndef COMPILER_MSVC | |
| const uint8_t GLTestHelper::kCheckClearValue; | |
| #endif | |
| bool GLTestHelper::InitializeGL(gl::GLImplementation gl_impl) { | |
| if (gl_impl == gl::GLImplementation::kGLImplementationNone) { | |
| if (!gl::init::InitializeGLNoExtensionsOneOff()) | |
| return false; | |
| } else { | |
| if (!gl::init::InitializeGLOneOffImplementation( | |
| gl_impl, | |
| false, // fallback_to_software_gl | |
| false, // gpu_service_logging | |
| false, // disable_gl_drawing | |
| false // init_extensions | |
| )) { | |
| return false; | |
| } | |
| } | |
| gpu::GPUInfo gpu_info; | |
| gpu::CollectGraphicsInfoForTesting(&gpu_info); | |
| gpu::GLManager::g_gpu_feature_info = gpu::ComputeGpuFeatureInfo( | |
| gpu_info, gpu::GpuPreferences(), base::CommandLine::ForCurrentProcess(), | |
| nullptr // needs_more_info | |
| ); | |
| gl::init::SetDisabledExtensionsPlatform( | |
| gpu::GLManager::g_gpu_feature_info.disabled_extensions); | |
| return gl::init::InitializeExtensionSettingsOneOffPlatform(); | |
| } | |
| bool GLTestHelper::InitializeGLDefault() { | |
| return GLTestHelper::InitializeGL( | |
| gl::GLImplementation::kGLImplementationNone); | |
| } | |
| bool GLTestHelper::HasExtension(const char* extension) { | |
| // Pad with an extra space to ensure that |extension| is not a substring of | |
| // another extension. | |
| std::string extensions = | |
| std::string(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))) + | |
| " "; | |
| std::string extension_padded = std::string(extension) + " "; | |
| return extensions.find(extension_padded) != std::string::npos; | |
| } | |
| bool GLTestHelper::CheckGLError(const char* msg, int line) { | |
| bool success = true; | |
| GLenum error = GL_NO_ERROR; | |
| while ((error = glGetError()) != GL_NO_ERROR) { | |
| success = false; | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), error) | |
| << "GL ERROR in " << msg << " at line " << line << " : " << error; | |
| } | |
| return success; | |
| } | |
| GLuint GLTestHelper::CompileShader(GLenum type, const char* shaderSrc) { | |
| GLuint shader = glCreateShader(type); | |
| // Load the shader source | |
| glShaderSource(shader, 1, &shaderSrc, nullptr); | |
| // Compile the shader | |
| glCompileShader(shader); | |
| return shader; | |
| } | |
| GLuint GLTestHelper::LoadShader(GLenum type, const char* shaderSrc) { | |
| GLuint shader = CompileShader(type, shaderSrc); | |
| // Check the compile status | |
| GLint value = 0; | |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &value); | |
| if (value == 0) { | |
| char buffer[1024]; | |
| GLsizei length = 0; | |
| glGetShaderInfoLog(shader, sizeof(buffer), &length, buffer); | |
| std::string log(buffer, length); | |
| EXPECT_EQ(1, value) << "Error compiling shader: " << log; | |
| glDeleteShader(shader); | |
| shader = 0; | |
| } | |
| return shader; | |
| } | |
| GLuint GLTestHelper::LinkProgram( | |
| GLuint vertex_shader, GLuint fragment_shader) { | |
| // Create the program object | |
| GLuint program = glCreateProgram(); | |
| glAttachShader(program, vertex_shader); | |
| glAttachShader(program, fragment_shader); | |
| // Link the program | |
| glLinkProgram(program); | |
| return program; | |
| } | |
| GLuint GLTestHelper::SetupProgram( | |
| GLuint vertex_shader, GLuint fragment_shader) { | |
| GLuint program = LinkProgram(vertex_shader, fragment_shader); | |
| // Check the link status | |
| GLint linked = 0; | |
| glGetProgramiv(program, GL_LINK_STATUS, &linked); | |
| if (linked == 0) { | |
| char buffer[1024]; | |
| GLsizei length = 0; | |
| glGetProgramInfoLog(program, sizeof(buffer), &length, buffer); | |
| std::string log(buffer, length); | |
| EXPECT_EQ(1, linked) << "Error linking program: " << log; | |
| glDeleteProgram(program); | |
| program = 0; | |
| } | |
| return program; | |
| } | |
| GLuint GLTestHelper::LoadProgram( | |
| const char* vertex_shader_source, | |
| const char* fragment_shader_source) { | |
| GLuint vertex_shader = LoadShader( | |
| GL_VERTEX_SHADER, vertex_shader_source); | |
| GLuint fragment_shader = LoadShader( | |
| GL_FRAGMENT_SHADER, fragment_shader_source); | |
| if (!vertex_shader || !fragment_shader) { | |
| return 0; | |
| } | |
| return SetupProgram(vertex_shader, fragment_shader); | |
| } | |
| GLuint GLTestHelper::SetupUnitQuad(GLint position_location) { | |
| GLuint vbo = 0; | |
| glGenBuffers(1, &vbo); | |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
| static const float vertices[] = { | |
| 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, | |
| 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, | |
| }; | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); | |
| glEnableVertexAttribArray(position_location); | |
| glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE, 0, 0); | |
| return vbo; | |
| } | |
| std::vector<GLuint> GLTestHelper::SetupIndexedUnitQuad( | |
| GLint position_location) { | |
| GLuint array_buffer = SetupUnitQuad(position_location); | |
| static const uint8_t indices[] = {0, 1, 2, 3, 4, 5}; | |
| GLuint index_buffer = 0; | |
| glGenBuffers(1, &index_buffer); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); | |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6, indices, GL_STATIC_DRAW); | |
| std::vector<GLuint> buffers(2); | |
| buffers[0] = array_buffer; | |
| buffers[1] = index_buffer; | |
| return buffers; | |
| } | |
| GLuint GLTestHelper::SetupColorsForUnitQuad( | |
| GLint location, const GLfloat color[4], GLenum usage) { | |
| GLuint vbo = 0; | |
| glGenBuffers(1, &vbo); | |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
| GLfloat vertices[6 * 4]; | |
| for (int ii = 0; ii < 6; ++ii) { | |
| for (int jj = 0; jj < 4; ++jj) { | |
| vertices[ii * 4 + jj] = color[jj]; | |
| } | |
| } | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, usage); | |
| glEnableVertexAttribArray(location); | |
| glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, 0); | |
| return vbo; | |
| } | |
| bool GLTestHelper::CheckPixels(GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLint tolerance, | |
| const uint8_t* color, | |
| const uint8_t* mask) { | |
| GLsizei size = width * height * 4; | |
| std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]); | |
| memset(pixels.get(), kCheckClearValue, size); | |
| glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); | |
| int bad_count = 0; | |
| for (GLint yy = 0; yy < height; ++yy) { | |
| for (GLint xx = 0; xx < width; ++xx) { | |
| int offset = yy * width * 4 + xx * 4; | |
| for (int jj = 0; jj < 4; ++jj) { | |
| uint8_t actual = pixels[offset + jj]; | |
| uint8_t expected = color[jj]; | |
| int diff = actual - expected; | |
| diff = diff < 0 ? -diff: diff; | |
| if ((!mask || mask[jj]) && diff > tolerance) { | |
| EXPECT_EQ(expected, actual) << " at " << (xx + x) << ", " << (yy + y) | |
| << " channel " << jj; | |
| ++bad_count; | |
| // Exit early just so we don't spam the log but we print enough | |
| // to hopefully make it easy to diagnose the issue. | |
| if (bad_count > 16) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return bad_count == 0; | |
| } | |
| namespace { | |
| void Set16BitValue(uint8_t dest[2], uint16_t value) { | |
| dest[0] = value & 0xFFu; | |
| dest[1] = value >> 8; | |
| } | |
| void Set32BitValue(uint8_t dest[4], uint32_t value) { | |
| dest[0] = (value >> 0) & 0xFFu; | |
| dest[1] = (value >> 8) & 0xFFu; | |
| dest[2] = (value >> 16) & 0xFFu; | |
| dest[3] = (value >> 24) & 0xFFu; | |
| } | |
| struct BitmapHeaderFile { | |
| uint8_t magic[2]; | |
| uint8_t size[4]; | |
| uint8_t reserved[4]; | |
| uint8_t offset[4]; | |
| }; | |
| struct BitmapInfoHeader{ | |
| uint8_t size[4]; | |
| uint8_t width[4]; | |
| uint8_t height[4]; | |
| uint8_t planes[2]; | |
| uint8_t bit_count[2]; | |
| uint8_t compression[4]; | |
| uint8_t size_image[4]; | |
| uint8_t x_pels_per_meter[4]; | |
| uint8_t y_pels_per_meter[4]; | |
| uint8_t clr_used[4]; | |
| uint8_t clr_important[4]; | |
| }; | |
| } | |
| bool GLTestHelper::SaveBackbufferAsBMP( | |
| const char* filename, int width, int height) { | |
| FILE* fp = fopen(filename, "wb"); | |
| EXPECT_TRUE(fp != nullptr); | |
| glPixelStorei(GL_PACK_ALIGNMENT, 1); | |
| int num_pixels = width * height; | |
| int size = num_pixels * 4; | |
| std::unique_ptr<uint8_t[]> data(new uint8_t[size]); | |
| uint8_t* pixels = data.get(); | |
| glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); | |
| // RGBA to BGRA | |
| for (int ii = 0; ii < num_pixels; ++ii) { | |
| int offset = ii * 4; | |
| uint8_t t = pixels[offset + 0]; | |
| pixels[offset + 0] = pixels[offset + 2]; | |
| pixels[offset + 2] = t; | |
| } | |
| BitmapHeaderFile bhf; | |
| BitmapInfoHeader bih; | |
| bhf.magic[0] = 'B'; | |
| bhf.magic[1] = 'M'; | |
| Set32BitValue(bhf.size, 0); | |
| Set32BitValue(bhf.reserved, 0); | |
| Set32BitValue(bhf.offset, sizeof(bhf) + sizeof(bih)); | |
| Set32BitValue(bih.size, sizeof(bih)); | |
| Set32BitValue(bih.width, width); | |
| Set32BitValue(bih.height, height); | |
| Set16BitValue(bih.planes, 1); | |
| Set16BitValue(bih.bit_count, 32); | |
| Set32BitValue(bih.compression, 0); | |
| Set32BitValue(bih.x_pels_per_meter, 0); | |
| Set32BitValue(bih.y_pels_per_meter, 0); | |
| Set32BitValue(bih.clr_used, 0); | |
| Set32BitValue(bih.clr_important, 0); | |
| fwrite(&bhf, sizeof(bhf), 1, fp); | |
| fwrite(&bih, sizeof(bih), 1, fp); | |
| fwrite(pixels, size, 1, fp); | |
| fclose(fp); | |
| return true; | |
| } | |
| void GLTestHelper::DrawTextureQuad(const GLenum texture_target, | |
| const char* vertex_src, | |
| const char* fragment_src, | |
| const char* position_name, | |
| const char* sampler_name, | |
| const char* face_name) { | |
| GLuint program = GLTestHelper::LoadProgram(vertex_src, fragment_src); | |
| EXPECT_NE(program, 0u); | |
| glUseProgram(program); | |
| GLint position_loc = glGetAttribLocation(program, position_name); | |
| GLint sampler_location = glGetUniformLocation(program, sampler_name); | |
| ASSERT_NE(position_loc, -1); | |
| ASSERT_NE(sampler_location, -1); | |
| GLint face_loc = -1; | |
| if (gpu::gles2::GLES2Util::GLFaceTargetToTextureTarget(texture_target) == | |
| GL_TEXTURE_CUBE_MAP) { | |
| ASSERT_NE(face_name, nullptr); | |
| face_loc = glGetUniformLocation(program, face_name); | |
| ASSERT_NE(face_loc, -1); | |
| glUniform1i(face_loc, texture_target); | |
| } | |
| GLuint vertex_buffer = GLTestHelper::SetupUnitQuad(position_loc); | |
| ASSERT_NE(vertex_buffer, 0u); | |
| glActiveTexture(GL_TEXTURE0); | |
| glUniform1i(sampler_location, 0); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| glDeleteProgram(program); | |
| glDeleteBuffers(1, &vertex_buffer); | |
| } | |
| GpuCommandBufferTestEGL::GpuCommandBufferTestEGL() : gl_reinitialized_(false) {} | |
| GpuCommandBufferTestEGL::~GpuCommandBufferTestEGL() {} | |
| bool GpuCommandBufferTestEGL::InitializeEGLGLES2(int width, int height) { | |
| if (gl::GetGLImplementation() != | |
| gl::GLImplementation::kGLImplementationEGLGLES2) { | |
| const auto impls = gl::init::GetAllowedGLImplementations(); | |
| if (!base::ContainsValue(impls, | |
| gl::GLImplementation::kGLImplementationEGLGLES2)) { | |
| LOG(INFO) << "Skip test, implementation EGLGLES2 is not available"; | |
| return false; | |
| } | |
| gpu::GPUInfo gpu_info; | |
| gpu::CollectContextGraphicsInfo(&gpu_info, gpu::GpuPreferences()); | |
| // See crbug.com/822716, the ATI proprietary driver has eglGetProcAddress | |
| // but eglInitialize crashes with x11. | |
| if (gpu_info.gl_vendor.find("ATI Technologies Inc.") != std::string::npos) { | |
| LOG(INFO) << "Skip test, ATI proprietary driver crashes with egl/x11"; | |
| return false; | |
| } | |
| gl_reinitialized_ = true; | |
| gl::init::ShutdownGL(false /* due_to_fallback */); | |
| if (!GLTestHelper::InitializeGL( | |
| gl::GLImplementation::kGLImplementationEGLGLES2)) { | |
| LOG(INFO) << "Skip test, failed to initialize EGLGLES2"; | |
| return false; | |
| } | |
| } | |
| DCHECK_EQ(gl::GLImplementation::kGLImplementationEGLGLES2, | |
| gl::GetGLImplementation()); | |
| // Make the GL context current now to get all extensions. | |
| GLManager::Options options; | |
| options.size = gfx::Size(width, height); | |
| gl_.Initialize(options); | |
| gl_.MakeCurrent(); | |
| bool result = | |
| gl::init::GetGLWindowSystemBindingInfo(&window_system_binding_info_); | |
| DCHECK(result); | |
| egl_extensions_ = | |
| gfx::MakeExtensionSet(window_system_binding_info_.extensions); | |
| gl_extensions_ = | |
| gfx::MakeExtensionSet(gl::GetGLExtensionsFromCurrentContext()); | |
| return true; | |
| } | |
| void GpuCommandBufferTestEGL::RestoreGLDefault() { | |
| gl_.Destroy(); | |
| if (gl_reinitialized_) { | |
| gl::init::ShutdownGL(false /* due_to_fallback */); | |
| GLTestHelper::InitializeGLDefault(); | |
| } | |
| gl_reinitialized_ = false; | |
| gl_extensions_.clear(); | |
| egl_extensions_.clear(); | |
| window_system_binding_info_ = gl::GLWindowSystemBindingInfo(); | |
| } | |
| #if defined(OS_LINUX) | |
| scoped_refptr<gl::GLImageNativePixmap> | |
| GpuCommandBufferTestEGL::CreateGLImageNativePixmap(gfx::BufferFormat format, | |
| gfx::Size size, | |
| uint8_t* pixels) const { | |
| // Upload raw pixels to a new GL texture. | |
| GLuint tex_client_id = 0; | |
| glGenTextures(1, &tex_client_id); | |
| DCHECK_NE(0u, tex_client_id); | |
| glBindTexture(GL_TEXTURE_2D, tex_client_id); | |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, pixels); | |
| // Make sure the texture exists in the service side. | |
| glFinish(); | |
| // This works because the test run in a similar mode as In-Process-GPU. | |
| unsigned int tex_service_id = 0; | |
| gl_.decoder()->GetServiceTextureId(tex_client_id, &tex_service_id); | |
| EXPECT_NE(0u, tex_service_id); | |
| // Create an EGLImage from the real texture id. | |
| scoped_refptr<gl::GLImageNativePixmap> image(new gl::GLImageNativePixmap( | |
| size, gl::GLImageNativePixmap::GetInternalFormatForTesting(format))); | |
| bool result = image->InitializeFromTexture(tex_service_id); | |
| DCHECK(result); | |
| // The test will own the EGLImage no need to keep a reference on the GL | |
| // texture after returning from this function. This is covered by the | |
| // EGL_KHR_image_base.txt specification, i.e. the underlying memory remains | |
| // allocated as long as there is at least one sibling (like ref count). | |
| glDeleteTextures(1, &tex_client_id); | |
| return image; | |
| } | |
| gfx::NativePixmapHandle GpuCommandBufferTestEGL::CreateNativePixmapHandle( | |
| gfx::BufferFormat format, | |
| gfx::Size size, | |
| uint8_t* pixels) { | |
| scoped_refptr<gl::GLImageNativePixmap> image = | |
| CreateGLImageNativePixmap(format, size, pixels); | |
| EXPECT_TRUE(image); | |
| EXPECT_EQ(size, image->GetSize()); | |
| // Export the EGLImage as dmabuf fds | |
| // The test will own the dmabuf fds so no need to keep a reference on the | |
| // EGLImage after returning from this function. | |
| return image->ExportHandle(); | |
| } | |
| #endif | |
| } // namespace gpu |