| // Copyright 2012 The Chromium Authors | |
| // 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 <array> | |
| #include <memory> | |
| #include <string> | |
| #include "base/command_line.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/logging.h" | |
| #include "build/build_config.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/gl_utils.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/init/gl_factory.h" | |
| namespace gpu { | |
| // GCC requires these declarations, but MSVC requires they not be present. | |
| #ifndef COMPILER_MSVC | |
| const uint8_t GLTestHelper::kCheckClearValue; | |
| #endif | |
| gl::GLDisplay* GLTestHelper::InitializeGL(gl::GLImplementation gl_impl) { | |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) | |
| gpu::TrySetNonSoftwareDevicePreferenceForTesting(gl::GpuPreference::kDefault); | |
| #endif | |
| gl::GLDisplay* display = nullptr; | |
| if (gl_impl == gl::GLImplementation::kGLImplementationNone) { | |
| display = gl::init::InitializeGLNoExtensionsOneOff( | |
| /*init_bindings=*/true, | |
| /*gpu_preference=*/gl::GpuPreference::kDefault); | |
| } else { | |
| if (!gl::init::InitializeStaticGLBindingsImplementation( | |
| gl::GLImplementationParts(gl_impl))) { | |
| return nullptr; | |
| } | |
| display = gl::init::InitializeGLOneOffPlatformImplementation( | |
| /*disable_gl_drawing=*/false, | |
| /*init_extensions=*/false, | |
| /*gpu_preference=*/gl::GpuPreference::kDefault); | |
| } | |
| if (!display) | |
| return nullptr; | |
| 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); | |
| if (!gl::init::InitializeExtensionSettingsOneOffPlatform(display)) | |
| return nullptr; | |
| return display; | |
| } | |
| gl::GLDisplay* 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.contains(extension_padded); | |
| } | |
| 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); | |
| std::array<GLfloat, 6 * 4> vertices; | |
| for (int ii = 0; ii < 6; ++ii) { | |
| for (int jj = 0; jj < 4; ++jj) { | |
| vertices[ii * 4 + jj] = UNSAFE_TODO(color[jj]); | |
| } | |
| } | |
| glBufferData(GL_ARRAY_BUFFER, | |
| (vertices.size() * sizeof(decltype(vertices)::value_type)), | |
| vertices.data(), 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) { | |
| std::vector<uint8_t> colors(width * height * 4); | |
| for (int i = 0; i < width * height * 4; i += 4) | |
| UNSAFE_TODO(memcpy(&colors[i], color, 4)); | |
| return CheckPixels(x, y, width, height, tolerance, colors, mask); | |
| } | |
| bool GLTestHelper::CheckPixels(GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLint tolerance, | |
| const std::vector<uint8_t>& expected, | |
| const uint8_t* mask) { | |
| GLsizei size = width * height * 4; | |
| std::vector<uint8_t> pixels(size, kCheckClearValue); | |
| glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); | |
| 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_component = expected[offset + jj]; | |
| int diff = actual - expected_component; | |
| diff = diff < 0 ? -diff: diff; | |
| if ((!mask || UNSAFE_TODO(mask[jj])) && diff > tolerance) { | |
| EXPECT_EQ(static_cast<int>(expected_component), | |
| static_cast<int>(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(base::span<uint8_t, 2> dest, uint16_t value) { | |
| dest[0] = value & 0xFFu; | |
| dest[1] = value >> 8; | |
| } | |
| void Set32BitValue(base::span<uint8_t, 4> dest, 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]; | |
| }; | |
| } // namespace | |
| 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; | |
| auto data = base::HeapArray<uint8_t>::WithSize(size); | |
| glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data.data()); | |
| // RGBA to BGRA | |
| for (int ii = 0; ii < num_pixels; ++ii) { | |
| int offset = ii * 4; | |
| uint8_t t = data[offset + 0]; | |
| data[offset + 0] = data[offset + 2]; | |
| data[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); | |
| UNSAFE_TODO(fwrite(&bhf, sizeof(bhf), 1, fp)); | |
| UNSAFE_TODO(fwrite(&bih, sizeof(bih), 1, fp)); | |
| UNSAFE_TODO(fwrite(data.data(), 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::InitializeEGL(int width, int height) { | |
| gl::GLImplementation current_impl = gl::GetGLImplementation(); | |
| if (!(current_impl == gl::kGLImplementationEGLGLES2 || | |
| current_impl == gl::kGLImplementationEGLANGLE)) { | |
| gpu::GPUInfo gpu_info; | |
| gpu::CollectContextGraphicsInfo(&gpu_info); | |
| gpu::CollectBasicGraphicsInfo(base::CommandLine::ForCurrentProcess(), | |
| &gpu_info); | |
| // See crbug.com/822716, the ATI proprietary driver has eglGetProcAddress | |
| // but eglInitialize crashes with x11. | |
| if (gpu_info.gl_vendor.contains("ATI Technologies Inc.")) { | |
| LOG(INFO) << "Skip test, ATI proprietary driver crashes with egl/x11"; | |
| return false; | |
| } | |
| // The native EGL driver is not supported with the passthrough command | |
| // decoder, in that case use ANGLE | |
| gl::GLImplementationParts new_impl(gl::kGLImplementationEGLGLES2); | |
| if (gpu_info.passthrough_cmd_decoder) | |
| new_impl = gl::GLImplementationParts(gl::kGLImplementationEGLANGLE); | |
| const auto allowed_impls = gl::init::GetAllowedGLImplementations(); | |
| if (!new_impl.IsAllowed(allowed_impls)) { | |
| LOG(INFO) << "Skip test, no EGL implementation is available"; | |
| return false; | |
| } | |
| gl_reinitialized_ = true; | |
| gl::init::ShutdownGL(gl_display_, false /* due_to_fallback */); | |
| gl_display_ = GLTestHelper::InitializeGL(new_impl.gl); | |
| if (!gl_display_) { | |
| LOG(INFO) << "Skip test, failed to initialize EGL"; | |
| return false; | |
| } | |
| } | |
| current_impl = gl::GetGLImplementation(); | |
| DCHECK(current_impl == gl::kGLImplementationEGLGLES2 || | |
| current_impl == gl::kGLImplementationEGLANGLE); | |
| // Make the GL context current now to get all extensions. | |
| GLManager::Options options; | |
| options.size = gfx::Size(width, height); | |
| gl_.Initialize(options); | |
| gl_.MakeCurrent(); | |
| gl_extensions_ = | |
| gfx::MakeExtensionSet(gl::GetGLExtensionsFromCurrentContext()); | |
| gl::GLVersionInfo gl_version_info( | |
| reinterpret_cast<const char*>(glGetString(GL_VERSION)), | |
| reinterpret_cast<const char*>(glGetString(GL_RENDERER)), gl_extensions_); | |
| bool result = gl::init::GetGLWindowSystemBindingInfo( | |
| gl_version_info, &window_system_binding_info_); | |
| DCHECK(result); | |
| egl_extensions_ = | |
| gfx::MakeExtensionSet(window_system_binding_info_.extensions); | |
| return true; | |
| } | |
| void GpuCommandBufferTestEGL::RestoreGLDefault() { | |
| gl_.Destroy(); | |
| if (gl_reinitialized_) { | |
| gl::init::ShutdownGL(gl_display_, false /* due_to_fallback */); | |
| gl_display_ = GLTestHelper::InitializeGLDefault(); | |
| } | |
| gl_reinitialized_ = false; | |
| gl_extensions_.clear(); | |
| egl_extensions_.clear(); | |
| window_system_binding_info_ = gl::GLWindowSystemBindingInfo(); | |
| } | |
| } // namespace gpu |