blob: 33b83a5f3b180315527d94131b1d933993b5b0b9 [file] [log] [blame]
//
// Copyright 2015 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.
//
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
void TexImageCubeMapFaces(GLint level,
GLenum internalformat,
GLsizei width,
GLenum format,
GLenum type,
void *pixels)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, internalformat, width, width, 0, format,
type, pixels);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, level, internalformat, width, width, 0, format,
type, pixels);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, level, internalformat, width, width, 0, format,
type, pixels);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, level, internalformat, width, width, 0, format,
type, pixels);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, level, internalformat, width, width, 0, format,
type, pixels);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, level, internalformat, width, width, 0, format,
type, pixels);
}
class BaseMipmapTest : public ANGLETest
{
protected:
void clearAndDrawQuad(GLuint program, GLsizei viewportWidth, GLsizei viewportHeight)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, viewportWidth, viewportHeight);
ASSERT_GL_NO_ERROR();
drawQuad(program, "position", 0.0f);
}
};
} // namespace
class MipmapTest : public BaseMipmapTest
{
protected:
MipmapTest()
: m2DProgram(0),
mCubeProgram(0),
mTexture2D(0),
mTextureCube(0),
mLevelZeroBlueInitData(),
mLevelZeroWhiteInitData(),
mLevelOneGreenInitData(),
mLevelTwoRedInitData(),
mOffscreenFramebuffer(0)
{
setWindowWidth(128);
setWindowHeight(128);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void setUp2DProgram()
{
// Vertex Shader source
constexpr char kVS[] = R"(attribute vec4 position;
varying vec2 vTexCoord;
void main()
{
gl_Position = position;
vTexCoord = (position.xy * 0.5) + 0.5;
})";
// Fragment Shader source
constexpr char kFS[] = R"(precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
void main()
{
gl_FragColor = texture2D(uTexture, vTexCoord);
})";
m2DProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, m2DProgram);
}
void setUpCubeProgram()
{
// A simple vertex shader for the texture cube
constexpr char kVS[] = R"(attribute vec4 position;
varying vec4 vPosition;
void main()
{
gl_Position = position;
vPosition = position;
})";
// A very simple fragment shader to sample from the negative-Y face of a texture cube.
constexpr char kFS[] = R"(precision mediump float;
uniform samplerCube uTexture;
varying vec4 vPosition;
void main()
{
gl_FragColor = textureCube(uTexture, vec3(vPosition.x, -1, vPosition.y));
})";
mCubeProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mCubeProgram);
}
void testSetUp() override
{
setUp2DProgram();
setUpCubeProgram();
mLevelZeroBlueInitData =
createRGBInitData(getWindowWidth(), getWindowHeight(), 0, 0, 255); // Blue
mLevelZeroWhiteInitData =
createRGBInitData(getWindowWidth(), getWindowHeight(), 255, 255, 255); // White
mLevelOneGreenInitData =
createRGBInitData((getWindowWidth() / 2), (getWindowHeight() / 2), 0, 255, 0); // Green
mLevelTwoRedInitData =
createRGBInitData((getWindowWidth() / 4), (getWindowHeight() / 4), 255, 0, 0); // Red
glGenFramebuffers(1, &mOffscreenFramebuffer);
glGenTextures(1, &mTexture2D);
// Initialize the texture2D to be empty, and don't use mips.
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
ASSERT_EQ(getWindowWidth(), getWindowHeight());
// Create a non-mipped texture cube. Set the negative-Y face to be blue.
glGenTextures(1, &mTextureCube);
glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
TexImageCubeMapFaces(0, GL_RGB, getWindowWidth(), GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, getWindowWidth(), getWindowWidth(),
0, GL_RGB, GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
// Complete the texture cube without mipmaps to start with.
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
ASSERT_GL_NO_ERROR();
}
void testTearDown() override
{
glDeleteProgram(m2DProgram);
glDeleteProgram(mCubeProgram);
glDeleteFramebuffers(1, &mOffscreenFramebuffer);
glDeleteTextures(1, &mTexture2D);
glDeleteTextures(1, &mTextureCube);
}
std::vector<GLubyte> createRGBInitData(GLint width, GLint height, GLint r, GLint g, GLint b)
{
std::vector<GLubyte> data(3 * width * height);
for (int i = 0; i < width * height; i += 1)
{
data[3 * i + 0] = static_cast<GLubyte>(r);
data[3 * i + 1] = static_cast<GLubyte>(g);
data[3 * i + 2] = static_cast<GLubyte>(b);
}
return data;
}
void clearTextureLevel0(GLenum textarget,
GLuint texture,
GLfloat red,
GLfloat green,
GLfloat blue,
GLfloat alpha)
{
glBindFramebuffer(GL_FRAMEBUFFER, mOffscreenFramebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget, texture, 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(red, green, blue, alpha);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
GLuint m2DProgram;
GLuint mCubeProgram;
GLuint mTexture2D;
GLuint mTextureCube;
std::vector<GLubyte> mLevelZeroBlueInitData;
std::vector<GLubyte> mLevelZeroWhiteInitData;
std::vector<GLubyte> mLevelOneGreenInitData;
std::vector<GLubyte> mLevelTwoRedInitData;
private:
GLuint mOffscreenFramebuffer;
};
class MipmapTestES3 : public BaseMipmapTest
{
protected:
MipmapTestES3()
: mTexture(0),
mArrayProgram(0),
mTextureArraySliceUniformLocation(-1),
m3DProgram(0),
mTexture3DSliceUniformLocation(-1),
mTexture3DLODUniformLocation(-1),
m2DProgram(0)
{
setWindowWidth(128);
setWindowHeight(128);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
const char *vertexShaderSource()
{
// Don't put "#version ..." on its own line. See [cpp]p1:
// "If there are sequences of preprocessing tokens within the list of arguments that
// would otherwise act as preprocessing directives, the behavior is undefined"
return
R"(#version 300 es
precision highp float;
in vec4 position;
out vec2 texcoord;
void main()
{
gl_Position = vec4(position.xy, 0.0, 1.0);
texcoord = (position.xy * 0.5) + 0.5;
})";
}
void setUpArrayProgram()
{
constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform highp sampler2DArray tex;
uniform int slice;
in vec2 texcoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = texture(tex, vec3(texcoord, float(slice)));
})";
mArrayProgram = CompileProgram(vertexShaderSource(), kFS);
if (mArrayProgram == 0)
{
FAIL() << "shader compilation failed.";
}
mTextureArraySliceUniformLocation = glGetUniformLocation(mArrayProgram, "slice");
ASSERT_NE(-1, mTextureArraySliceUniformLocation);
glUseProgram(mArrayProgram);
glUseProgram(0);
ASSERT_GL_NO_ERROR();
}
void setUp3DProgram()
{
constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform highp sampler3D tex;
uniform float slice;
uniform float lod;
in vec2 texcoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = textureLod(tex, vec3(texcoord, slice), lod);
})";
m3DProgram = CompileProgram(vertexShaderSource(), kFS);
if (m3DProgram == 0)
{
FAIL() << "shader compilation failed.";
}
mTexture3DSliceUniformLocation = glGetUniformLocation(m3DProgram, "slice");
ASSERT_NE(-1, mTexture3DSliceUniformLocation);
mTexture3DLODUniformLocation = glGetUniformLocation(m3DProgram, "lod");
ASSERT_NE(-1, mTexture3DLODUniformLocation);
glUseProgram(m3DProgram);
glUniform1f(mTexture3DLODUniformLocation, 0);
glUseProgram(0);
ASSERT_GL_NO_ERROR();
}
void setUp2DProgram()
{
constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform highp sampler2D tex;
in vec2 texcoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = texture(tex, texcoord);
})";
m2DProgram = CompileProgram(vertexShaderSource(), kFS);
ASSERT_NE(0u, m2DProgram);
ASSERT_GL_NO_ERROR();
}
void setUpCubeProgram()
{
// A very simple fragment shader to sample from the negative-Y face of a texture cube.
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform samplerCube uTexture;
in vec2 texcoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = texture(uTexture, vec3(texcoord.x, -1, texcoord.y));
})";
mCubeProgram = CompileProgram(vertexShaderSource(), kFS);
ASSERT_NE(0u, mCubeProgram);
ASSERT_GL_NO_ERROR();
}
void testSetUp() override
{
glGenTextures(1, &mTexture);
ASSERT_GL_NO_ERROR();
setUpArrayProgram();
setUp3DProgram();
setUp2DProgram();
setUpCubeProgram();
}
void testTearDown() override
{
glDeleteTextures(1, &mTexture);
glDeleteProgram(mArrayProgram);
glDeleteProgram(m3DProgram);
glDeleteProgram(m2DProgram);
glDeleteProgram(mCubeProgram);
}
GLuint mTexture;
GLuint mArrayProgram;
GLint mTextureArraySliceUniformLocation;
GLuint m3DProgram;
GLint mTexture3DSliceUniformLocation;
GLint mTexture3DLODUniformLocation;
GLuint m2DProgram;
GLuint mCubeProgram;
};
class MipmapTestES31 : public BaseMipmapTest
{
protected:
MipmapTestES31()
{
setWindowWidth(128);
setWindowHeight(128);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
};
// This test uses init data for the first three levels of the texture. It passes the level 0 data
// in, then renders, then level 1, then renders, etc. This ensures that renderers using the zero LOD
// workaround (e.g. D3D11 FL9_3) correctly pass init data to the mipmapped texture, even if the the
// zero-LOD texture is currently in use.
TEST_P(MipmapTest, DISABLED_ThreeLevelsInitData)
{
// Pass in level zero init data.
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
ASSERT_GL_NO_ERROR();
// Disable mips.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// Draw a full-sized quad, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Draw a half-sized quad, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Draw a quarter-sized quad, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
// Complete the texture by initializing the remaining levels.
int n = 1;
while (getWindowWidth() / (1U << n) >= 1)
{
glTexImage2D(GL_TEXTURE_2D, n, GL_RGB, getWindowWidth() / (1U << n),
getWindowWidth() / (1U << n), 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
n += 1;
}
// Pass in level one init data.
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGB, getWindowWidth() / 2, getWindowHeight() / 2, 0, GL_RGB,
GL_UNSIGNED_BYTE, mLevelOneGreenInitData.data());
ASSERT_GL_NO_ERROR();
// Draw a full-sized quad, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Draw a half-sized quad, and check it's blue. We've not enabled mipmaps yet, so our init data
// for level one shouldn't be used.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Enable mipmaps.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Draw a half-sized quad, and check it's green.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
// Draw a quarter-sized quad, and check it's black, since we've not passed any init data for
// level two.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::black);
// Pass in level two init data.
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGB, getWindowWidth() / 4, getWindowHeight() / 4, 0, GL_RGB,
GL_UNSIGNED_BYTE, mLevelTwoRedInitData.data());
ASSERT_GL_NO_ERROR();
// Draw a full-sized quad, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Draw a half-sized quad, and check it's green.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
// Draw a quarter-sized quad, and check it's red.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
// Now disable mipmaps again, and render multiple sized quads. They should all be blue, since
// level 0 is blue.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
// Now reset level 0 to white, keeping mipmaps disabled. Then, render various sized quads. They
// should be white.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE, mLevelZeroWhiteInitData.data());
ASSERT_GL_NO_ERROR();
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::white);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::white);
// Then enable mipmaps again. The quads should be white, green, red respectively.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
}
// This test generates (and uses) mipmaps on a texture using init data. D3D11 will use a
// non-renderable TextureStorage for this. The test then disables mips, renders to level zero of the
// texture, and reenables mips before using the texture again. To do this, D3D11 has to convert the
// TextureStorage into a renderable one. This test ensures that the conversion works correctly. In
// particular, on D3D11 Feature Level 9_3 it ensures that both the zero LOD workaround texture AND
// the 'normal' texture are copied during conversion.
TEST_P(MipmapTest, GenerateMipmapFromInitDataThenRender)
{
// Pass in initial data so the texture is blue.
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
// Then generate the mips.
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// Enable mipmaps.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Now draw the texture to various different sized areas.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Use mip level 1
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Use mip level 2
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
ASSERT_GL_NO_ERROR();
// Disable mips. Render a quad using the texture and ensure it's blue.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Clear level 0 of the texture to red.
clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 1.0f, 0.0f, 0.0f, 1.0f);
// Reenable mips, and try rendering different-sized quads.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Level 0 is now red, so this should render red.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
// Use mip level 1, blue.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Use mip level 2, blue.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
}
// This test ensures that mips are correctly generated from a rendered image.
// In particular, on D3D11 Feature Level 9_3, the clear call will be performed on the zero-level
// texture, rather than the mipped one. The test ensures that the zero-level texture is correctly
// copied into the mipped texture before the mipmaps are generated.
TEST_P(MipmapTest, GenerateMipmapFromRenderedImage)
{
glBindTexture(GL_TEXTURE_2D, mTexture2D);
// Clear the texture to blue.
clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
// Then generate the mips
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// Enable mips.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Now draw the texture to various different sized areas.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Use mip level 1
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Use mip level 2
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
}
// Test to ensure that rendering to a mipmapped texture works, regardless of whether mipmaps are
// enabled or not.
// TODO: This test hits a texture rebind bug in the D3D11 renderer. Fix this.
TEST_P(MipmapTest, RenderOntoLevelZeroAfterGenerateMipmap)
{
// TODO(geofflang): Figure out why this is broken on AMD OpenGL
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
glBindTexture(GL_TEXTURE_2D, mTexture2D);
// Clear the texture to blue.
clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
// Now, draw the texture to a quad that's the same size as the texture. This draws to the
// default framebuffer. The quad should be blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Now go back to the texture, and generate mips on it.
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// Now try rendering the textured quad again. Note: we've not told GL to use the generated mips.
// The quad should be blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Now tell GL to use the generated mips.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
EXPECT_GL_NO_ERROR();
// Now render the textured quad again. It should be still be blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Now render the textured quad to an area smaller than the texture (i.e. to force
// minification). This should be blue.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
// Now clear the texture to green. This just clears the top level. The lower mips should remain
// blue.
clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 1.0f, 0.0f, 1.0f);
// Render a textured quad equal in size to the texture. This should be green, since we just
// cleared level 0.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
// Render a small textured quad. This forces minification, so should render blue (the color of
// levels 1+).
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
// Disable mipmaps again
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
ASSERT_GL_NO_ERROR();
// Render a textured quad equal in size to the texture. This should be green, the color of level
// 0 in the texture.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
// Render a small textured quad. This would force minification if mips were enabled, but they're
// not. Therefore, this should be green.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
}
// This test defines a valid mipchain manually, with an extra level that's unused on the first few
// draws. Later on, it redefines the whole mipchain but this time, uses the last mip that was
// already uploaded before. The test expects that mip to be usable.
TEST_P(MipmapTest, DefineValidExtraLevelAndUseItLater)
{
glBindTexture(GL_TEXTURE_2D, mTexture2D);
GLubyte *levels[] = {mLevelZeroBlueInitData.data(), mLevelOneGreenInitData.data(),
mLevelTwoRedInitData.data()};
int maxLevel = 1 + static_cast<int>(floor(log2(std::max(getWindowWidth(), getWindowHeight()))));
for (int i = 0; i < maxLevel; i++)
{
glTexImage2D(GL_TEXTURE_2D, i, GL_RGB, getWindowWidth() >> i, getWindowHeight() >> i, 0,
GL_RGB, GL_UNSIGNED_BYTE, levels[i % 3]);
}
// Define an extra level that won't be used for now
std::vector<GLubyte> magentaExtraLevelData =
createRGBInitData(getWindowWidth() * 2, getWindowHeight() * 2, 255, 0, 255);
glTexImage2D(GL_TEXTURE_2D, maxLevel, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE,
magentaExtraLevelData.data());
ASSERT_GL_NO_ERROR();
// Enable mipmaps.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Draw a full-sized quad using mip 0, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
// Draw a full-sized quad using mip 1, and check it's green.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
// Draw a full-sized quad using mip 2, and check it's red.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
// Draw a full-sized quad using the last mip, and check it's green.
clearAndDrawQuad(m2DProgram, 1, 1);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Now redefine everything above level 8 to be a mipcomplete chain again.
std::vector<GLubyte> levelDoubleSizeYellowInitData =
createRGBInitData(getWindowWidth() * 2, getWindowHeight() * 2, 255, 255, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth() * 2, getWindowHeight() * 2, 0, GL_RGB,
GL_UNSIGNED_BYTE, levelDoubleSizeYellowInitData.data()); // 256
for (int i = 0; i < maxLevel - 1; i++)
{
glTexImage2D(GL_TEXTURE_2D, i + 1, GL_RGB, getWindowWidth() >> i, getWindowHeight() >> i, 0,
GL_RGB, GL_UNSIGNED_BYTE, levels[i % 3]);
}
// At this point we have a valid mip chain, the last level being magenta if we draw 1x1 pixel.
clearAndDrawQuad(m2DProgram, 1, 1);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
// Draw a full-sized quad using mip 0, and check it's yellow.
clearAndDrawQuad(m2DProgram, getWindowWidth() * 2, getWindowHeight() * 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::yellow);
// Draw a full-sized quad using mip 1, and check it's blue.
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Draw a full-sized quad using mip 2, and check it's green.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
}
// Regression test for a bug that cause mipmaps to only generate using the top left corner as input.
TEST_P(MipmapTest, MipMapGenerationD3D9Bug)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage") ||
!IsGLExtensionEnabled("GL_OES_rgb8_rgba8") ||
!IsGLExtensionEnabled("GL_ANGLE_texture_usage"));
const GLColor mip0Color[4] = {
GLColor::red,
GLColor::green,
GLColor::red,
GLColor::green,
};
const GLColor mip1Color = GLColor(127, 127, 0, 255);
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture.get());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexStorage2DEXT(GL_TEXTURE_2D, 2, GL_RGBA8_OES, 2, 2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color);
glGenerateMipmap(GL_TEXTURE_2D);
// Only draw to a 1 pixel viewport so the lower mip is used
clearAndDrawQuad(m2DProgram, 1, 1);
EXPECT_PIXEL_COLOR_NEAR(0, 0, mip1Color, 1.0);
}
// This test ensures that the level-zero workaround for TextureCubes (on D3D11 Feature Level 9_3)
// works as expected. It tests enabling/disabling mipmaps, generating mipmaps, and rendering to
// level zero.
TEST_P(MipmapTest, TextureCubeGeneralLevelZero)
{
// http://anglebug.com/3145
ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
// http://anglebug.com/2822
ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsVulkan());
glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
// Draw. Since the negative-Y face's is blue, this should be blue.
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Generate mipmaps, and render. This should be blue.
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Draw using a smaller viewport (to force a lower LOD of the texture). This should still be
// blue.
clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Now clear the negative-Y face of the cube to red.
clearTextureLevel0(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, mTextureCube, 1.0f, 0.0f, 0.0f, 1.0f);
// Draw using a full-size viewport. This should be red.
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw using a quarter-size viewport, to force a lower LOD. This should be *BLUE*, since we
// only cleared level zero of the negative-Y face to red, and left its mipmaps blue.
clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Disable mipmaps again, and draw a to a quarter-size viewport.
// Since this should use level zero of the texture, this should be *RED*.
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// This test ensures that rendering to level-zero of a TextureCube works as expected.
TEST_P(MipmapTest, TextureCubeRenderToLevelZero)
{
// http://anglebug.com/3145
ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
// http://anglebug.com/2822
ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsVulkan());
glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
// Draw. Since the negative-Y face's is blue, this should be blue.
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Now clear the negative-Y face of the cube to red.
clearTextureLevel0(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, mTextureCube, 1.0f, 0.0f, 0.0f, 1.0f);
// Draw using a full-size viewport. This should be red.
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
// Draw a to a quarter-size viewport. This should also be red.
clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// Creates a mipmapped 2D array texture with three layers, and calls ANGLE's GenerateMipmap.
// Then tests if the mipmaps are rendered correctly for all three layers.
TEST_P(MipmapTestES3, MipmapsForTextureArray)
{
int px = getWindowWidth() / 2;
int py = getWindowHeight() / 2;
glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 5, GL_RGBA8, 16, 16, 3);
// Fill the first layer with red
std::vector<GLColor> pixelsRed(16 * 16, GLColor::red);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsRed.data());
// Fill the second layer with green
std::vector<GLColor> pixelsGreen(16 * 16, GLColor::green);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsGreen.data());
// Fill the third layer with blue
std::vector<GLColor> pixelsBlue(16 * 16, GLColor::blue);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsBlue.data());
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
EXPECT_GL_NO_ERROR();
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
EXPECT_GL_NO_ERROR();
glUseProgram(mArrayProgram);
EXPECT_GL_NO_ERROR();
// Draw the first slice
glUniform1i(mTextureArraySliceUniformLocation, 0);
drawQuad(mArrayProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
// Draw the second slice
glUniform1i(mTextureArraySliceUniformLocation, 1);
drawQuad(mArrayProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
// Draw the third slice
glUniform1i(mTextureArraySliceUniformLocation, 2);
drawQuad(mArrayProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::blue);
}
// Create a mipmapped 2D array texture with more layers than width / height, and call
// GenerateMipmap.
TEST_P(MipmapTestES3, MipmapForDeepTextureArray)
{
int px = getWindowWidth() / 2;
int py = getWindowHeight() / 2;
glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
// Fill the whole texture with red.
std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsRed.data());
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
EXPECT_GL_NO_ERROR();
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
EXPECT_GL_NO_ERROR();
glUseProgram(mArrayProgram);
EXPECT_GL_NO_ERROR();
// Draw the first slice
glUniform1i(mTextureArraySliceUniformLocation, 0);
drawQuad(mArrayProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
// Draw the fourth slice
glUniform1i(mTextureArraySliceUniformLocation, 3);
drawQuad(mArrayProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
}
// Creates a mipmapped 3D texture with two layers, and calls ANGLE's GenerateMipmap.
// Then tests if the mipmaps are rendered correctly for all two layers.
TEST_P(MipmapTestES3, MipmapsForTexture3D)
{
// TODO(cnorthrop): Enabled the group to cover texture base level, but this test
// needs some triage: http://anglebug.com/3950
ANGLE_SKIP_TEST_IF(IsVulkan());
int px = getWindowWidth() / 2;
int py = getWindowHeight() / 2;
glBindTexture(GL_TEXTURE_3D, mTexture);
glTexStorage3D(GL_TEXTURE_3D, 5, GL_RGBA8, 16, 16, 2);
// Fill the first layer with red
std::vector<GLColor> pixelsRed(16 * 16, GLColor::red);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsRed.data());
// Fill the second layer with green
std::vector<GLColor> pixelsGreen(16 * 16, GLColor::green);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 1, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsGreen.data());
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
EXPECT_GL_NO_ERROR();
glGenerateMipmap(GL_TEXTURE_3D);
EXPECT_GL_NO_ERROR();
glUseProgram(m3DProgram);
EXPECT_GL_NO_ERROR();
// Mipmap level 0
// Draw the first slice
glUniform1f(mTexture3DLODUniformLocation, 0.);
glUniform1f(mTexture3DSliceUniformLocation, 0.25f);
drawQuad(m3DProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
// Draw the second slice
glUniform1f(mTexture3DSliceUniformLocation, 0.75f);
drawQuad(m3DProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
// Mipmap level 1
// The second mipmap should only have one slice.
glUniform1f(mTexture3DLODUniformLocation, 1.);
drawQuad(m3DProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(px, py, 127, 127, 0, 255, 1.0);
glUniform1f(mTexture3DSliceUniformLocation, 0.75f);
drawQuad(m3DProgram, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(px, py, 127, 127, 0, 255, 1.0);
}
// Create a 2D texture with levels 0-2, call GenerateMipmap with base level 1 so that level 0 stays
// the same, and then sample levels 0 and 2.
// GLES 3.0.4 section 3.8.10:
// "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
// the levelbase array, regardless of their previous contents. All other mipmap arrays, including
// the levelbase array, are left unchanged by this computation."
TEST_P(MipmapTestES3, GenerateMipmapBaseLevel)
{
// Observed incorrect rendering on AMD, sampling level 2 returns black.
ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
glBindTexture(GL_TEXTURE_2D, mTexture);
ASSERT_EQ(getWindowWidth(), getWindowHeight());
// Fill level 0 with blue
std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, pixelsBlue.data());
// Fill level 1 with red
std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight() / 4, GLColor::red);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth() / 2, getWindowHeight() / 2, 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixelsRed.data());
// Fill level 2 with green
std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 16, GLColor::green);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, getWindowWidth() / 4, getWindowHeight() / 4, 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixelsGreen.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
EXPECT_GL_NO_ERROR();
// The blue level 0 should be untouched by this since base level is 1.
glGenerateMipmap(GL_TEXTURE_2D);
EXPECT_GL_NO_ERROR();
// Draw using level 2. It should be set to red by GenerateMipmap.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
// Draw using level 0. It should still be blue.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
}
// Create a cube map with levels 0-2, call GenerateMipmap with base level 1 so that level 0 stays
// the same, and then sample levels 0 and 2.
// GLES 3.0.4 section 3.8.10:
// "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
// the levelbase array, regardless of their previous contents. All other mipmap arrays, including
// the levelbase array, are left unchanged by this computation."
TEST_P(MipmapTestES3, GenerateMipmapCubeBaseLevel)
{
// Observed incorrect rendering on AMD, sampling level 2 returns black.
ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
ASSERT_EQ(getWindowWidth(), getWindowHeight());
glBindTexture(GL_TEXTURE_CUBE_MAP, mTexture);
std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowWidth(), GLColor::blue);
TexImageCubeMapFaces(0, GL_RGBA8, getWindowWidth(), GL_RGBA, GL_UNSIGNED_BYTE,
pixelsBlue.data());
// Fill level 1 with red
std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowWidth() / 4, GLColor::red);
TexImageCubeMapFaces(1, GL_RGBA8, getWindowWidth() / 2, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsRed.data());
// Fill level 2 with green
std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowWidth() / 16, GLColor::green);
TexImageCubeMapFaces(2, GL_RGBA8, getWindowWidth() / 4, GL_RGBA, GL_UNSIGNED_BYTE,
pixelsGreen.data());
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 1);
EXPECT_GL_NO_ERROR();
// The blue level 0 should be untouched by this since base level is 1.
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
EXPECT_GL_NO_ERROR();
// Draw using level 2. It should be set to red by GenerateMipmap.
clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
// Observed incorrect rendering on NVIDIA, level zero seems to be incorrectly affected by
// GenerateMipmap.
// http://anglebug.com/3851
ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL());
// Draw using level 0. It should still be blue.
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
}
// Create a texture with levels 0-2, call GenerateMipmap with max level 1 so that level 2 stays the
// same, and then sample levels 1 and 2.
// GLES 3.0.4 section 3.8.10:
// "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
// the levelbase array, regardless of their previous contents. All other mipmap arrays, including
// the levelbase array, are left unchanged by this computation."
TEST_P(MipmapTestES3, GenerateMipmapMaxLevel)
{
glBindTexture(GL_TEXTURE_2D, mTexture);
// Fill level 0 with blue
std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, pixelsBlue.data());
// Fill level 1 with red
std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight() / 4, GLColor::red);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth() / 2, getWindowHeight() / 2, 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixelsRed.data());
// Fill level 2 with green
std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 16, GLColor::green);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, getWindowWidth() / 4, getWindowHeight() / 4, 0,
GL_RGBA, GL_UNSIGNED_BYTE, pixelsGreen.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
EXPECT_GL_NO_ERROR();
// The green level 2 should be untouched by this since max level is 1.
glGenerateMipmap(GL_TEXTURE_2D);
EXPECT_GL_NO_ERROR();
// Draw using level 1. It should be set to blue by GenerateMipmap.
clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
// Draw using level 2. It should still be green.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
}
// Call GenerateMipmap with out-of-range base level. The spec is interpreted so that an out-of-range
// base level does not have a color-renderable/texture-filterable internal format, so the
// GenerateMipmap call generates INVALID_OPERATION. GLES 3.0.4 section 3.8.10:
// "If the levelbase array was not specified with an unsized internal format from table 3.3 or a
// sized internal format that is both color-renderable and texture-filterable according to table
// 3.13, an INVALID_OPERATION error is generated."
TEST_P(MipmapTestES3, GenerateMipmapBaseLevelOutOfRange)
{
glBindTexture(GL_TEXTURE_2D, mTexture);
// Fill level 0 with blue
std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, pixelsBlue.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1000);
EXPECT_GL_NO_ERROR();
// Expecting the out-of-range base level to be treated as not color-renderable and
// texture-filterable.
glGenerateMipmap(GL_TEXTURE_2D);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// Draw using level 0. It should still be blue.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
}
// Call GenerateMipmap with out-of-range base level on an immutable texture. The base level should
// be clamped, so the call doesn't generate an error.
TEST_P(MipmapTestES3, GenerateMipmapBaseLevelOutOfRangeImmutableTexture)
{
// TODO(cnorthrop): Interacts with immutable texture supprt: http://anglebug.com/3950
ANGLE_SKIP_TEST_IF(IsVulkan());
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1000);
EXPECT_GL_NO_ERROR();
// This is essentially a no-op, since the texture only has one level.
glGenerateMipmap(GL_TEXTURE_2D);
EXPECT_GL_NO_ERROR();
// The only level of the texture should still be green.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
}
// A native version of the WebGL2 test tex-base-level-bug.html
TEST_P(MipmapTestES3, BaseLevelTextureBug)
{
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsAMD());
// Regression in 10.12.4 needing workaround -- crbug.com/705865.
// Seems to be passing on AMD GPUs. Definitely not NVIDIA.
// Probably not Intel.
ANGLE_SKIP_TEST_IF(IsOSX() && (IsNVIDIA() || IsIntel()));
// TODO(cnorthrop): Figure out what's going on here: http://anglebug.com/3950
ANGLE_SKIP_TEST_IF(IsVulkan());
std::vector<GLColor> texDataRed(2u * 2u, GLColor::red);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
ASSERT_GL_NO_ERROR();
drawQuad(m2DProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
drawQuad(m2DProgram, "position", 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
TEST_P(MipmapTestES31, MipmapWithMemoryBarrier)
{
std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight(), GLColor::red);
std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 4, GLColor::green);
constexpr char kVS[] = R"(#version 300 es
precision highp float;
in vec4 position;
out vec2 texcoord;
void main()
{
gl_Position = vec4(position.xy, 0.0, 1.0);
texcoord = (position.xy * 0.5) + 0.5;
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform highp sampler2D tex;
in vec2 texcoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = texture(tex, texcoord);
})";
ANGLE_GL_PROGRAM(m2DProgram, kVS, kFS);
glUseProgram(m2DProgram);
// Create a texture with red and enable the mipmap
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
// Fill level 0 with red
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, pixelsRed.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
EXPECT_GL_NO_ERROR();
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// level 2 is red
clearAndDrawQuad(m2DProgram.get(), getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
// Clear the level 1 to green
glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, getWindowWidth() / 2, getWindowHeight() / 2, GL_RGBA,
GL_UNSIGNED_BYTE, pixelsGreen.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
EXPECT_GL_NO_ERROR();
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GL_NO_ERROR();
// Insert a memory barrier, then it will break the graph node submission order.
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
// level 0 is red
clearAndDrawQuad(m2DProgram.get(), getWindowWidth(), getWindowHeight());
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
// Draw using level 2. It should be set to green by GenerateMipmap.
clearAndDrawQuad(m2DProgram.get(), getWindowWidth() / 4, getWindowHeight() / 4);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
}
// 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(MipmapTest);
ANGLE_INSTANTIATE_TEST_ES3(MipmapTestES3);
ANGLE_INSTANTIATE_TEST_ES31(MipmapTestES31);