blob: 25346d7e32d26b757b71e39bfe13ae84c476f5bb [file] [log] [blame]
// 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/gl_version_info.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);
// 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();
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(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.
auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, 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