blob: bd2ad3a9839c9cd2196696887277e10eb9a1fbca [file]
// Copyright 2013 The Chromium Authors
// 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/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <GLES3/gl3.h>
#include <stdint.h>
#include <array>
#include "base/compiler_specific.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#define SHADER(src) #src
namespace gpu {
static const uint16_t kRedMask = 0xF800;
static const uint16_t kGreenMask = 0x07E0;
static const uint16_t kBlueMask = 0x001F;
// Color palette in 565 format.
static const auto kPalette = std::to_array<uint16_t>({
kGreenMask | kBlueMask, // Cyan.
kBlueMask | kRedMask, // Magenta.
kRedMask | kGreenMask, // Yellow.
0x0000, // Black.
kRedMask, // Red.
kGreenMask, // Green.
kBlueMask, // Blue.
0xFFFF, // White.
});
static const unsigned kBlockSize = 4;
static const unsigned kPaletteSize =
(kPalette.size() * sizeof(decltype(kPalette)::value_type)) /
sizeof(kPalette[0]);
static const unsigned kTextureWidth = kBlockSize * kPaletteSize;
static const unsigned kTextureHeight = kBlockSize;
static const char* extension(GLenum format) {
switch(format) {
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
return "GL_ANGLE_texture_compression_dxt1";
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
return "GL_ANGLE_texture_compression_dxt3";
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return "GL_ANGLE_texture_compression_dxt5";
default:
NOTREACHED();
}
}
// Index that chooses the given colors (color_0 and color_1),
// not the interpolated colors (color_2 and color_3).
static const uint16_t kColor0 = 0x0000;
static const uint16_t kColor1 = 0x5555;
static GLuint LoadCompressedTexture(const void* data,
GLsizeiptr size,
GLenum format,
GLsizei width,
GLsizei height) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glCompressedTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0, size, data);
return texture;
}
GLuint LoadTextureDXT1(bool alpha) {
const unsigned kStride = 4;
uint16_t data[kStride * kPaletteSize];
for (unsigned i = 0; i < kPaletteSize; ++i) {
// Each iteration defines a 4x4 block of texture.
unsigned j = kStride * i;
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_0.
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_1.
UNSAFE_TODO(data[j++]) = kColor0; // color index.
UNSAFE_TODO(data[j++]) = kColor1; // color index.
}
GLenum format = alpha ?
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
return LoadCompressedTexture(
data, sizeof(data), format, kTextureWidth, kTextureHeight);
}
GLuint LoadTextureDXT3() {
const unsigned kStride = 8;
const uint16_t kOpaque = 0xFFFF;
uint16_t data[kStride * kPaletteSize];
for (unsigned i = 0; i < kPaletteSize; ++i) {
// Each iteration defines a 4x4 block of texture.
unsigned j = kStride * i;
UNSAFE_TODO(data[j++]) = kOpaque; // alpha row 0.
UNSAFE_TODO(data[j++]) = kOpaque; // alpha row 1.
UNSAFE_TODO(data[j++]) = kOpaque; // alpha row 2.
UNSAFE_TODO(data[j++]) = kOpaque; // alpha row 3.
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_0.
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_1.
UNSAFE_TODO(data[j++]) = kColor0; // color index.
UNSAFE_TODO(data[j++]) = kColor1; // color index.
}
return LoadCompressedTexture(data,
sizeof(data),
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
kTextureWidth,
kTextureHeight);
}
GLuint LoadTextureDXT5() {
const unsigned kStride = 8;
const uint16_t kClear = 0x0000;
const uint16_t kAlpha7 = 0xFFFF; // Opaque alpha index.
uint16_t data[kStride * kPaletteSize];
for (unsigned i = 0; i < kPaletteSize; ++i) {
// Each iteration defines a 4x4 block of texture.
unsigned j = kStride * i;
UNSAFE_TODO(data[j++]) = kClear; // alpha_0 | alpha_1.
UNSAFE_TODO(data[j++]) = kAlpha7; // alpha index.
UNSAFE_TODO(data[j++]) = kAlpha7; // alpha index.
UNSAFE_TODO(data[j++]) = kAlpha7; // alpha index.
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_0.
UNSAFE_TODO(data[j++]) = kPalette[i]; // color_1.
UNSAFE_TODO(data[j++]) = kColor0; // color index.
UNSAFE_TODO(data[j++]) = kColor1; // color index.
}
return LoadCompressedTexture(data,
sizeof(data),
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
kTextureWidth,
kTextureHeight);
}
static void ToRGB888(uint16_t rgb565, uint8_t rgb888[]) {
uint8_t r5 = (rgb565 & kRedMask) >> 11;
uint8_t g6 = (rgb565 & kGreenMask) >> 5;
uint8_t b5 = (rgb565 & kBlueMask);
// Replicate upper bits to lower empty bits.
rgb888[0] = (r5 << 3) | (r5 >> 2);
UNSAFE_TODO(rgb888[1]) = (g6 << 2) | (g6 >> 4);
UNSAFE_TODO(rgb888[2]) = (b5 << 3) | (b5 >> 2);
}
class CompressedTextureTest : public ::testing::TestWithParam<GLenum> {
protected:
void SetUp() override {
GLManager::Options options;
options.size = gfx::Size(kTextureWidth, kTextureHeight);
gl_.Initialize(options);
}
void TearDown() override { gl_.Destroy(); }
GLuint LoadProgram() {
const char* v_shader_src = SHADER(
attribute vec2 a_position;
varying vec2 v_texcoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texcoord = (a_position + 1.0) * 0.5;
}
);
const char* f_shader_src = SHADER(
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texcoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texcoord);
}
);
return GLTestHelper::LoadProgram(v_shader_src, f_shader_src);
}
GLuint LoadTexture(GLenum format) {
switch (format) {
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: return LoadTextureDXT1(false);
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return LoadTextureDXT1(true);
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return LoadTextureDXT3();
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return LoadTextureDXT5();
default:
NOTREACHED();
}
}
private:
GLManager gl_;
};
// The test draws a texture in the given format and verifies that the drawn
// pixels are of the same color as the texture.
// The texture consists of 4x4 blocks of texels (same as DXT), one for each
// color defined in kPalette.
TEST_P(CompressedTextureTest, Draw) {
GLenum format = GetParam();
// This test is only valid if compressed texture extension is supported.
const char* ext = extension(format);
if (!GLTestHelper::HasExtension(ext))
return;
// Load shader program.
GLuint program = LoadProgram();
ASSERT_NE(program, 0u);
GLint position_loc = glGetAttribLocation(program, "a_position");
GLint texture_loc = glGetUniformLocation(program, "u_texture");
ASSERT_NE(position_loc, -1);
ASSERT_NE(texture_loc, -1);
glUseProgram(program);
// Load geometry.
GLuint vbo = GLTestHelper::SetupUnitQuad(position_loc);
ASSERT_NE(vbo, 0u);
// Load texture.
GLuint texture = LoadTexture(format);
ASSERT_NE(texture, 0u);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(texture_loc, 0);
// Draw.
glDrawArrays(GL_TRIANGLES, 0, 6);
glFlush();
// Verify results.
int origin[] = {0, 0};
uint8_t expected_rgba[] = {0, 0, 0, 255};
for (unsigned i = 0; i < kPaletteSize; ++i) {
origin[0] = kBlockSize * i;
ToRGB888(kPalette[i], expected_rgba);
EXPECT_TRUE(GLTestHelper::CheckPixels(origin[0], origin[1], kBlockSize,
kBlockSize, 0, expected_rgba,
nullptr));
}
GLTestHelper::CheckGLError("CompressedTextureTest.Draw", __LINE__);
}
static const GLenum kFormats[] = {
GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
};
INSTANTIATE_TEST_SUITE_P(Format,
CompressedTextureTest,
::testing::ValuesIn(kFormats));
class CompressedTextureTestES3 : public ::testing::Test {
protected:
void SetUp() override {
GLManager::Options options;
options.size = gfx::Size(kTextureWidth, kTextureHeight);
options.context_type = CONTEXT_TYPE_OPENGLES3;
gl_.Initialize(options);
}
void TearDown() override { gl_.Destroy(); }
private:
GLManager gl_;
};
// Test that compressed sub-image updates work when TEXTURE_BASE_LEVEL > 0.
// This is a workaround for a PowerVR driver bug where it miscomputes the
// offset.
TEST_F(CompressedTextureTestES3, ASTCCompressedSubImageWithBaseLevel) {
if (!GLTestHelper::HasExtension("GL_KHR_texture_compression_astc_ldr")) {
return;
}
#if BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/529932631): Skip on Adreno GPUs for the moment
// because of apparent driver bugs causing GL_INVALID_OPERATION
// during the glCompressedTexSubImage2D calls. Working around this
// bug by making the texture levels a multiple of the ASTC block
// size causes this test to no longer reproduce the Imagination
// driver bug it was intended to catch.
std::string renderer =
reinterpret_cast<const char*>(glGetString(GL_RENDERER));
if (renderer.find("Adreno") != std::string::npos) {
return;
}
#endif
// Use shaders that match the proof-of-concept.
// They don't use vertex attributes, but gl_VertexID to generate a quad.
const char* kVS =
"#version 300 es\n"
"out vec2 uv;\n"
"void main() {\n"
" vec2 p = vec2(gl_VertexID & 1, gl_VertexID >> 1);\n"
" uv = p;\n"
" gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n"
"}\n";
const char* kFS =
"#version 300 es\n"
"precision highp float;\n"
"uniform sampler2D t;\n"
"in vec2 uv;\n"
"out vec4 c;\n"
"void main() {\n"
" c = texture(t, uv);\n"
"}\n";
GLuint program = GLTestHelper::LoadProgram(kVS, kFS);
ASSERT_NE(program, 0u);
glUseProgram(program);
GLint tex_location = glGetUniformLocation(program, "t");
ASSERT_NE(tex_location, -1);
glUniform1i(tex_location, 0);
GLTestHelper::CheckGLError("Setup program", __LINE__);
// 8x5 ASTC format.
GLenum format = GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
constexpr GLsizei kWidth = 8;
constexpr GLsizei kHeight = 160;
constexpr GLsizei kLevels = 5;
std::vector<uint8_t> data(16, 0);
// Loop multiple times to increase chances of hitting OOB write/crash if
// workaround fails. Keep textures alive to groom the heap.
constexpr int kIterations = 16;
std::vector<GLuint> textures(kIterations);
glGenTextures(kIterations, textures.data());
for (int i = 0; i < kIterations; ++i) {
glBindTexture(GL_TEXTURE_2D, textures[i]);
glTexStorage2DEXT(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight);
GLTestHelper::CheckGLError("glTexStorage2D", __LINE__);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4);
GLTestHelper::CheckGLError("glTexParameteri", __LINE__);
// Draw using the program that samples the texture.
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFinish();
GLTestHelper::CheckGLError("Draw", __LINE__);
// Perform sub-image update to level 3.
// 1x5 pixels is 1 block for 8x5 ASTC, which is 16 bytes.
glCompressedTexSubImage2D(GL_TEXTURE_2D, 3, 0, 0, 1, 5, format,
static_cast<GLsizei>(data.size()), data.data());
GLTestHelper::CheckGLError("glCompressedTexSubImage2D level 3", __LINE__);
// Also try level 4 (which is the base level)
glCompressedTexSubImage2D(GL_TEXTURE_2D, 4, 0, 0, 1, 5, format,
static_cast<GLsizei>(data.size()), data.data());
GLTestHelper::CheckGLError("glCompressedTexSubImage2D level 4", __LINE__);
}
glDeleteTextures(kIterations, textures.data());
glDeleteProgram(program);
}
} // namespace gpu