blob: c0931796401aa7e69eb753366f41089c1cf14a0b [file]
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// BPTCCompressedTextureTest.cpp: Tests of the GL_EXT_texture_compression_bptc extension
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
const unsigned int kPixelTolerance = 1u;
// The pixel data represents a 4x4 pixel image with the left side colored red and the right side
// green. It was BC7 encoded using Microsoft's BC6HBC7Encoder.
const std::array<GLubyte, 16> kBC7Data4x4 = {0x50, 0x1f, 0xfc, 0xf, 0x0, 0xf0, 0xe3, 0xe1,
0xe1, 0xe1, 0xc1, 0xf, 0xfc, 0xc0, 0xf, 0xfc};
// The pixel data represents a 4x4 pixel image with the transparent black solid color.
// Sampling from a zero-filled block is undefined, so use a valid one.
const std::array<GLubyte, 16> kBC7BlackData4x4 = {0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// BC7 Mode 6 packs fields LSB-first across 128 bits:
// bits [0..6] = 0b1000000 (Mode 6: 7-bit RGBA endpoints + 1-bit p-bit)
// bits [7..13] = R0, [14..20] = R1, [21..27] = G0, [28..34] = G1,
// bits [35..41] = B0, [42..48] = B1, [49..55] = A0, [56..62] = A1,
// bit [63] = P0, [64] = P1, [65..127] = 16 texel indices (all 0 -> endpoint 0).
// Mode 6 with R0=0x7f (bits 7..13 -> byte 0 bit 7 = 0x80, byte 1 bits 0..5 = 0x3f),
// A0=0x7f (bits 49..55 -> byte 6 bits 1..7 = 0xfe), and P0=1 (bit 63 -> byte 7 = 0x80)
// -> endpoint 0 is solid RGBA(255, 0, 0, 255) (red).
constexpr std::array<GLubyte, 16> kBC7RedData4x4 = {0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
} // anonymous namespace
class BPTCCompressedTextureTest : public ANGLETest<>
{
protected:
BPTCCompressedTextureTest()
{
setWindowWidth(128);
setWindowHeight(128);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void testSetUp() override
{
constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
varying vec2 texcoord;
void main()
{
gl_Position = position;
texcoord = (position.xy * 0.5) + 0.5;
texcoord.y = 1.0 - texcoord.y;
})";
constexpr char kFS[] = R"(precision highp float;
uniform sampler2D tex;
varying vec2 texcoord;
void main()
{
gl_FragColor = texture2D(tex, texcoord);
})";
mTextureProgram = CompileProgram(kVS, kFS);
if (mTextureProgram == 0)
{
FAIL() << "shader compilation failed.";
}
mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");
ASSERT_GL_NO_ERROR();
}
void testTearDown() override { glDeleteProgram(mTextureProgram); }
void setupTextureParameters(GLuint texture)
{
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void drawTexture()
{
glUseProgram(mTextureProgram);
glUniform1i(mTextureUniformLocation, 0);
drawQuad(mTextureProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
}
GLuint mTextureProgram;
GLint mTextureUniformLocation;
};
class BPTCCompressedTextureTestES3 : public BPTCCompressedTextureTest
{
public:
BPTCCompressedTextureTestES3() : BPTCCompressedTextureTest() {}
};
// Test sampling from a BC7 non-SRGB image.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC7)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), kBC7Data4x4.data());
EXPECT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test sampling from a BC7 SRGB image.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC7SRGB)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), kBC7Data4x4.data());
EXPECT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test that using the BC6H floating point formats doesn't crash.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC6HNoCrash)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
// This fake pixel data represents a 4x4 pixel image.
// TODO(http://anglebug.com/40096529): Add pixel tests for these formats. These need HDR source
// images.
std::vector<GLubyte> data;
data.resize(16u, 0u);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, 4, 4, 0,
data.size(), data.data());
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, 4, 4, 0,
data.size(), data.data());
EXPECT_GL_NO_ERROR();
drawTexture();
}
// Test texStorage2D with a BPTC format.
TEST_P(BPTCCompressedTextureTestES3, CompressedTexStorage)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
GLTexture texture;
setupTextureParameters(texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4);
EXPECT_GL_NO_ERROR();
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), kBC7Data4x4.data());
EXPECT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test validation of glCompressedTexSubImage2D with BPTC formats
TEST_P(BPTCCompressedTextureTest, CompressedTexSubImageValidation)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
std::vector<GLubyte> data(16 * 2 * 2); // 2x2 blocks, thats 8x8 pixels.
// Size mip 0 to a large size.
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 8, 8, 0,
data.size(), data.data());
ASSERT_GL_NO_ERROR();
// Test a sub image with an offset that isn't a multiple of the block size.
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 3, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
// Test a sub image with a negative offset.
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_VALUE);
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_VALUE);
}
// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTest, CopyTexImage2DDisallowed)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 0, 0, 4, 4, 0);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTest, CopyTexSubImage2DDisallowed)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), kBC7Data4x4.data());
ASSERT_GL_NO_ERROR();
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 4, 4);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTestES3, CopyTexSubImage3DDisallowed)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 1);
ASSERT_GL_NO_ERROR();
glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 4, 4);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
// Test uploading texture data from a PBO to a texture.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexImage)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
GLBuffer buffer;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
ASSERT_GL_NO_ERROR();
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
// Destroy the data
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7BlackData4x4.size(), kBC7BlackData4x4.data(),
GL_STREAM_DRAW);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7BlackData4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
// Initialize again. This time, the texture's image is already allocated, so the PBO data
// upload could be directly done.
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test uploading texture data from a PBO to a non-zero base texture.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexImageNonZeroBase)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
GLBuffer buffer;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
ASSERT_GL_NO_ERROR();
glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
// Destroy the data
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7BlackData4x4.size(), kBC7BlackData4x4.data(),
GL_STREAM_DRAW);
glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7BlackData4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
// Initialize again. This time, the texture's image is already allocated, so the PBO data
// upload could be directly done.
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
kBC7Data4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test uploading texture data from a PBO to a texture allocated with texStorage2D.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexStorage)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
setupTextureParameters(texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4);
ASSERT_GL_NO_ERROR();
GLBuffer buffer;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
ASSERT_GL_NO_ERROR();
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
kBC7Data4x4.size(), nullptr);
ASSERT_GL_NO_ERROR();
drawTexture();
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
kPixelTolerance);
}
// Test validation of glCompressedTexSubImage3D with BPTC formats
TEST_P(BPTCCompressedTextureTestES3, CompressedTexSubImage3DValidation)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
GLTexture texture;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
std::vector<GLubyte> data(16 * 2 * 2); // 2x2x1 blocks, thats 8x8x1 pixels.
// Size mip 0 to a large size.
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 8, 8, 1, 0,
data.size(), data.data());
ASSERT_GL_NO_ERROR();
// Test a sub image with an offset that isn't a multiple of the block size.
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 2, 0, 0, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 2, 0, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
// Test a sub image with a negative offset.
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, 0, 0, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_VALUE);
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -1, 0, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_VALUE);
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -1, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
kBC7Data4x4.data());
ASSERT_GL_ERROR(GL_INVALID_VALUE);
}
// Test that uploading and sampling 3D BPTC mip levels smaller than a 4x4 block with depth > 1
// allocates a staging texture with sufficient depth at the selected mip subresource.
TEST_P(BPTCCompressedTextureTestES3, CompressedTexImage3DSubBlockMipDepth)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
constexpr char kVS[] = R"(#version 300 es
in vec4 a_position;
void main()
{
gl_Position = a_position;
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp sampler3D;
uniform sampler3D u_tex;
uniform float u_z;
out vec4 my_FragColor;
void main()
{
my_FragColor = texture(u_tex, vec3(0.5, 0.5, u_z));
})";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glUseProgram(program);
GLint zLoc = glGetUniformLocation(program, "u_z");
ASSERT_NE(-1, zLoc);
auto testSubBlockMipUploadAndSample = [&](GLint level, GLsizei width, GLsizei height,
GLsizei depth) {
GLTexture tex;
glBindTexture(GL_TEXTURE_3D, tex);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, level);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, level);
const size_t blocksPerSlice =
static_cast<size_t>((width + 3) / 4) * static_cast<size_t>((height + 3) / 4);
const size_t totalBlocks = blocksPerSlice * static_cast<size_t>(depth);
std::vector<GLubyte> data;
data.reserve(totalBlocks * 16);
// Fill slices [0, depth - 2] with transparentBlack and the last slice (depth - 1) with red.
for (size_t block = 0; block < totalBlocks; ++block)
{
const auto &blockData = (block < blocksPerSlice * static_cast<size_t>(depth - 1))
? kBC7BlackData4x4
: kBC7RedData4x4;
data.insert(data.end(), blockData.begin(), blockData.end());
}
glCompressedTexImage3D(GL_TEXTURE_3D, level, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, width,
height, depth, 0, static_cast<GLsizei>(data.size()), data.data());
ASSERT_GL_NO_ERROR();
glUniform1f(zLoc, 0.5f / static_cast<float>(depth));
drawQuad(program, "a_position", 0.5f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::transparentBlack, kPixelTolerance);
glUniform1f(zLoc, (static_cast<float>(depth) - 0.5f) / static_cast<float>(depth));
drawQuad(program, "a_position", 0.5f);
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
};
// Level 1 at 1024x2x1024 (height < 4 -> lodOffset = 1): inputRowPitch (4096) != mapped D3D11
// DepthPitch (32768), exercising the strided per-slice/per-row loop in LoadCompressedToNative.
// Without expanding staging depth by lodOffset, mip 1 only has 512 physical slices instead of
// 1024, writing 512 slices across a 16 MiB out-of-bounds span.
testSubBlockMipUploadAndSample(1, 1024, 2, 1024);
// Level 2 at 1x1x512 (width < 4, height < 4 -> lodOffset = 2): both inputDepthPitch and
// mapped D3D11 DepthPitch are 16 bytes, exercising the single contiguous memcpy fast-path
// (inputImageSize == outputImageSize) in LoadCompressedToNative.
testSubBlockMipUploadAndSample(2, 1, 1, 512);
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(BPTCCompressedTextureTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BPTCCompressedTextureTestES3);
ANGLE_INSTANTIATE_TEST_ES3(BPTCCompressedTextureTestES3);
class BPTCCompressedTextureTestES3WebGL : public BPTCCompressedTextureTestES3
{
protected:
BPTCCompressedTextureTestES3WebGL()
{
setWebGLCompatibilityEnabled(true);
setRobustResourceInit(true);
}
};
// Test that initializing a large 3D BPTC texture doesn't overflow the size calculation.
// This is a regression test for a bug where the size was computed as (width/4 * 16) * height *
// depth instead of (width/4 * 16) * (height/4) * depth.
TEST_P(BPTCCompressedTextureTestES3WebGL, DeferredInit3DOverflow)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
// The overflow happens in the D3D11 backend.
// The dimensions 2048x2048x320 were reported to trigger it.
// 2048/4 * 16 = 8192 (row pitch)
// 8192 * 2048 * 320 = 5,368,709,120, which wraps to 1,073,741,824 in 32-bit GLuint.
// The correct size is 1,342,177,280 (1.25 GB).
// Since the wrapped buggy size is smaller than the correct size, it triggers an OOB read.
// 1.25 GB is large enough that it might trigger GL_OUT_OF_MEMORY on some systems.
GLTexture tex;
glBindTexture(GL_TEXTURE_3D, tex);
{
ScopedIgnorePlatformMessages ignore;
glTexStorage3D(GL_TEXTURE_3D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 2048, 2048, 320);
}
GLenum err = glGetError();
// Allow GL_OUT_OF_MEMORY as the texture is large.
ASSERT_TRUE(err == GL_NO_ERROR || err == GL_OUT_OF_MEMORY || err == GL_INVALID_OPERATION);
if (err != GL_OUT_OF_MEMORY && err != GL_INVALID_OPERATION)
{
// Trigger deferred initialization by updating a small sub-region.
std::vector<GLubyte> data(16, 0);
glCompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 1,
GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 16, data.data());
err = glGetError();
EXPECT_TRUE(err == GL_NO_ERROR || err == GL_OUT_OF_MEMORY);
}
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BPTCCompressedTextureTestES3WebGL);
// The overflow happens in the "slow path" of initializeContents, which is only called if
// robust resource initialization is enabled. Since it is always enabled in WebGL, we
// enable it here to reproduce the bug.
ANGLE_INSTANTIATE_TEST(BPTCCompressedTextureTestES3WebGL, ES3_D3D11());