blob: d4dd71158b398a828eb973c0dda50e68daba5c5b [file] [log] [blame]
// Copyright 2014 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 <GLES2/gl2.h>
#include <GLES2/gl2chromium.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "base/process/process_handle.h"
#include "build/build_config.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/service/command_buffer_service.h"
#include "gpu/command_buffer/service/image_manager.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gfx/half_float.h"
#include "ui/gl/gl_image.h"
#include "ui/gl/test/gl_image_test_support.h"
#if defined(OS_LINUX)
#include "gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h"
#include "ui/gfx/linux/client_native_pixmap_factory_dmabuf.h"
#endif
#define SKIP_TEST_IF(cmd) \
do { \
if (cmd) { \
LOG(INFO) << "Skip test because " << #cmd; \
return; \
} \
} while (false)
using testing::_;
using testing::IgnoreResult;
using testing::InvokeWithoutArgs;
using testing::Invoke;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
namespace gpu {
namespace gles2 {
static const int kImageWidth = 32;
static const int kImageHeight = 32;
class GpuMemoryBufferTest : public testing::TestWithParam<gfx::BufferFormat> {
protected:
void SetUp() override {
GLManager::Options options;
options.size = gfx::Size(kImageWidth, kImageHeight);
gl_.Initialize(options);
gl_.MakeCurrent();
}
void TearDown() override {
gl_.Destroy();
}
GLManager gl_;
};
#if defined(OS_LINUX)
class GpuMemoryBufferTestEGL : public testing::Test,
public gpu::GpuCommandBufferTestEGL {
public:
GpuMemoryBufferTestEGL()
: egl_gles2_initialized_(false),
native_pixmap_factory_(gfx::CreateClientNativePixmapFactoryDmabuf()) {}
protected:
void SetUp() override {
egl_gles2_initialized_ = InitializeEGLGLES2(kImageWidth, kImageHeight);
gl_.set_use_native_pixmap_memory_buffers(true);
}
void TearDown() override { RestoreGLDefault(); }
bool egl_gles2_initialized_;
std::unique_ptr<gfx::ClientNativePixmapFactory> native_pixmap_factory_;
};
#endif // defined(OS_LINUX)
namespace {
#define SHADER(Src) #Src
// clang-format off
const char kVertexShader[] =
SHADER(
attribute vec4 a_position;
varying vec2 v_texCoord;
void main() {
gl_Position = a_position;
v_texCoord = vec2((a_position.x + 1.0) * 0.5, (a_position.y + 1.0) * 0.5);
}
);
const char* kFragmentShader =
SHADER(
precision mediump float;
uniform sampler2D a_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(a_texture, v_texCoord);
}
);
// clang-format on
GLenum InternalFormat(gfx::BufferFormat format) {
switch (format) {
case gfx::BufferFormat::R_8:
return GL_RED;
case gfx::BufferFormat::R_16:
return GL_R16_EXT;
case gfx::BufferFormat::RG_88:
return GL_RG;
case gfx::BufferFormat::BGR_565:
case gfx::BufferFormat::RGBX_1010102:
return GL_RGB;
case gfx::BufferFormat::RGBA_4444:
case gfx::BufferFormat::RGBA_8888:
return GL_RGBA;
case gfx::BufferFormat::BGRA_8888:
case gfx::BufferFormat::BGRX_1010102:
return GL_BGRA_EXT;
case gfx::BufferFormat::RGBA_F16:
return GL_RGBA;
case gfx::BufferFormat::BGRX_8888:
case gfx::BufferFormat::RGBX_8888:
case gfx::BufferFormat::YVU_420:
case gfx::BufferFormat::YUV_420_BIPLANAR:
case gfx::BufferFormat::P010:
NOTREACHED() << gfx::BufferFormatToString(format);
return 0;
}
NOTREACHED();
return 0;
}
uint32_t BufferFormatToFourCC(gfx::BufferFormat format) {
switch (format) {
case gfx::BufferFormat::BGR_565:
return libyuv::FOURCC_ANY; // libyuv::FOURCC_RGBP has wrong endianness.
case gfx::BufferFormat::RGBA_4444:
return libyuv::FOURCC_ANY; // libyuv::FOURCC_R444 has wrong endianness.
case gfx::BufferFormat::RGBA_8888:
return libyuv::FOURCC_ABGR;
case gfx::BufferFormat::BGRA_8888:
return libyuv::FOURCC_ARGB;
case gfx::BufferFormat::RGBX_1010102:
return libyuv::FOURCC_AB30;
case gfx::BufferFormat::BGRX_1010102:
return libyuv::FOURCC_AR30;
case gfx::BufferFormat::YUV_420_BIPLANAR:
return libyuv::FOURCC_NV12;
case gfx::BufferFormat::YVU_420:
return libyuv::FOURCC_YV12;
case gfx::BufferFormat::R_8:
case gfx::BufferFormat::R_16:
case gfx::BufferFormat::RG_88:
case gfx::BufferFormat::RGBA_F16:
case gfx::BufferFormat::BGRX_8888:
case gfx::BufferFormat::RGBX_8888:
case gfx::BufferFormat::P010:
return libyuv::FOURCC_ANY;
}
NOTREACHED();
return libyuv::FOURCC_ANY;
}
} // namespace
// Verifies that the read-back colour after map-write-unmap is the original.
TEST_P(GpuMemoryBufferTest, MapUnmap) {
const gfx::BufferFormat buffer_format = GetParam();
const uint32_t libyuv_fourcc = BufferFormatToFourCC(buffer_format);
if (libyuv_fourcc == static_cast<uint32_t>(libyuv::FOURCC_ANY)) {
LOG(WARNING) << gfx::BufferFormatToString(buffer_format)
<< " not supported, skipping test";
return;
}
std::unique_ptr<gfx::GpuMemoryBuffer> buffer(gl_.CreateGpuMemoryBuffer(
gfx::Size(kImageWidth, kImageHeight), buffer_format));
ASSERT_TRUE(buffer->Map());
ASSERT_NE(nullptr, buffer->memory(0));
ASSERT_NE(0, buffer->stride(0));
constexpr uint8_t color_rgba[] = {127u, 0u, 0u, 255u};
constexpr uint8_t color_bgra[] = {0u, 0u, 127u, 255u};
const size_t num_planes = NumberOfPlanesForLinearBufferFormat(buffer_format);
for (size_t plane = 0; plane < num_planes; ++plane) {
gl::GLImageTestSupport::SetBufferDataToColor(
kImageWidth, kImageHeight, buffer->stride(plane), plane, buffer_format,
color_rgba, static_cast<uint8_t*>(buffer->memory(plane)));
}
buffer->Unmap();
ASSERT_TRUE(buffer->Map());
ASSERT_NE(nullptr, buffer->memory(0));
ASSERT_NE(0, buffer->stride(0));
const uint8_t* data = static_cast<uint8_t*>(buffer->memory(0));
const int stride = buffer->stride(0);
// libyuv defines the formats as word-order.
uint8_t argb[kImageWidth * kImageHeight * 4] = {};
const int result = libyuv::ConvertToARGB(
data, stride * kImageWidth, argb, kImageWidth /* dst_stride_argb */,
0 /* crop_x */, 0 /* crop_y */, kImageWidth, kImageHeight,
kImageWidth /* rop_width */, kImageHeight /* crop_height */,
libyuv::kRotate0, libyuv_fourcc);
constexpr int max_error = 2;
ASSERT_EQ(result, 0) << gfx::BufferFormatToString(buffer_format);
int bad_count = 0;
for (int y = 0; y < kImageHeight; ++y) {
for (int x = 0; x < kImageWidth; ++x) {
int offset = y * kImageWidth + x * 4;
for (int c = 0; c < 4; ++c) {
// |argb| in word order is read as B, G, R, A on little endian .
const uint8_t actual = argb[offset + c];
const uint8_t expected = color_bgra[c];
EXPECT_NEAR(expected, actual, max_error)
<< " at " << x << ", " << y << " channel " << c;
bad_count += std::abs(actual - expected) > max_error;
// Exit early just so we don't spam the log but we print enough to
// hopefully make it easy to diagnose the issue.
ASSERT_LE(bad_count, 4);
}
}
}
buffer->Unmap();
}
// An end to end test that tests the whole GpuMemoryBuffer lifecycle.
TEST_P(GpuMemoryBufferTest, Lifecycle) {
const gfx::BufferFormat buffer_format = GetParam();
if (buffer_format == gfx::BufferFormat::R_8 &&
!gl_.GetCapabilities().texture_rg) {
LOG(WARNING) << "texture_rg not supported. Skipping test.";
return;
}
if (buffer_format == gfx::BufferFormat::RGBA_F16 &&
!gl_.GetCapabilities().texture_half_float_linear) {
LOG(WARNING) << "texture_half_float_linear not supported. Skipping test.";
return;
}
if (buffer_format == gfx::BufferFormat::RGBX_1010102 &&
!gl_.GetCapabilities().image_xb30) {
LOG(WARNING) << "image_xb30 not supported. Skipping test.";
return;
}
if (buffer_format == gfx::BufferFormat::BGRX_1010102 &&
!gl_.GetCapabilities().image_xr30) {
LOG(WARNING) << "image_xr30 not supported. Skipping test.";
return;
}
if (buffer_format == gfx::BufferFormat::YVU_420 ||
buffer_format == gfx::BufferFormat::YUV_420_BIPLANAR) {
LOG(WARNING) << "GLImageMemory doesn't support YUV formats, skipping test.";
return;
}
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
ASSERT_NE(0u, texture_id);
glBindTexture(GL_TEXTURE_2D, texture_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);
// Create the gpu memory buffer.
std::unique_ptr<gfx::GpuMemoryBuffer> buffer(gl_.CreateGpuMemoryBuffer(
gfx::Size(kImageWidth, kImageHeight), buffer_format));
// Map buffer for writing.
ASSERT_TRUE(buffer->Map());
ASSERT_NE(nullptr, buffer->memory(0));
ASSERT_NE(0, buffer->stride(0));
constexpr uint8_t pixel[] = {255u, 0u, 0u, 255u};
const size_t num_planes = NumberOfPlanesForLinearBufferFormat(buffer_format);
for (size_t plane = 0; plane < num_planes; ++plane) {
gl::GLImageTestSupport::SetBufferDataToColor(
kImageWidth, kImageHeight, buffer->stride(plane), plane, buffer_format,
pixel, static_cast<uint8_t*>(buffer->memory(0)));
}
buffer->Unmap();
// Create the image. This should add the image ID to the ImageManager.
GLuint image_id =
glCreateImageCHROMIUM(buffer->AsClientBuffer(), kImageWidth, kImageHeight,
InternalFormat(buffer_format));
ASSERT_NE(0u, image_id);
ASSERT_TRUE(gl_.decoder()->GetImageManagerForTest()->LookupImage(image_id) !=
nullptr);
// Bind the image.
glBindTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id);
// Build program, buffers and draw the texture.
GLuint vertex_shader =
GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShader);
GLuint fragment_shader =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShader);
GLuint program = GLTestHelper::SetupProgram(vertex_shader, fragment_shader);
ASSERT_NE(0u, program);
glUseProgram(program);
GLint sampler_location = glGetUniformLocation(program, "a_texture");
ASSERT_NE(-1, sampler_location);
glUniform1i(sampler_location, 0);
GLuint vbo =
GLTestHelper::SetupUnitQuad(glGetAttribLocation(program, "a_position"));
ASSERT_NE(0u, vbo);
glViewport(0, 0, kImageWidth, kImageHeight);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_TRUE(glGetError() == GL_NO_ERROR);
// Check if pixels match the values that were assigned to the mapped buffer.
GLTestHelper::CheckPixels(0, 0, kImageWidth, kImageHeight, 0, pixel, nullptr);
EXPECT_TRUE(GL_NO_ERROR == glGetError());
// Release the image.
glReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id);
// Clean up.
glDeleteProgram(program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glDeleteBuffers(1, &vbo);
glDestroyImageCHROMIUM(image_id);
glDeleteTextures(1, &texture_id);
}
#if defined(OS_LINUX)
// Test glCreateImageCHROMIUM with gfx::NATIVE_PIXMAP. Basically the test
// reproduces the situation where some dmabuf fds are available outside the
// gpu process and the user wants to import them using glCreateImageCHROMIUM.
// It can be the case when vaapi is setup in a media service hosted in a
// dedicated process, i.e. not the gpu process.
TEST_F(GpuMemoryBufferTestEGL, GLCreateImageCHROMIUMFromNativePixmap) {
SKIP_TEST_IF(!egl_gles2_initialized_);
// This extension is required for glCreateImageCHROMIUM on Linux.
SKIP_TEST_IF(!HasEGLExtension("EGL_EXT_image_dma_buf_import"));
// This extension is required for the test to work but not for the real
// world, see CreateNativePixmapHandle.
SKIP_TEST_IF(!HasEGLExtension("EGL_MESA_image_dma_buf_export"));
// This extension is required for glCreateImageCHROMIUM on Linux.
SKIP_TEST_IF(!HasGLExtension("GL_OES_EGL_image"));
gfx::BufferFormat format = gfx::BufferFormat::RGBX_8888;
gfx::Size size(kImageWidth, kImageHeight);
size_t buffer_size = gfx::BufferSizeForBufferFormat(size, format);
uint8_t pixel[] = {255u, 0u, 0u, 255u};
size_t plane = 0;
uint32_t stride = gfx::RowSizeForBufferFormat(size.width(), format, plane);
std::unique_ptr<uint8_t[]> pixels(new uint8_t[buffer_size]);
gl::GLImageTestSupport::SetBufferDataToColor(kImageWidth, kImageHeight,
stride, 0 /* plane */, format,
pixel, pixels.get());
// A real use case would be to export a VAAPI surface as dmabuf fds. But for
// simplicity the test gets them from a GL texture.
gfx::NativePixmapHandle native_pixmap_handle =
CreateNativePixmapHandle(format, size, pixels.get());
EXPECT_EQ(1u, native_pixmap_handle.planes.size());
// Initialize a GpuMemoryBufferHandle to wrap a native pixmap.
gfx::GpuMemoryBufferHandle handle;
handle.type = gfx::NATIVE_PIXMAP;
handle.native_pixmap_handle = std::move(native_pixmap_handle);
EXPECT_TRUE(handle.id.is_valid());
// Create a GMB to pass to glCreateImageCHROMIUM.
std::unique_ptr<gfx::GpuMemoryBuffer> buffer =
gpu::GpuMemoryBufferImplNativePixmap::CreateFromHandle(
native_pixmap_factory_.get(), std::move(handle), size, format,
gfx::BufferUsage::SCANOUT,
base::RepeatingCallback<void(const gpu::SyncToken&)>());
EXPECT_NE(nullptr, buffer.get());
EXPECT_TRUE(buffer->GetId().is_valid());
// Create the image. This should add the image ID to the ImageManager.
GLuint image_id = glCreateImageCHROMIUM(buffer->AsClientBuffer(),
size.width(), size.height(), GL_RGB);
EXPECT_NE(0u, image_id);
// In the tests the gl::GLImage is added into the ImageManager when calling
// GLManager::CreateImage. In real cases the GpuControl would not own the
// ImageManager. I.e. for the tests the ImageManager lives in the client side
// so there is no need to call glShallowFinishCHROMIUM().
EXPECT_TRUE(gl_.decoder()->GetImageManagerForTest()->LookupImage(image_id) !=
nullptr);
ASSERT_TRUE(glGetError() == GL_NO_ERROR);
// Need a texture to bind the image.
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
ASSERT_NE(0u, texture_id);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_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);
// Bind the image.
glBindTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id);
// Build program, buffers and draw the texture.
GLuint vertex_shader =
GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShader);
GLuint fragment_shader =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShader);
GLuint program = GLTestHelper::SetupProgram(vertex_shader, fragment_shader);
ASSERT_NE(0u, program);
glUseProgram(program);
GLint sampler_location = glGetUniformLocation(program, "a_texture");
ASSERT_NE(-1, sampler_location);
glUniform1i(sampler_location, 0);
GLuint vbo =
GLTestHelper::SetupUnitQuad(glGetAttribLocation(program, "a_position"));
ASSERT_NE(0u, vbo);
glViewport(0, 0, kImageWidth, kImageHeight);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_TRUE(glGetError() == GL_NO_ERROR);
// Check if pixels match the values that were assigned to the mapped buffer.
GLTestHelper::CheckPixels(0, 0, kImageWidth, kImageHeight, 0, pixel, nullptr);
EXPECT_TRUE(GL_NO_ERROR == glGetError());
// Release the image.
glReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id);
// Clean up.
glDeleteProgram(program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glDeleteBuffers(1, &vbo);
glDestroyImageCHROMIUM(image_id);
glDeleteTextures(1, &texture_id);
}
#endif // defined(OS_LINUX)
INSTANTIATE_TEST_SUITE_P(
GpuMemoryBufferTests,
GpuMemoryBufferTest,
::testing::Values(gfx::BufferFormat::R_8,
gfx::BufferFormat::BGR_565,
gfx::BufferFormat::RGBA_4444,
gfx::BufferFormat::RGBA_8888,
gfx::BufferFormat::RGBX_1010102,
gfx::BufferFormat::BGRX_1010102,
gfx::BufferFormat::BGRA_8888,
gfx::BufferFormat::RGBA_F16,
gfx::BufferFormat::YVU_420,
gfx::BufferFormat::YUV_420_BIPLANAR));
} // namespace gles2
} // namespace gpu