| // Copyright 2026 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/gl2chromium.h> | |
| #include <GLES2/gl2ext.h> | |
| #include <GLES2/gl2extchromium.h> | |
| #include <GLES3/gl3.h> | |
| #include <stdint.h> | |
| #include <array> | |
| #include <vector> | |
| #include "base/containers/span.h" | |
| #include "base/logging.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/tests/gl_manager.h" | |
| #include "gpu/command_buffer/tests/gl_test_utils.h" | |
| #include "gpu/config/gpu_driver_bug_workarounds.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace gpu { | |
| class GLTextureTest : public testing::TestWithParam<bool> { | |
| protected: | |
| static constexpr int kWindowWidth = 128; | |
| static constexpr int kWindowHeight = 128; | |
| void SetUp() override { | |
| GLManager::Options options; | |
| options.size = gfx::Size(kWindowWidth, kWindowHeight); | |
| options.context_type = CONTEXT_TYPE_OPENGLES3; | |
| GpuDriverBugWorkarounds workarounds; | |
| workarounds.upload_oversized_mip_levels_via_unpack_buffer = GetParam(); | |
| gl_.InitializeWithWorkarounds(options, workarounds); | |
| } | |
| void TearDown() override { gl_.Destroy(); } | |
| bool IsApplicable() const { return gl_.IsInitialized(); } | |
| GLuint SetupDrawProgram() { | |
| constexpr const char kVertexShader[] = | |
| "#version 300 es\n" | |
| "in vec2 a_position;\n" | |
| "out vec2 v_texCoord;\n" | |
| "void main() {\n" | |
| " gl_Position = vec4(a_position, 0.0, 1.0);\n" | |
| " v_texCoord = 0.5 * (a_position + vec2(1.0, 1.0));\n" | |
| "}\n"; | |
| constexpr const char kFragmentShader[] = | |
| "#version 300 es\n" | |
| "precision mediump float;\n" | |
| "uniform sampler2D u_sampler;\n" | |
| "in vec2 v_texCoord;\n" | |
| "out vec4 o_color;\n" | |
| "void main() {\n" | |
| " o_color = texture(u_sampler, v_texCoord);\n" | |
| "}\n"; | |
| GLuint program = GLTestHelper::LoadProgram(kVertexShader, kFragmentShader); | |
| if (!program) { | |
| return 0; | |
| } | |
| glUseProgram(program); | |
| GLint sampler_loc = glGetUniformLocation(program, "u_sampler"); | |
| glUniform1i(sampler_loc, 0); | |
| glActiveTexture(GL_TEXTURE0); | |
| return program; | |
| } | |
| void DrawQuad(GLuint program) { | |
| GLint position_loc = glGetAttribLocation(program, "a_position"); | |
| GLuint vbo = GLTestHelper::SetupUnitQuad(position_loc); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| glDeleteBuffers(1, &vbo); | |
| } | |
| GLManager gl_; | |
| }; | |
| // Test that defining an oversized nonzero mip level on an uncompressed texture | |
| // succeeds and renders correctly. | |
| TEST_P(GLTextureTest, OversizedMipLevelsUncompressed) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| GLuint program = SetupDrawProgram(); | |
| ASSERT_NE(program, 0u); | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // Upload Level 0: 64x64 solid red. | |
| constexpr int kLevel0Width = 64; | |
| constexpr int kLevel0Height = 64; | |
| std::vector<uint32_t> red_data(kLevel0Width * kLevel0Height, 0xFF0000FF); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kLevel0Width, kLevel0Height, 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, red_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Upload Level 1: 128x128 solid green (oversized relative to Level 0). | |
| constexpr int kLevel1Width = 128; | |
| constexpr int kLevel1Height = 128; | |
| std::vector<uint32_t> green_data(kLevel1Width * kLevel1Height, 0xFF00FF00); | |
| glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kLevel1Width, kLevel1Height, 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, green_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Verify Level 0 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | |
| DrawQuad(program); | |
| const uint8_t kRed[4] = {255, 0, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 0, | |
| kRed, nullptr)); | |
| // Verify Level 1 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | |
| DrawQuad(program); | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 0, | |
| kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteProgram(program); | |
| } | |
| // Test that defining an oversized nonzero mip level on an ETC compressed | |
| // texture succeeds and renders correctly. | |
| TEST_P(GLTextureTest, OversizedMipLevelsCompressedETC) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| const bool has_etc = | |
| GLTestHelper::HasExtension("GL_OES_compressed_ETC2_RGB8_texture") || | |
| GLTestHelper::HasExtension("GL_ARB_ES3_compatibility"); | |
| // In OpenGL ES 3.0 core, ETC2 is always supported. On desktop GL, check for | |
| // extension. | |
| if (!has_etc && | |
| !GLTestHelper::HasExtension("GL_OES_compressed_ETC2_RGB8_texture")) { | |
| LOG(INFO) << "ETC2 not supported, skipping test"; | |
| return; | |
| } | |
| GLuint program = SetupDrawProgram(); | |
| ASSERT_NE(program, 0u); | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // ETC2 RGB8: 8 bytes per 4x4 block. | |
| // Level 0: 64x64 (256 blocks = 2048 bytes) solid red. | |
| // Byte 0 = 0xFF (R1=15, R2=15), Bytes 1..7 = 0x00. Decodes to (255, 2, 2, | |
| // 255). | |
| constexpr int kLevel0Width = 64; | |
| constexpr int kLevel0Height = 64; | |
| constexpr size_t kLevel0Blocks = (kLevel0Width / 4) * (kLevel0Height / 4); | |
| constexpr size_t kLevel0Bytes = kLevel0Blocks * 8; | |
| std::vector<uint8_t> red_etc_data(kLevel0Bytes, 0); | |
| for (size_t b = 0; b < kLevel0Blocks; ++b) { | |
| red_etc_data[b * 8 + 0] = 0xFF; | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, kLevel0Width, kLevel0Height, 0, | |
| static_cast<GLsizei>(kLevel0Bytes), red_etc_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Level 1: 128x128 (1024 blocks = 8192 bytes) solid green. | |
| // Byte 1 = 0xFF (G1=15, G2=15), Bytes 0, 2..7 = 0x00. Decodes to (2, 255, 2, | |
| // 255). | |
| constexpr int kLevel1Width = 128; | |
| constexpr int kLevel1Height = 128; | |
| constexpr size_t kLevel1Blocks = (kLevel1Width / 4) * (kLevel1Height / 4); | |
| constexpr size_t kLevel1Bytes = kLevel1Blocks * 8; | |
| std::vector<uint8_t> green_etc_data(kLevel1Bytes); | |
| for (size_t b = 0; b < kLevel1Blocks; ++b) { | |
| green_etc_data[b * 8 + 1] = 0xFF; | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, kLevel1Width, kLevel1Height, 0, | |
| static_cast<GLsizei>(kLevel1Bytes), green_etc_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Verify Level 0 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | |
| DrawQuad(program); | |
| const uint8_t kEtcRed[4] = {255, 2, 2, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 1, | |
| kEtcRed, nullptr)); | |
| // Verify Level 1 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | |
| DrawQuad(program); | |
| // Bugs in the PowerVR driver prevent full redefinition of oversized | |
| // compressed mip levels when level 0 is already defined, because the driver | |
| // restricts compressed level 1 storage to the slot allocated by the level 0 | |
| // chain. Check only the lower-left quadrant of the rendered output. | |
| const uint8_t kEtcGreen[4] = {2, 255, 2, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels( | |
| 0, 0, kWindowWidth / 4, kWindowHeight / 4, 1, kEtcGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteProgram(program); | |
| } | |
| // Test that defining an oversized nonzero mip level on an ASTC compressed | |
| // texture succeeds and renders correctly. | |
| TEST_P(GLTextureTest, OversizedMipLevelsCompressedASTC) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| const bool has_astc = | |
| GLTestHelper::HasExtension("GL_KHR_texture_compression_astc_ldr") || | |
| GLTestHelper::HasExtension("GL_OES_texture_compression_astc"); | |
| if (!has_astc) { | |
| LOG(INFO) << "ASTC not supported, skipping test"; | |
| return; | |
| } | |
| GLuint program = SetupDrawProgram(); | |
| ASSERT_NE(program, 0u); | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // ASTC 4x4 void-extent solid color blocks: 16 bytes per block. | |
| constexpr auto kAstcRedBlock = | |
| std::to_array<uint8_t>({0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}); | |
| constexpr auto kAstcGreenBlock = | |
| std::to_array<uint8_t>({0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF}); | |
| // Level 0: 64x64 (256 blocks = 4096 bytes) solid red. | |
| constexpr int kLevel0Width = 64; | |
| constexpr int kLevel0Height = 64; | |
| constexpr size_t kLevel0Blocks = (kLevel0Width / 4) * (kLevel0Height / 4); | |
| constexpr size_t kLevel0Bytes = kLevel0Blocks * 16; | |
| std::vector<uint8_t> red_astc_data(kLevel0Bytes); | |
| for (size_t b = 0; b < kLevel0Blocks; ++b) { | |
| base::span(red_astc_data).subspan(b * 16u, 16u).copy_from(kAstcRedBlock); | |
| } | |
| glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_4x4_KHR, | |
| kLevel0Width, kLevel0Height, 0, | |
| static_cast<GLsizei>(kLevel0Bytes), | |
| red_astc_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Level 1: 128x128 (1024 blocks = 16384 bytes) solid green. | |
| constexpr int kLevel1Width = 128; | |
| constexpr int kLevel1Height = 128; | |
| constexpr size_t kLevel1Blocks = (kLevel1Width / 4) * (kLevel1Height / 4); | |
| constexpr size_t kLevel1Bytes = kLevel1Blocks * 16; | |
| std::vector<uint8_t> green_astc_data(kLevel1Bytes); | |
| for (size_t b = 0; b < kLevel1Blocks; ++b) { | |
| base::span(green_astc_data) | |
| .subspan(b * 16u, 16u) | |
| .copy_from(kAstcGreenBlock); | |
| } | |
| glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_ASTC_4x4_KHR, | |
| kLevel1Width, kLevel1Height, 0, | |
| static_cast<GLsizei>(kLevel1Bytes), | |
| green_astc_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Verify Level 0 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | |
| DrawQuad(program); | |
| const uint8_t kRed[4] = {255, 0, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 0, | |
| kRed, nullptr)); | |
| // Verify Level 1 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | |
| DrawQuad(program); | |
| // Bugs in the PowerVR driver prevent full redefinition of oversized | |
| // compressed mip levels when level 0 is already defined, because the driver | |
| // restricts compressed level 1 storage to the slot allocated by the level 0 | |
| // chain. Check only the lower-left quadrant of the rendered output. | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth / 4, | |
| kWindowHeight / 4, 0, kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteProgram(program); | |
| } | |
| // Test that defining an oversized nonzero mip level (level 0 257x257, level 1 | |
| // 256x256) on an ASTC 12x12 compressed texture does not crash during the | |
| // level 1 upload. | |
| TEST_P(GLTextureTest, OversizedMipLevelsCompressedASTC12x12_256x256) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| const bool has_astc = | |
| GLTestHelper::HasExtension("GL_KHR_texture_compression_astc_ldr") || | |
| GLTestHelper::HasExtension("GL_OES_texture_compression_astc"); | |
| if (!has_astc) { | |
| LOG(INFO) << "ASTC not supported, skipping test"; | |
| return; | |
| } | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // ASTC void-extent solid color block: 16 bytes per block. | |
| constexpr auto kAstcBlock = | |
| std::to_array<uint8_t>({0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}); | |
| // Level 0: 257x257. In 12x12 blocks: ceil(257/12) = 22 blocks per axis. | |
| constexpr int kLevel0Width = 257; | |
| constexpr int kLevel0Height = 257; | |
| constexpr size_t kLevel0Blocks = 22 * 22; | |
| constexpr size_t kLevel0Bytes = kLevel0Blocks * 16; | |
| std::vector<uint8_t> level0_data(kLevel0Bytes); | |
| for (size_t b = 0; b < kLevel0Blocks; ++b) { | |
| base::span(level0_data).subspan(b * 16u, 16u).copy_from(kAstcBlock); | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_12x12_KHR, kLevel0Width, | |
| kLevel0Height, 0, static_cast<GLsizei>(kLevel0Bytes), level0_data.data()); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Level 1: 256x256. In 12x12 blocks: ceil(256/12) = 22 blocks per axis. | |
| constexpr int kLevel1Width = 256; | |
| constexpr int kLevel1Height = 256; | |
| constexpr size_t kLevel1Blocks = 22 * 22; | |
| constexpr size_t kLevel1Bytes = kLevel1Blocks * 16; | |
| std::vector<uint8_t> level1_data(kLevel1Bytes); | |
| for (size_t b = 0; b < kLevel1Blocks; ++b) { | |
| base::span(level1_data).subspan(b * 16u, 16u).copy_from(kAstcBlock); | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_ASTC_12x12_KHR, kLevel1Width, | |
| kLevel1Height, 0, static_cast<GLsizei>(kLevel1Bytes), level1_data.data()); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glFinish(); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glDeleteTextures(1, &tex); | |
| } | |
| // Test that defining an oversized nonzero mip level (level 0 257x257, level 1 | |
| // 192x192) on an ASTC 12x12 compressed texture does not crash during the | |
| // level 1 upload. | |
| TEST_P(GLTextureTest, OversizedMipLevelsCompressedASTC12x12_192x192) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| const bool has_astc = | |
| GLTestHelper::HasExtension("GL_KHR_texture_compression_astc_ldr") || | |
| GLTestHelper::HasExtension("GL_OES_texture_compression_astc"); | |
| if (!has_astc) { | |
| LOG(INFO) << "ASTC not supported, skipping test"; | |
| return; | |
| } | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // ASTC void-extent solid color block: 16 bytes per block. | |
| constexpr auto kAstcBlock = | |
| std::to_array<uint8_t>({0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}); | |
| // Level 0: 257x257. In 12x12 blocks: ceil(257/12) = 22 blocks per axis. | |
| constexpr int kLevel0Width = 257; | |
| constexpr int kLevel0Height = 257; | |
| constexpr size_t kLevel0Blocks = 22 * 22; | |
| constexpr size_t kLevel0Bytes = kLevel0Blocks * 16; | |
| std::vector<uint8_t> level0_data(kLevel0Bytes); | |
| for (size_t b = 0; b < kLevel0Blocks; ++b) { | |
| base::span(level0_data).subspan(b * 16u, 16u).copy_from(kAstcBlock); | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_12x12_KHR, kLevel0Width, | |
| kLevel0Height, 0, static_cast<GLsizei>(kLevel0Bytes), level0_data.data()); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Level 1: 192x192. In 12x12 blocks: ceil(192/12) = 16 blocks per axis. | |
| constexpr int kLevel1Width = 192; | |
| constexpr int kLevel1Height = 192; | |
| constexpr size_t kLevel1Blocks = 16 * 16; | |
| constexpr size_t kLevel1Bytes = kLevel1Blocks * 16; | |
| std::vector<uint8_t> level1_data(kLevel1Bytes); | |
| for (size_t b = 0; b < kLevel1Blocks; ++b) { | |
| base::span(level1_data).subspan(b * 16u, 16u).copy_from(kAstcBlock); | |
| } | |
| glCompressedTexImage2D( | |
| GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_ASTC_12x12_KHR, kLevel1Width, | |
| kLevel1Height, 0, static_cast<GLsizei>(kLevel1Bytes), level1_data.data()); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glFinish(); | |
| ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glDeleteTextures(1, &tex); | |
| } | |
| // Test that defining an oversized nonzero mip level on a DXT compressed texture | |
| // succeeds and renders correctly. | |
| TEST_P(GLTextureTest, OversizedMipLevelsCompressedDXT) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| const bool has_dxt = | |
| GLTestHelper::HasExtension("GL_EXT_texture_compression_dxt1") || | |
| GLTestHelper::HasExtension("GL_EXT_texture_compression_s3tc") || | |
| GLTestHelper::HasExtension("GL_ANGLE_texture_compression_dxt1"); | |
| if (!has_dxt) { | |
| LOG(INFO) << "DXT1 not supported, skipping test"; | |
| return; | |
| } | |
| GLuint program = SetupDrawProgram(); | |
| ASSERT_NE(program, 0u); | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // DXT1: 8 bytes per 4x4 block. | |
| constexpr auto kDxtRedBlock = | |
| std::to_array<uint8_t>({0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); | |
| constexpr auto kDxtGreenBlock = | |
| std::to_array<uint8_t>({0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); | |
| // Level 0: 64x64 (256 blocks = 2048 bytes) solid red. | |
| constexpr int kLevel0Width = 64; | |
| constexpr int kLevel0Height = 64; | |
| constexpr size_t kLevel0Blocks = (kLevel0Width / 4) * (kLevel0Height / 4); | |
| constexpr size_t kLevel0Bytes = kLevel0Blocks * 8; | |
| std::vector<uint8_t> red_dxt_data(kLevel0Bytes); | |
| for (size_t b = 0; b < kLevel0Blocks; ++b) { | |
| base::span(red_dxt_data).subspan(b * 8u, 8u).copy_from(kDxtRedBlock); | |
| } | |
| glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, | |
| kLevel0Width, kLevel0Height, 0, | |
| static_cast<GLsizei>(kLevel0Bytes), | |
| red_dxt_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Level 1: 128x128 (1024 blocks = 8192 bytes) solid green. | |
| constexpr int kLevel1Width = 128; | |
| constexpr int kLevel1Height = 128; | |
| constexpr size_t kLevel1Blocks = (kLevel1Width / 4) * (kLevel1Height / 4); | |
| constexpr size_t kLevel1Bytes = kLevel1Blocks * 8; | |
| std::vector<uint8_t> green_dxt_data(kLevel1Bytes); | |
| for (size_t b = 0; b < kLevel1Blocks; ++b) { | |
| base::span(green_dxt_data).subspan(b * 8u, 8u).copy_from(kDxtGreenBlock); | |
| } | |
| glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, | |
| kLevel1Width, kLevel1Height, 0, | |
| static_cast<GLsizei>(kLevel1Bytes), | |
| green_dxt_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Verify Level 0 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | |
| DrawQuad(program); | |
| const uint8_t kRed[4] = {255, 0, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 0, | |
| kRed, nullptr)); | |
| // Verify Level 1 sampling. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | |
| DrawQuad(program); | |
| // Bugs in the PowerVR driver prevent full redefinition of oversized | |
| // compressed mip levels when level 0 is already defined, because the driver | |
| // restricts compressed level 1 storage to the slot allocated by the level 0 | |
| // chain. Check only the lower-left quadrant of the rendered output. | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth / 4, | |
| kWindowHeight / 4, 0, kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteProgram(program); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(GLTextureTestWithWorkaround, | |
| GLTextureTest, | |
| testing::Bool()); | |
| // Mipmap tests reuse most of the GLTextureTest code but enable the | |
| // recreate_mipmap_levels_before_generate workaround instead. | |
| class GLTextureMipmapTest : public GLTextureTest { | |
| protected: | |
| void SetUp() override { | |
| GLManager::Options options; | |
| options.size = gfx::Size(kWindowWidth, kWindowHeight); | |
| options.context_type = CONTEXT_TYPE_OPENGLES3; | |
| GpuDriverBugWorkarounds workarounds; | |
| workarounds.recreate_mipmap_levels_before_generate = GetParam(); | |
| gl_.InitializeWithWorkarounds(options, workarounds); | |
| } | |
| }; | |
| // Test that calling glGenerateMipmap on a 2D texture with existing mismatched | |
| // (undersized) nonzero mip levels properly regenerates and renders the levels. | |
| TEST_P(GLTextureMipmapTest, GenerateMipmapWithMismatchedLevels2D) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| GLuint program = SetupDrawProgram(); | |
| EXPECT_NE(program, 0u); | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // Upload Level 1: 1x1 solid red (mismatched dimensions relative to Level 0). | |
| const uint32_t red_pixel = 0xFF0000FF; | |
| glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| &red_pixel); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Upload Level 0: 64x64 solid green. | |
| constexpr int kLevel0Width = 64; | |
| constexpr int kLevel0Height = 64; | |
| std::vector<uint32_t> green_data(kLevel0Width * kLevel0Height, 0xFF00FF00); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kLevel0Width, kLevel0Height, 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, green_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Generate mipmaps. | |
| glGenerateMipmap(GL_TEXTURE_2D); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Verify Level 1 sampling renders green. | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | |
| DrawQuad(program); | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kWindowWidth, kWindowHeight, 0, | |
| kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteProgram(program); | |
| } | |
| // Test that calling glGenerateMipmap on a 3D texture with mismatched nonzero | |
| // levels works correctly. | |
| TEST_P(GLTextureMipmapTest, GenerateMipmapWithMismatchedLevels3D) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| #if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY) | |
| // TODO(crbug.com/551065272): Figure out why this fails with an incomplete | |
| // framebuffer error on Android x86/x64. | |
| GTEST_SKIP() << "Skipping test on Android x86/x64"; | |
| #else | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_3D, tex); | |
| glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // Upload Level 1: 1x1x1 solid red. | |
| const uint32_t red_pixel = 0xFF0000FF; | |
| glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 1, 1, 1, 0, GL_RGBA, | |
| GL_UNSIGNED_BYTE, &red_pixel); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Upload Level 0: 16x16x4 solid green. | |
| constexpr int kLevel0Width = 16; | |
| constexpr int kLevel0Height = 16; | |
| constexpr int kLevel0Depth = 4; | |
| std::vector<uint32_t> green_data(kLevel0Width * kLevel0Height * kLevel0Depth, | |
| 0xFF00FF00); | |
| glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kLevel0Width, kLevel0Height, | |
| kLevel0Depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, green_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glGenerateMipmap(GL_TEXTURE_3D); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Setup FBO for verifying texture contents. | |
| constexpr int kLevel1Width = 8; | |
| constexpr int kLevel1Height = 8; | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| GLuint fbo = 0; | |
| glGenFramebuffers(1, &fbo); | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); | |
| // Verify the contents of layer 0. | |
| glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 1, | |
| 0); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kLevel1Width, kLevel1Height, 0, | |
| kGreen, nullptr)); | |
| // Verify the contents of layer 1. | |
| glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 1, | |
| 1); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kLevel1Width, kLevel1Height, 0, | |
| kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteFramebuffers(1, &fbo); | |
| #endif // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY) | |
| } | |
| // Test that calling glGenerateMipmap on a 2D array texture with mismatched | |
| // nonzero levels works correctly. | |
| TEST_P(GLTextureMipmapTest, GenerateMipmapWithMismatchedLevels2DArray) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_2D_ARRAY, tex); | |
| glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| // Upload Level 1: 1x1x2 solid red. | |
| const uint32_t red_pixels[2] = {0xFF0000FF, 0xFF0000FF}; | |
| glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 1, 1, 2, 0, GL_RGBA, | |
| GL_UNSIGNED_BYTE, red_pixels); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Upload Level 0: 16x16x2 solid green. | |
| constexpr int kLevel0Width = 16; | |
| constexpr int kLevel0Height = 16; | |
| constexpr int kLevel0Layers = 2; | |
| std::vector<uint32_t> green_data(kLevel0Width * kLevel0Height * kLevel0Layers, | |
| 0xFF00FF00); | |
| glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kLevel0Width, kLevel0Height, | |
| kLevel0Layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, green_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| glGenerateMipmap(GL_TEXTURE_2D_ARRAY); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Setup FBO for verifying texture contents. | |
| constexpr int kLevel1Width = 8; | |
| constexpr int kLevel1Height = 8; | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| GLuint fbo = 0; | |
| glGenFramebuffers(1, &fbo); | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); | |
| // Verify the contents of layer 0. | |
| glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 1, | |
| 0); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kLevel1Width, kLevel1Height, 0, | |
| kGreen, nullptr)); | |
| // Verify the contents of layer 1. | |
| glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 1, | |
| 1); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kLevel1Width, kLevel1Height, 0, | |
| kGreen, nullptr)); | |
| glDeleteTextures(1, &tex); | |
| glDeleteFramebuffers(1, &fbo); | |
| } | |
| // Test that calling glGenerateMipmap on a cube map with mismatched nonzero | |
| // levels works correctly. | |
| TEST_P(GLTextureMipmapTest, GenerateMipmapWithMismatchedLevelsCubeMap) { | |
| if (!IsApplicable()) { | |
| return; | |
| } | |
| GLuint tex = 0; | |
| glGenTextures(1, &tex); | |
| glBindTexture(GL_TEXTURE_CUBE_MAP, tex); | |
| glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| const uint32_t red_pixel = 0xFF0000FF; | |
| for (int face = 0; face < 6; ++face) { | |
| glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 1, GL_RGBA8, 1, 1, 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, &red_pixel); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| } | |
| constexpr int kLevel0Width = 16; | |
| constexpr int kLevel0Height = 16; | |
| std::vector<uint32_t> green_data(kLevel0Width * kLevel0Height, 0xFF00FF00); | |
| for (int face = 0; face < 6; ++face) { | |
| glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA8, | |
| kLevel0Width, kLevel0Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| green_data.data()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| } | |
| glGenerateMipmap(GL_TEXTURE_CUBE_MAP); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| // Setup FBO for verifying texture contents. | |
| constexpr int kLevel1Width = 8; | |
| constexpr int kLevel1Height = 8; | |
| const uint8_t kGreen[4] = {0, 255, 0, 255}; | |
| GLuint fbo = 0; | |
| glGenFramebuffers(1, &fbo); | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); | |
| for (int face = 0; face < 6; ++face) { | |
| // Verify the contents of the face. | |
| glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, tex, 1); | |
| EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), | |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); | |
| EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kLevel1Width, kLevel1Height, 0, | |
| kGreen, nullptr)); | |
| } | |
| glDeleteTextures(1, &tex); | |
| glDeleteFramebuffers(1, &fbo); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(GLTextureMipmapTestWithWorkaround, | |
| GLTextureMipmapTest, | |
| testing::Bool()); | |
| } // namespace gpu |