blob: 09dfd8f0be2ce9366f44e14724b49f32d56a01b1 [file] [log] [blame]
//
// Copyright 2017 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.
//
// RobustResourceInitTest: Tests for GL_ANGLE_robust_resource_initialization.
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
namespace angle
{
constexpr char kSimpleTextureVertexShader[] =
"#version 300 es\n"
"in vec4 position;\n"
"out vec2 texcoord;\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
" texcoord = vec2(position.xy * 0.5 - 0.5);\n"
"}";
// TODO(jmadill): Would be useful in a shared place in a utils folder.
void UncompressDXTBlock(int destX,
int destY,
int destWidth,
const std::vector<uint8_t> &src,
int srcOffset,
GLenum format,
std::vector<GLColor> *colorsOut)
{
auto make565 = [src](int offset) {
return static_cast<int>(src[offset + 0]) + static_cast<int>(src[offset + 1]) * 256;
};
auto make8888From565 = [](int c) {
return GLColor(
static_cast<GLubyte>(floor(static_cast<float>((c >> 11) & 0x1F) * (255.0f / 31.0f))),
static_cast<GLubyte>(floor(static_cast<float>((c >> 5) & 0x3F) * (255.0f / 63.0f))),
static_cast<GLubyte>(floor(static_cast<float>((c >> 0) & 0x1F) * (255.0f / 31.0f))),
255);
};
auto mix = [](int mult, GLColor c0, GLColor c1, float div) {
GLColor r = GLColor::transparentBlack;
for (int ii = 0; ii < 4; ++ii)
{
r[ii] = static_cast<GLubyte>(floor(static_cast<float>(c0[ii] * mult + c1[ii]) / div));
}
return r;
};
bool isDXT1 =
(format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT);
int colorOffset = srcOffset + (isDXT1 ? 0 : 8);
int color0 = make565(colorOffset + 0);
int color1 = make565(colorOffset + 2);
bool c0gtc1 = color0 > color1 || !isDXT1;
GLColor rgba0 = make8888From565(color0);
GLColor rgba1 = make8888From565(color1);
std::array<GLColor, 4> colors = {{rgba0, rgba1,
c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
c0gtc1 ? mix(2, rgba1, rgba0, 3) : GLColor::black}};
// Original comment preserved below for posterity:
// "yea I know there is a lot of math in this inner loop. so sue me."
for (int yy = 0; yy < 4; ++yy)
{
uint8_t pixels = src[colorOffset + 4 + yy];
for (int xx = 0; xx < 4; ++xx)
{
uint8_t code = (pixels >> (xx * 2)) & 0x3;
GLColor srcColor = colors[code];
uint8_t alpha = 0;
switch (format)
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
alpha = 255;
break;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
alpha = (code == 3 && !c0gtc1) ? 0 : 255;
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
{
uint8_t alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
uint8_t alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
alpha = alpha1 | (alpha1 << 4);
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
{
uint8_t alpha0 = src[srcOffset + 0];
uint8_t alpha1 = src[srcOffset + 1];
int alphaOff = (yy >> 1) * 3 + 2;
uint32_t alphaBits =
static_cast<uint32_t>(src[srcOffset + alphaOff + 0]) +
static_cast<uint32_t>(src[srcOffset + alphaOff + 1]) * 256 +
static_cast<uint32_t>(src[srcOffset + alphaOff + 2]) * 65536;
int alphaShift = (yy % 2) * 12 + xx * 3;
uint8_t alphaCode = static_cast<uint8_t>((alphaBits >> alphaShift) & 0x7);
if (alpha0 > alpha1)
{
switch (alphaCode)
{
case 0:
alpha = alpha0;
break;
case 1:
alpha = alpha1;
break;
default:
// TODO(jmadill): fix rounding
alpha = ((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7;
break;
}
}
else
{
switch (alphaCode)
{
case 0:
alpha = alpha0;
break;
case 1:
alpha = alpha1;
break;
case 6:
alpha = 0;
break;
case 7:
alpha = 255;
break;
default:
// TODO(jmadill): fix rounding
alpha = ((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5;
break;
}
}
}
break;
default:
ASSERT_FALSE(true);
break;
}
int dstOff = ((destY + yy) * destWidth + destX + xx);
(*colorsOut)[dstOff] = GLColor(srcColor[0], srcColor[1], srcColor[2], alpha);
}
}
}
int GetBlockSize(GLenum format)
{
bool isDXT1 =
format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
return isDXT1 ? 8 : 16;
}
std::vector<GLColor> UncompressDXTIntoSubRegion(int width,
int height,
int subX0,
int subY0,
int subWidth,
int subHeight,
const std::vector<uint8_t> &data,
GLenum format)
{
std::vector<GLColor> dest(width * height, GLColor::transparentBlack);
if ((width % 4) != 0 || (height % 4) != 0 || (subX0 % 4) != 0 || (subY0 % 4) != 0 ||
(subWidth % 4) != 0 || (subHeight % 4) != 0)
{
std::cout << "Implementation error in UncompressDXTIntoSubRegion.";
return dest;
}
int blocksAcross = subWidth / 4;
int blocksDown = subHeight / 4;
int blockSize = GetBlockSize(format);
for (int yy = 0; yy < blocksDown; ++yy)
{
for (int xx = 0; xx < blocksAcross; ++xx)
{
UncompressDXTBlock(subX0 + xx * 4, subY0 + yy * 4, width, data,
(yy * blocksAcross + xx) * blockSize, format, &dest);
}
}
return dest;
}
class RobustResourceInitTest : public ANGLETest
{
protected:
constexpr static int kWidth = 128;
constexpr static int kHeight = 128;
RobustResourceInitTest()
{
setWindowWidth(kWidth);
setWindowHeight(kHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(24);
setConfigStencilBits(8);
setRobustResourceInit(true);
// Test flakiness was noticed when reusing displays.
forceNewDisplay();
}
bool hasGLExtension()
{
return IsGLExtensionEnabled("GL_ANGLE_robust_resource_initialization");
}
bool hasEGLExtension()
{
return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_robust_resource_initialization");
}
bool hasRobustSurfaceInit()
{
if (!hasEGLExtension())
{
return false;
}
EGLint robustSurfaceInit = EGL_FALSE;
eglQuerySurface(getEGLWindow()->getDisplay(), getEGLWindow()->getSurface(),
EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, &robustSurfaceInit);
return robustSurfaceInit;
}
void setupTexture(GLTexture *tex);
void setup3DTexture(GLTexture *tex);
// Checks for uninitialized (non-zero pixels) in a Texture.
void checkNonZeroPixels(GLTexture *texture,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip);
void checkNonZeroPixels3D(GLTexture *texture,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
int textureLayer,
const GLColor &skip);
void checkFramebufferNonZeroPixels(int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip);
void checkCustomFramebufferNonZeroPixels(int fboWidth,
int fboHeight,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip);
static std::string GetSimpleTextureFragmentShader(const char *samplerType)
{
std::stringstream fragmentStream;
fragmentStream << "#version 300 es\n"
"precision mediump "
<< samplerType
<< "sampler2D;\n"
"precision mediump float;\n"
"out "
<< samplerType
<< "vec4 color;\n"
"in vec2 texcoord;\n"
"uniform "
<< samplerType
<< "sampler2D tex;\n"
"void main()\n"
"{\n"
" color = texture(tex, texcoord);\n"
"}";
return fragmentStream.str();
}
template <typename ClearFunc>
void maskedDepthClear(ClearFunc clearFunc);
template <typename ClearFunc>
void maskedStencilClear(ClearFunc clearFunc);
};
class RobustResourceInitTestES3 : public RobustResourceInitTest
{
protected:
template <typename PixelT>
void testIntegerTextureInit(const char *samplerType,
GLenum internalFormatRGBA,
GLenum internalFormatRGB,
GLenum type);
};
class RobustResourceInitTestES31 : public RobustResourceInitTest
{};
// Robust resource initialization is not based on hardware support or native extensions, check that
// it only works on the implemented renderers
TEST_P(RobustResourceInitTest, ExpectedRendererSupport)
{
bool shouldHaveSupport =
IsD3D11() || IsD3D11_FL93() || IsD3D9() || IsOpenGL() || IsOpenGLES() || IsVulkan();
EXPECT_EQ(shouldHaveSupport, hasGLExtension());
EXPECT_EQ(shouldHaveSupport, hasEGLExtension());
EXPECT_EQ(shouldHaveSupport, hasRobustSurfaceInit());
}
// Tests of the GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE query.
TEST_P(RobustResourceInitTest, Queries)
{
// If context extension string exposed, check queries.
if (IsGLExtensionEnabled("GL_ANGLE_robust_resource_initialization"))
{
GLboolean enabled = 0;
glGetBooleanv(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, &enabled);
EXPECT_GL_TRUE(enabled);
EXPECT_GL_TRUE(glIsEnabled(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE));
EXPECT_GL_NO_ERROR();
}
else
{
// Querying robust resource init should return INVALID_ENUM.
GLboolean enabled = 0;
glGetBooleanv(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, &enabled);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
}
}
// Tests that buffers start zero-filled if the data pointer is null.
TEST_P(RobustResourceInitTest, BufferData)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, getWindowWidth() * getWindowHeight() * sizeof(GLfloat), nullptr,
GL_STATIC_DRAW);
constexpr char kVS[] =
"attribute vec2 position;\n"
"attribute float testValue;\n"
"varying vec4 colorOut;\n"
"void main() {\n"
" gl_Position = vec4(position, 0, 1);\n"
" colorOut = testValue == 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);\n"
"}";
constexpr char kFS[] =
"varying mediump vec4 colorOut;\n"
"void main() {\n"
" gl_FragColor = colorOut;\n"
"}";
ANGLE_GL_PROGRAM(program, kVS, kFS);
GLint testValueLoc = glGetAttribLocation(program.get(), "testValue");
ASSERT_NE(-1, testValueLoc);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexAttribPointer(testValueLoc, 1, GL_FLOAT, GL_FALSE, 4, nullptr);
glEnableVertexAttribArray(testValueLoc);
glBindBuffer(GL_ARRAY_BUFFER, 0);
drawQuad(program.get(), "position", 0.5f);
ASSERT_GL_NO_ERROR();
std::vector<GLColor> expected(getWindowWidth() * getWindowHeight(), GLColor::green);
std::vector<GLColor> actual(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actual.data());
EXPECT_EQ(expected, actual);
}
// Regression test for passing a zero size init buffer with the extension.
TEST_P(RobustResourceInitTest, BufferDataZeroSize)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
}
// The following test code translated from WebGL 1 test:
// https://www.khronos.org/registry/webgl/sdk/tests/conformance/misc/uninitialized-test.html
void RobustResourceInitTest::setupTexture(GLTexture *tex)
{
GLuint tempTexture;
glGenTextures(1, &tempTexture);
glBindTexture(GL_TEXTURE_2D, tempTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// this can be quite undeterministic so to improve odds of seeing uninitialized data write bits
// into tex then delete texture then re-create one with same characteristics (driver will likely
// reuse mem) with this trick on r59046 WebKit/OSX I get FAIL 100% of the time instead of ~15%
// of the time.
std::array<uint8_t, kWidth * kHeight * 4> badData;
for (size_t i = 0; i < badData.size(); ++i)
{
badData[i] = static_cast<uint8_t>(i % 255);
}
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
badData.data());
glDeleteTextures(1, &tempTexture);
// This will create the GLTexture.
glBindTexture(GL_TEXTURE_2D, *tex);
}
void RobustResourceInitTest::setup3DTexture(GLTexture *tex)
{
GLuint tempTexture;
glGenTextures(1, &tempTexture);
glBindTexture(GL_TEXTURE_3D, tempTexture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, kWidth, kHeight, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
// this can be quite undeterministic so to improve odds of seeing uninitialized data write bits
// into tex then delete texture then re-create one with same characteristics (driver will likely
// reuse mem) with this trick on r59046 WebKit/OSX I get FAIL 100% of the time instead of ~15%
// of the time.
std::array<uint8_t, kWidth * kHeight * 2 * 4> badData;
for (size_t i = 0; i < badData.size(); ++i)
{
badData[i] = static_cast<uint8_t>(i % 255);
}
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, 2, GL_RGBA, GL_UNSIGNED_BYTE,
badData.data());
glDeleteTextures(1, &tempTexture);
// This will create the GLTexture.
glBindTexture(GL_TEXTURE_3D, *tex);
}
void RobustResourceInitTest::checkNonZeroPixels(GLTexture *texture,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip)
{
glBindTexture(GL_TEXTURE_2D, 0);
GLFramebuffer fb;
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->get(), 0);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
checkFramebufferNonZeroPixels(skipX, skipY, skipWidth, skipHeight, skip);
}
void RobustResourceInitTest::checkNonZeroPixels3D(GLTexture *texture,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
int textureLayer,
const GLColor &skip)
{
glBindTexture(GL_TEXTURE_3D, 0);
GLFramebuffer fb;
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture->get(), 0,
textureLayer);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
checkFramebufferNonZeroPixels(skipX, skipY, skipWidth, skipHeight, skip);
}
void RobustResourceInitTest::checkFramebufferNonZeroPixels(int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip)
{
checkCustomFramebufferNonZeroPixels(kWidth, kHeight, skipX, skipY, skipWidth, skipHeight, skip);
}
void RobustResourceInitTest::checkCustomFramebufferNonZeroPixels(int fboWidth,
int fboHeight,
int skipX,
int skipY,
int skipWidth,
int skipHeight,
const GLColor &skip)
{
std::vector<GLColor> data(fboWidth * fboHeight);
glReadPixels(0, 0, fboWidth, fboHeight, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
int k = 0;
for (int y = 0; y < fboHeight; ++y)
{
for (int x = 0; x < fboWidth; ++x)
{
int index = (y * fboWidth + x);
if (x >= skipX && x < skipX + skipWidth && y >= skipY && y < skipY + skipHeight)
{
ASSERT_EQ(skip, data[index]) << " at pixel " << x << ", " << y;
}
else
{
k += (data[index] != GLColor::transparentBlack) ? 1 : 0;
}
}
}
EXPECT_EQ(0, k);
}
// Reading an uninitialized texture (texImage2D) should succeed with all bytes set to 0.
TEST_P(RobustResourceInitTest, ReadingUninitializedTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLTexture tex;
setupTexture(&tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
EXPECT_GL_NO_ERROR();
}
// Test that calling glTexImage2D multiple times with the same size and no data resets all texture
// data
TEST_P(RobustResourceInitTest, ReuploadingClearsTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// crbug.com/826576
ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA() && IsDesktopOpenGL());
// Put some data into the texture
std::array<GLColor, kWidth * kHeight> data;
data.fill(GLColor::white);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
data.data());
// Reset the texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
EXPECT_GL_NO_ERROR();
}
// Cover the case where null pixel data is uploaded to a texture and then sub image is used to
// upload partial data
TEST_P(RobustResourceInitTest, TexImageThenSubImage)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2407, but only fails on Nexus devices
ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES());
// Put some data into the texture
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Force the D3D texture to create a storage
checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
std::array<GLColor, kWidth * kHeight> data;
data.fill(GLColor::white);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth / 2, kHeight / 2, GL_RGBA, GL_UNSIGNED_BYTE,
data.data());
checkNonZeroPixels(&tex, 0, 0, kWidth / 2, kHeight / 2, GLColor::white);
EXPECT_GL_NO_ERROR();
}
// Reading an uninitialized texture (texImage3D) should succeed with all bytes set to 0.
TEST_P(RobustResourceInitTestES3, ReadingUninitialized3DTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLTexture tex;
setup3DTexture(&tex);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, kWidth, kHeight, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
checkNonZeroPixels3D(&tex, 0, 0, 0, 0, 0, GLColor::transparentBlack);
EXPECT_GL_NO_ERROR();
}
// Copy of the copytexsubimage3d_texture_wrongly_initialized test that is part of the WebGL2
// conformance suite: copy-texture-image-webgl-specific.html
TEST_P(RobustResourceInitTestES3, CopyTexSubImage3DTextureWronglyInitialized)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr GLint kTextureLayer = 0;
constexpr GLint kTextureWidth = 2;
constexpr GLint kTextureHeight = 2;
constexpr GLint kTextureDepth = 2;
constexpr size_t kTextureDataSize = kTextureWidth * kTextureHeight * 4;
GLTexture texture2D;
glBindTexture(GL_TEXTURE_2D, texture2D);
constexpr std::array<uint8_t, kTextureDataSize> data = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10}};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureWidth, kTextureHeight, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data.data());
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2D, 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
GLTexture texture3D;
glBindTexture(GL_TEXTURE_3D, texture3D);
glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, kTextureWidth, kTextureHeight, kTextureDepth);
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, kTextureLayer, 0, 0, kTextureWidth, kTextureHeight);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture3D, 0, kTextureLayer);
std::array<uint8_t, kTextureDataSize> pixels;
glReadPixels(0, 0, kTextureWidth, kTextureHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
ASSERT_GL_NO_ERROR();
EXPECT_EQ(data, pixels);
}
// Test that binding an EGL surface to a texture does not cause it to be cleared.
TEST_P(RobustResourceInitTestES3, BindTexImage)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
EGLWindow *window = getEGLWindow();
EGLSurface surface = window->getSurface();
EGLDisplay display = window->getDisplay();
EGLConfig config = window->getConfig();
EGLContext context = window->getContext();
EGLint surfaceType = 0;
eglGetConfigAttrib(display, config, EGL_SURFACE_TYPE, &surfaceType);
// Test skipped because EGL config cannot be used to create pbuffers.
ANGLE_SKIP_TEST_IF((surfaceType & EGL_PBUFFER_BIT) == 0);
EGLint bindToSurfaceRGBA = 0;
eglGetConfigAttrib(display, config, EGL_BIND_TO_TEXTURE_RGBA, &bindToSurfaceRGBA);
// Test skipped because EGL config cannot be used to create pbuffers.
ANGLE_SKIP_TEST_IF(bindToSurfaceRGBA == EGL_FALSE);
EGLint attribs[] = {
EGL_WIDTH, 32,
EGL_HEIGHT, 32,
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
EGL_NONE,
};
EGLSurface pbuffer = eglCreatePbufferSurface(display, config, attribs);
ASSERT_NE(EGL_NO_SURFACE, pbuffer);
// Clear the pbuffer
eglMakeCurrent(display, pbuffer, pbuffer, context);
GLColor clearColor = GLColor::magenta;
glClearColor(clearColor.R, clearColor.G, clearColor.B, clearColor.A);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, clearColor);
// Bind the pbuffer to a texture and read its color
eglMakeCurrent(display, surface, surface, context);
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
eglBindTexImage(display, pbuffer, EGL_BACK_BUFFER);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
EXPECT_PIXEL_COLOR_EQ(0, 0, clearColor);
}
else
{
std::cout << "Read pixels check skipped because framebuffer was not complete." << std::endl;
}
eglDestroySurface(display, pbuffer);
}
// Tests that drawing with an uninitialized Texture works as expected.
TEST_P(RobustResourceInitTest, DrawWithTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
constexpr char kVS[] =
"attribute vec2 position;\n"
"varying vec2 texCoord;\n"
"void main() {\n"
" gl_Position = vec4(position, 0, 1);\n"
" texCoord = (position * 0.5) + 0.5;\n"
"}";
constexpr char kFS[] =
"precision mediump float;\n"
"varying vec2 texCoord;\n"
"uniform sampler2D tex;\n"
"void main() {\n"
" gl_FragColor = texture2D(tex, texCoord);\n"
"}";
ANGLE_GL_PROGRAM(program, kVS, kFS);
drawQuad(program, "position", 0.5f);
checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
}
// Reading a partially initialized texture (texImage2D) should succeed with all uninitialized bytes
// set to 0 and initialized bytes untouched.
TEST_P(RobustResourceInitTest, ReadingPartiallyInitializedTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2407, but only fails on Nexus devices
ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES());
GLTexture tex;
setupTexture(&tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLColor data(108, 72, 36, 9);
glTexSubImage2D(GL_TEXTURE_2D, 0, kWidth / 2, kHeight / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
&data.R);
checkNonZeroPixels(&tex, kWidth / 2, kHeight / 2, 1, 1, data);
EXPECT_GL_NO_ERROR();
}
// Uninitialized parts of textures initialized via copyTexImage2D should have all bytes set to 0.
TEST_P(RobustResourceInitTest, UninitializedPartsOfCopied2DTexturesAreBlack)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLTexture tex;
setupTexture(&tex);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLRenderbuffer rbo;
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
constexpr int fboWidth = 16;
constexpr int fboHeight = 16;
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, fboWidth, fboHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, kWidth, kHeight, 0);
checkNonZeroPixels(&tex, 0, 0, fboWidth, fboHeight, GLColor::red);
EXPECT_GL_NO_ERROR();
}
// Reading an uninitialized portion of a texture (copyTexImage2D with negative x and y) should
// succeed with all bytes set to 0. Regression test for a bug where the zeroing out of the
// texture was done via the same code path as glTexImage2D, causing the PIXEL_UNPACK_BUFFER
// to be used.
TEST_P(RobustResourceInitTestES3, ReadingOutOfBoundsCopiedTextureWithUnpackBuffer)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// TODO(geofflang@chromium.org): CopyTexImage from GL_RGBA4444 to GL_ALPHA fails when looking
// up which resulting format the texture should have.
ANGLE_SKIP_TEST_IF(IsOpenGL());
// GL_ALPHA texture can't be read with glReadPixels, for convenience this test uses
// glCopyTextureCHROMIUM to copy GL_ALPHA into GL_RGBA
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_CHROMIUM_copy_texture"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLRenderbuffer rbo;
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
constexpr int fboWidth = 16;
constexpr int fboHeight = 16;
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, fboWidth, fboHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
constexpr int x = -8;
constexpr int y = -8;
GLBuffer buffer;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
std::vector<GLColor> bunchOfGreen(fboWidth * fboHeight, GLColor::green);
glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(bunchOfGreen), bunchOfGreen.data(), GL_STATIC_DRAW);
EXPECT_GL_NO_ERROR();
// Use non-multiple-of-4 dimensions to make sure unpack alignment is set in the backends
// (http://crbug.com/836131)
constexpr int kTextureWidth = 127;
constexpr int kTextureHeight = 127;
// Use GL_ALPHA to force a CPU readback in the D3D11 backend
GLTexture texAlpha;
glBindTexture(GL_TEXTURE_2D, texAlpha);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, x, y, kTextureWidth, kTextureHeight, 0);
EXPECT_GL_NO_ERROR();
// GL_ALPHA cannot be glReadPixels, so copy into a GL_RGBA texture
GLTexture texRGBA;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
setupTexture(&texRGBA);
glCopyTextureCHROMIUM(texAlpha, 0, GL_TEXTURE_2D, texRGBA, 0, GL_RGBA, GL_UNSIGNED_BYTE,
GL_FALSE, GL_FALSE, GL_FALSE);
EXPECT_GL_NO_ERROR();
checkNonZeroPixels(&texRGBA, -x, -y, fboWidth, fboHeight, GLColor(0, 0, 0, 255));
EXPECT_GL_NO_ERROR();
}
// Reading an uninitialized portion of a texture (copyTexImage2D with negative x and y) should
// succeed with all bytes set to 0.
TEST_P(RobustResourceInitTest, ReadingOutOfBoundsCopiedTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// Flaky failure on Linux / NV / Vulkan when run in a sequence. http://anglebug.com/3416
ANGLE_SKIP_TEST_IF(IsVulkan() && IsNVIDIA() && IsLinux());
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsWindows() && IsVulkan());
GLTexture tex;
setupTexture(&tex);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLRenderbuffer rbo;
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
constexpr int fboWidth = 16;
constexpr int fboHeight = 16;
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, fboWidth, fboHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
constexpr int x = -8;
constexpr int y = -8;
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, kWidth, kHeight, 0);
checkNonZeroPixels(&tex, -x, -y, fboWidth, fboHeight, GLColor::red);
EXPECT_GL_NO_ERROR();
}
// Tests resources are initialized properly with multisample resolve.
TEST_P(RobustResourceInitTestES3, MultisampledDepthInitializedCorrectly)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2407
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
// Make the destination non-multisampled depth FBO.
GLTexture color;
glBindTexture(GL_TEXTURE_2D, color);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLRenderbuffer depth;
glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(0, 1, 0, 1);
glClearDepthf(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Make the multisampled depth FBO.
GLRenderbuffer msDepth;
glBindRenderbuffer(GL_RENDERBUFFER, msDepth);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, kWidth, kHeight);
GLFramebuffer msFBO;
glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msDepth);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
// Multisample resolve.
glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
GL_NEAREST);
ASSERT_GL_NO_ERROR();
// Test drawing with the resolved depth buffer.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDepthMask(GL_FALSE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_EQUAL);
drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// Basic test that textures are initialized correctly.
TEST_P(RobustResourceInitTest, Texture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
}
// Test that uploading texture data with an unpack state set correctly initializes the texture and
// the data is uploaded correctly.
TEST_P(RobustResourceInitTest, TextureWithUnpackState)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// GL_UNPACK_ROW_LENGTH requires ES 3.0 or GL_EXT_unpack_subimage
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
!EnsureGLExtensionEnabled("GL_EXT_unpack_subimage"));
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Upload a 2x2 rect using GL_UNPACK_ROW_LENGTH=4
GLColor colorData[8] = {
GLColor::green, GLColor::green, GLColor::red, GLColor::red,
GLColor::green, GLColor::green, GLColor::red, GLColor::red,
};
glPixelStorei(GL_UNPACK_ROW_LENGTH, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, colorData);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
checkFramebufferNonZeroPixels(0, 0, 2, 2, GLColor::green);
}
template <typename PixelT>
void RobustResourceInitTestES3::testIntegerTextureInit(const char *samplerType,
GLenum internalFormatRGBA,
GLenum internalFormatRGB,
GLenum type)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
std::string fs = GetSimpleTextureFragmentShader(samplerType);
ANGLE_GL_PROGRAM(program, kSimpleTextureVertexShader, fs.c_str());
// Make an RGBA framebuffer.
GLTexture framebufferTexture;
glBindTexture(GL_TEXTURE_2D, framebufferTexture);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatRGBA, kWidth, kHeight, 0, GL_RGBA_INTEGER, type,
nullptr);
ASSERT_GL_NO_ERROR();
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferTexture,
0);
// Make an RGB texture.
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatRGB, kWidth, kHeight, 0, GL_RGB_INTEGER, type,
nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
ASSERT_GL_NO_ERROR();
// Blit from the texture to the framebuffer.
drawQuad(program, "position", 0.5f);
std::array<PixelT, kWidth * kHeight * 4> data;
glReadPixels(0, 0, kWidth, kHeight, GL_RGBA_INTEGER, type, data.data());
// Check the color channels are zero and the alpha channel is 1.
int incorrectPixels = 0;
for (int y = 0; y < kHeight; ++y)
{
for (int x = 0; x < kWidth; ++x)
{
int index = (y * kWidth + x) * 4;
bool correct = (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 0 &&
data[index + 3] == 1);
incorrectPixels += (!correct ? 1 : 0);
}
}
ASSERT_GL_NO_ERROR();
EXPECT_EQ(0, incorrectPixels);
}
// Simple tests for integer formats that ANGLE must emulate on D3D11.
TEST_P(RobustResourceInitTestES3, TextureInit_UIntRGB8)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
testIntegerTextureInit<uint8_t>("u", GL_RGBA8UI, GL_RGB8UI, GL_UNSIGNED_BYTE);
}
TEST_P(RobustResourceInitTestES3, TextureInit_UIntRGB32)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
testIntegerTextureInit<uint32_t>("u", GL_RGBA32UI, GL_RGB32UI, GL_UNSIGNED_INT);
}
TEST_P(RobustResourceInitTestES3, TextureInit_IntRGB8)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
testIntegerTextureInit<int8_t>("i", GL_RGBA8I, GL_RGB8I, GL_BYTE);
}
TEST_P(RobustResourceInitTestES3, TextureInit_IntRGB32)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
testIntegerTextureInit<int32_t>("i", GL_RGBA32I, GL_RGB32I, GL_INT);
}
// Test that uninitialized image texture works well.
TEST_P(RobustResourceInitTestES31, ImageTextureInit_R32UI)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr char kCS[] =
R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D writeImage;
void main()
{
imageStore(writeImage, ivec2(gl_LocalInvocationID.xy), uvec4(200u));
})";
GLTexture texture;
// Don't upload data to texture.
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
EXPECT_GL_NO_ERROR();
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
glUseProgram(program.get());
glBindImageTexture(1, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
EXPECT_GL_NO_ERROR();
GLFramebuffer framebuffer;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
GLuint outputValue;
glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(200u, outputValue);
outputValue = 0u;
// Write to another uninitialized texture.
GLTexture texture2;
glBindTexture(GL_TEXTURE_2D, texture2);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
EXPECT_GL_NO_ERROR();
glBindImageTexture(1, texture2, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
glDispatchCompute(1, 1, 1);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
EXPECT_EQ(200u, outputValue);
}
// Basic test that renderbuffers are initialized correctly.
TEST_P(RobustResourceInitTest, Renderbuffer)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
GLRenderbuffer renderbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kWidth, kHeight);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
}
// Tests creating mipmaps with robust resource init.
TEST_P(RobustResourceInitTestES3, GenerateMipmap)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr GLint kTextureSize = 16;
// Initialize a 16x16 RGBA8 texture with no data.
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
std::string shader = GetSimpleTextureFragmentShader("");
ANGLE_GL_PROGRAM(program, kSimpleTextureVertexShader, shader.c_str());
// Generate mipmaps and verify all the mips.
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// Validate a small texture.
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Set viewport to resize the texture and draw.
glViewport(0, 0, 2, 2);
drawQuad(program, "position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
}
// Tests creating mipmaps for cube maps with robust resource init.
TEST_P(RobustResourceInitTestES3, GenerateMipmapCubeMap)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr GLint kTextureSize = 16;
constexpr GLint kTextureLevels = 5;
// Initialize a 16x16 RGBA8 texture with no data.
GLTexture tex;
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
for (GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
++target)
{
glTexImage2D(target, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Generate mipmaps and verify all the mips.
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
ASSERT_GL_NO_ERROR();
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
for (GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
++target)
{
for (GLint level = 0; level < kTextureLevels; ++level)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, tex, level);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
}
}
}
// Test blitting a framebuffer out-of-bounds. Multiple iterations.
TEST_P(RobustResourceInitTestES3, BlitFramebufferOutOfBounds)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2408
ANGLE_SKIP_TEST_IF(IsOSX() && IsAMD());
// Initiate data to read framebuffer
constexpr int size = 8;
constexpr GLenum readbufferFormat = GL_RGBA8;
constexpr GLenum drawbufferFormat = GL_RGBA8;
constexpr GLenum filter = GL_NEAREST;
std::vector<GLColor> readColors(size * size, GLColor::yellow);
// Create read framebuffer and feed data to read buffer
// Read buffer may have srgb image
GLTexture tex_read;
glBindTexture(GL_TEXTURE_2D, tex_read);
glTexImage2D(GL_TEXTURE_2D, 0, readbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
readColors.data());
GLFramebuffer fbo_read;
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_read);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_read, 0);
// Create draw framebuffer. Color in draw buffer is initialized to 0.
// Draw buffer may have srgb image
GLTexture tex_draw;
glBindTexture(GL_TEXTURE_2D, tex_draw);
glTexImage2D(GL_TEXTURE_2D, 0, drawbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
GLFramebuffer fbo_draw;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_draw);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_draw, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
using Region = std::array<int, 4>;
struct Test
{
constexpr Test(const Region &read, const Region &draw, const Region &real)
: readRegion(read), drawRegion(draw), realRegion(real)
{}
Region readRegion;
Region drawRegion;
Region realRegion;
};
constexpr std::array<Test, 2> tests = {{
// only src region is out-of-bounds, dst region has different width/height as src region.
{{{-2, -2, 4, 4}}, {{1, 1, 4, 4}}, {{2, 2, 4, 4}}},
// only src region is out-of-bounds, dst region has the same width/height as src region.
{{{-2, -2, 4, 4}}, {{1, 1, 7, 7}}, {{3, 3, 7, 7}}},
}};
// Blit read framebuffer to the image in draw framebuffer.
for (const auto &test : tests)
{
// both the read framebuffer and draw framebuffer bounds are [0, 0, 8, 8]
// blitting from src region to dst region
glBindTexture(GL_TEXTURE_2D, tex_draw);
glTexImage2D(GL_TEXTURE_2D, 0, drawbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
const auto &read = test.readRegion;
const auto &draw = test.drawRegion;
const auto &real = test.realRegion;
glBlitFramebuffer(read[0], read[1], read[2], read[3], draw[0], draw[1], draw[2], draw[3],
GL_COLOR_BUFFER_BIT, filter);
// Read pixels and check the correctness.
std::vector<GLColor> pixels(size * size);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_draw);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_read);
ASSERT_GL_NO_ERROR();
for (int ii = 0; ii < size; ++ii)
{
for (int jj = 0; jj < size; ++jj)
{
GLColor expectedColor = GLColor::transparentBlack;
if (ii >= real[0] && ii < real[2] && jj >= real[1] && jj < real[3])
{
expectedColor = GLColor::yellow;
}
int loc = ii * size + jj;
EXPECT_EQ(expectedColor, pixels[loc]) << " at [" << jj << ", " << ii << "]";
}
}
}
}
template <typename ClearFunc>
void RobustResourceInitTest::maskedDepthClear(ClearFunc clearFunc)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr int kSize = 16;
// Initialize a FBO with depth and simple color.
GLRenderbuffer depthbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, depthbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kSize, kSize);
GLTexture colorbuffer;
glBindTexture(GL_TEXTURE_2D, colorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuffer);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Disable depth writes and trigger a clear.
glDepthMask(GL_FALSE);
clearFunc(0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
// Draw red with a depth function that checks for the clear value.
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_EQUAL);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black) << "depth should not be 0.5f";
drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red) << "depth should be initialized to 1.0f";
}
// Test that clearing a masked depth buffer doesn't mark it clean.
TEST_P(RobustResourceInitTest, MaskedDepthClear)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2407
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
auto clearFunc = [](float depth) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepthf(depth);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
maskedDepthClear(clearFunc);
}
// Tests the same as MaskedDepthClear, but using ClearBuffer calls.
TEST_P(RobustResourceInitTestES3, MaskedDepthClearBuffer)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2407
ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
auto clearFunc = [](float depth) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glClearBufferfv(GL_DEPTH, 0, &depth);
};
maskedDepthClear(clearFunc);
}
template <typename ClearFunc>
void RobustResourceInitTest::maskedStencilClear(ClearFunc clearFunc)
{
constexpr int kSize = 16;
// Initialize a FBO with stencil and simple color.
GLRenderbuffer stencilbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, stencilbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, kSize, kSize);
GLTexture colorbuffer;
glBindTexture(GL_TEXTURE_2D, colorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
stencilbuffer);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Disable stencil writes and trigger a clear. Use a tricky mask that does not overlap the
// clear.
glStencilMask(0xF0);
clearFunc(0x0F);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
// Draw red with a stencil function that checks for stencil == 0
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_EQUAL, 0x00, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red) << "stencil should be equal to zero";
}
// Test that clearing a masked stencil buffer doesn't mark it clean.
TEST_P(RobustResourceInitTest, MaskedStencilClear)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
// http://anglebug.com/2407, but only fails on Nexus devices
ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES());
auto clearFunc = [](GLint clearValue) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearStencil(clearValue);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
};
maskedStencilClear(clearFunc);
}
// Test that clearing a masked stencil buffer doesn't mark it clean, with ClearBufferi.
TEST_P(RobustResourceInitTestES3, MaskedStencilClearBuffer)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/2408
ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL() && (IsIntel() || IsNVIDIA()));
ANGLE_SKIP_TEST_IF(IsLinux() && IsOpenGL());
// http://anglebug.com/2407, but only fails on Nexus devices
ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES());
auto clearFunc = [](GLint clearValue) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glClearBufferiv(GL_STENCIL, 0, &clearValue);
};
maskedStencilClear(clearFunc);
}
template <int Size, typename InitializedTest>
void VerifyRGBA8PixelRect(InitializedTest inInitialized)
{
std::array<std::array<GLColor, Size>, Size> actualPixels;
glReadPixels(0, 0, Size, Size, GL_RGBA, GL_UNSIGNED_BYTE, actualPixels.data());
ASSERT_GL_NO_ERROR();
for (int y = 0; y < Size; ++y)
{
for (int x = 0; x < Size; ++x)
{
if (inInitialized(x, y))
{
EXPECT_EQ(actualPixels[y][x], GLColor::red) << " at " << x << ", " << y;
}
else
{
EXPECT_EQ(actualPixels[y][x], GLColor::transparentBlack)
<< " at " << x << ", " << y;
}
}
}
}
// Tests that calling CopyTexSubImage2D will initialize the source & destination.
TEST_P(RobustResourceInitTest, CopyTexSubImage2D)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
static constexpr int kDestSize = 4;
constexpr int kSrcSize = kDestSize / 2;
static constexpr int kOffset = kSrcSize / 2;
std::vector<GLColor> redColors(kDestSize * kDestSize, GLColor::red);
// Initialize source texture with red.
GLTexture srcTexture;
glBindTexture(GL_TEXTURE_2D, srcTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSrcSize, kSrcSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
redColors.data());
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Create uninitialized destination texture.
GLTexture destTexture;
glBindTexture(GL_TEXTURE_2D, destTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kDestSize, kDestSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
// Trigger the copy from initialized source into uninitialized dest.
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kOffset, kOffset, 0, 0, kSrcSize, kSrcSize);
// Verify the pixel rectangle.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0);
ASSERT_GL_NO_ERROR();
auto srcInitTest = [](int x, int y) {
return (x >= kOffset) && x < (kDestSize - kOffset) && (y >= kOffset) &&
y < (kDestSize - kOffset);
};
VerifyRGBA8PixelRect<kDestSize>(srcInitTest);
// Make source texture uninitialized. Force a release by redefining a new size.
glBindTexture(GL_TEXTURE_2D, srcTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSrcSize, kSrcSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0);
// Fill destination texture with red.
glBindTexture(GL_TEXTURE_2D, destTexture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kDestSize, kDestSize, GL_RGBA, GL_UNSIGNED_BYTE,
redColors.data());
// Trigger a copy from uninitialized source into initialized dest.
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kOffset, kOffset, 0, 0, kSrcSize, kSrcSize);
// Verify the pixel rectangle.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0);
ASSERT_GL_NO_ERROR();
auto destInitTest = [srcInitTest](int x, int y) { return !srcInitTest(x, y); };
VerifyRGBA8PixelRect<kDestSize>(destInitTest);
}
// Tests that calling CopyTexSubImage3D will initialize the source & destination.
TEST_P(RobustResourceInitTestES3, CopyTexSubImage3D)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
static constexpr int kDestSize = 4;
constexpr int kSrcSize = kDestSize / 2;
static constexpr int kOffset = kSrcSize / 2;
std::vector<GLColor> redColors(kDestSize * kDestSize * kDestSize, GLColor::red);
GLTexture srcTexture;
GLFramebuffer framebuffer;
GLTexture destTexture;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// Initialize source texture with red.
glBindTexture(GL_TEXTURE_3D, srcTexture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kSrcSize, kSrcSize, kSrcSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, redColors.data());
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcTexture, 0, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Create uninitialized destination texture.
glBindTexture(GL_TEXTURE_3D, destTexture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kDestSize, kDestSize, kDestSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
// Trigger the copy from initialized source into uninitialized dest.
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, kOffset, kOffset, 0, 0, 0, kSrcSize, kSrcSize);
// Verify the pixel rectangle.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destTexture, 0, 0);
ASSERT_GL_NO_ERROR();
auto srcInitTest = [](int x, int y) {
return (x >= kOffset) && x < (kDestSize - kOffset) && (y >= kOffset) &&
y < (kDestSize - kOffset);
};
VerifyRGBA8PixelRect<kDestSize>(srcInitTest);
// Make source texture uninitialized.
glBindTexture(GL_TEXTURE_3D, srcTexture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kSrcSize, kSrcSize, kSrcSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcTexture, 0, 0);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Fill destination texture with red.
glBindTexture(GL_TEXTURE_3D, destTexture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kDestSize, kDestSize, kDestSize, 0, GL_RGBA,
GL_UNSIGNED_BYTE, redColors.data());
// Trigger a copy from uninitialized source into initialized dest.
glCopyTexSubImage3D(GL_TEXTURE_3D, 0, kOffset, kOffset, 0, 0, 0, kSrcSize, kSrcSize);
// Verify the pixel rectangle.
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destTexture, 0, 0);
ASSERT_GL_NO_ERROR();
auto destInitTest = [srcInitTest](int x, int y) { return !srcInitTest(x, y); };
VerifyRGBA8PixelRect<kDestSize>(destInitTest);
}
// Test basic robustness with 2D array textures.
TEST_P(RobustResourceInitTestES3, Texture2DArray)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr int kSize = 1024;
constexpr int kLayers = 8;
GLTexture texture;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, kLayers, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
for (int layer = 0; layer < kLayers; ++layer)
{
checkNonZeroPixels3D(&texture, 0, 0, 0, 0, layer, GLColor::transparentBlack);
}
}
// Test that using TexStorage2D followed by CompressedSubImage works with robust init.
// Taken from WebGL test conformance/extensions/webgl-compressed-texture-s3tc.
TEST_P(RobustResourceInitTestES3, CompressedSubImage)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsVulkan());
constexpr int width = 8;
constexpr int height = 8;
constexpr int subX0 = 0;
constexpr int subY0 = 0;
constexpr int subWidth = 4;
constexpr int subHeight = 4;
constexpr GLenum format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
static constexpr uint8_t img_8x8_rgb_dxt1[] = {
0xe0, 0x07, 0x00, 0xf8, 0x11, 0x10, 0x15, 0x00, 0x1f, 0x00, 0xe0,
0xff, 0x11, 0x10, 0x15, 0x00, 0xe0, 0x07, 0x1f, 0xf8, 0x44, 0x45,
0x40, 0x55, 0x1f, 0x00, 0xff, 0x07, 0x44, 0x45, 0x40, 0x55,
};
static constexpr uint8_t img_4x4_rgb_dxt1[] = {
0xe0, 0x07, 0x00, 0xf8, 0x11, 0x10, 0x15, 0x00,
};
std::vector<uint8_t> data(img_8x8_rgb_dxt1, img_8x8_rgb_dxt1 + ArraySize(img_8x8_rgb_dxt1));
std::vector<uint8_t> subData(img_4x4_rgb_dxt1, img_4x4_rgb_dxt1 + ArraySize(img_4x4_rgb_dxt1));
GLTexture colorbuffer;
glBindTexture(GL_TEXTURE_2D, colorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glViewport(0, 0, width, height);
// testing format width-x-height via texStorage2D
const auto &expectedData = UncompressDXTIntoSubRegion(width, height, subX0, subY0, subWidth,
subHeight, subData, format);
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
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);
glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height);
ASSERT_GL_NO_ERROR();
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, subX0, subY0, subWidth, subHeight, format,
static_cast<GLsizei>(subData.size()), subData.data());
ASSERT_GL_NO_ERROR();
draw2DTexturedQuad(0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
std::vector<GLColor> actualData(width * height);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, actualData.data());
ASSERT_GL_NO_ERROR();
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
int offset = x + y * width;
const GLColor expectedColor = expectedData[offset];
const GLColor actualColor = actualData[offset];
// Allow for some minor variation because the format is compressed.
EXPECT_NEAR(expectedColor.R, actualColor.R, 1) << " at (" << x << ", " << y << ")";
EXPECT_NEAR(expectedColor.G, actualColor.G, 1) << " at (" << x << ", " << y << ")";
EXPECT_NEAR(expectedColor.B, actualColor.B, 1) << " at (" << x << ", " << y << ")";
}
}
}
// Tests that a partial scissor still initializes contents as expected.
TEST_P(RobustResourceInitTest, ClearWithScissor)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
constexpr int kSize = 16;
GLRenderbuffer colorbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer);
ASSERT_GL_NO_ERROR();
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
// Scissor to half the width.
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, kSize / 2, kSize);
// Clear. Half the texture should be black, and half red.
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::transparentBlack);
}
// Tests that surfaces are initialized when they are created
TEST_P(RobustResourceInitTest, SurfaceInitialized)
{
ANGLE_SKIP_TEST_IF(!hasRobustSurfaceInit());
checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
}
// Tests that surfaces are initialized after swapping if they are not preserved
TEST_P(RobustResourceInitTest, SurfaceInitializedAfterSwap)
{
ANGLE_SKIP_TEST_IF(!hasRobustSurfaceInit());
EGLint swapBehaviour = 0;
ASSERT_TRUE(eglQuerySurface(getEGLWindow()->getDisplay(), getEGLWindow()->getSurface(),
EGL_SWAP_BEHAVIOR, &swapBehaviour));
const std::array<GLColor, 4> clearColors = {{
GLColor::blue,
GLColor::cyan,
GLColor::red,
GLColor::yellow,
}};
for (size_t i = 0; i < clearColors.size(); i++)
{
if (swapBehaviour == EGL_BUFFER_PRESERVED && i > 0)
{
EXPECT_PIXEL_COLOR_EQ(0, 0, clearColors[i - 1]);
}
else
{
checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
}
angle::Vector4 clearColor = clearColors[i].toNormalizedVector();
glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
swapBuffers();
}
}
// Test that multisampled 2D textures are initialized.
TEST_P(RobustResourceInitTestES31, Multisample2DTexture)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
// http://anglebug.com/4092
ANGLE_SKIP_TEST_IF(IsVulkan());
GLTexture texture;
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture);
glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 2, GL_RGBA8, kWidth, kHeight, false);
GLFramebuffer framebuffer;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
texture, 0);
GLTexture resolveTexture;
glBindTexture(GL_TEXTURE_2D, resolveTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
GLFramebuffer resolveFramebuffer;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFramebuffer);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture,
0);
ASSERT_GL_NO_ERROR();
glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
ASSERT_GL_NO_ERROR();
glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFramebuffer);
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::transparentBlack);
}
// Test that multisampled 2D texture arrays from OES_texture_storage_multisample_2d_array are
// initialized.
TEST_P(RobustResourceInitTestES31, Multisample2DTextureArray)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
if (IsGLExtensionRequestable("GL_OES_texture_storage_multisample_2d_array"))
{
glRequestExtensionANGLE("GL_OES_texture_storage_multisample_2d_array");
}
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_storage_multisample_2d_array"));
const GLsizei kLayers = 4;
GLTexture texture;
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY_OES, texture);
glTexStorage3DMultisampleOES(GL_TEXTURE_2D_MULTISAMPLE_ARRAY_OES, 2, GL_RGBA8, kWidth, kHeight,
kLayers, false);
GLTexture resolveTexture;
glBindTexture(GL_TEXTURE_2D, resolveTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
GLFramebuffer resolveFramebuffer;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFramebuffer);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture,
0);
ASSERT_GL_NO_ERROR();
for (GLsizei layerIndex = 0; layerIndex < kLayers; ++layerIndex)
{
GLFramebuffer framebuffer;
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
layerIndex);
glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
ASSERT_GL_NO_ERROR();
glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFramebuffer);
EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::transparentBlack);
}
}
// Tests that using an out of bounds draw offset with a dynamic array succeeds.
TEST_P(RobustResourceInitTest, DynamicVertexArrayOffsetOutOfBounds)
{
// Not implemented on Vulkan. http://anglebug.com/3350
ANGLE_SKIP_TEST_IF(IsVulkan());
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program);
GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, posLoc);
glEnableVertexAttribArray(posLoc);
GLBuffer buf;
glBindBuffer(GL_ARRAY_BUFFER, buf);
glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<const void *>(500));
glBufferData(GL_ARRAY_BUFFER, 100, nullptr, GL_DYNAMIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 3);
// Either no error or invalid operation is okay.
}
// Test to cover a bug that the multisampled depth attachment of a framebuffer are not successfully
// initialized before it is used as the read framebuffer in blitFramebuffer.
// Referenced from the following WebGL CTS:
// conformance2/renderbuffers/multisampled-depth-renderbuffer-initialization.html
TEST_P(RobustResourceInitTestES3, InitializeMultisampledDepthRenderbufferAfterCopyTextureCHROMIUM)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_CHROMIUM_copy_texture"));
// Call glCopyTextureCHROMIUM to set destTexture as the color attachment of the internal
// framebuffer mScratchFBO.
GLTexture sourceTexture;
glBindTexture(GL_TEXTURE_2D, sourceTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLTexture destTexture;
glBindTexture(GL_TEXTURE_2D, destTexture);
glCopyTextureCHROMIUM(sourceTexture, 0, GL_TEXTURE_2D, destTexture, 0, GL_RGBA,
GL_UNSIGNED_BYTE, GL_FALSE, GL_FALSE, GL_FALSE);
ASSERT_GL_NO_ERROR();
GLFramebuffer drawFbo;
glBindFramebuffer(GL_FRAMEBUFFER, drawFbo);
GLTexture colorTex;
glBindTexture(GL_TEXTURE_2D, colorTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
GLRenderbuffer drawDepthRbo;
glBindRenderbuffer(GL_RENDERBUFFER, drawDepthRbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, drawDepthRbo);
// Clear drawDepthRbo to 0.0f
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClearDepthf(0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
constexpr uint32_t kReadDepthRboSampleCount = 4;
GLFramebuffer readFbo;
glBindFramebuffer(GL_FRAMEBUFFER, readFbo);
GLRenderbuffer readDepthRbo;
glBindRenderbuffer(GL_RENDERBUFFER, readDepthRbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, kReadDepthRboSampleCount,
GL_DEPTH_COMPONENT16, kWidth, kHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, readDepthRbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFbo);
// Blit from readDepthRbo to drawDepthRbo. When robust resource init is enabled, readDepthRbo
// should be initialized to 1.0f by default, so the data in drawDepthRbo should also be 1.0f.
glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
GL_NEAREST);
ASSERT_GL_NO_ERROR();
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, drawFbo);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
// If drawDepthRbo is correctly set to 1.0f, the depth test can always pass, so the result
// should be green.
drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(RobustResourceInitTest);
ANGLE_INSTANTIATE_TEST_ES3(RobustResourceInitTestES3);
ANGLE_INSTANTIATE_TEST_ES31(RobustResourceInitTestES31);
} // namespace angle