diff options
Diffstat (limited to 'gfx/angle/src/tests/gl_tests/MipmapTest.cpp')
-rwxr-xr-x | gfx/angle/src/tests/gl_tests/MipmapTest.cpp | 1135 |
1 files changed, 1135 insertions, 0 deletions
diff --git a/gfx/angle/src/tests/gl_tests/MipmapTest.cpp b/gfx/angle/src/tests/gl_tests/MipmapTest.cpp new file mode 100755 index 000000000..2ab9411a8 --- /dev/null +++ b/gfx/angle/src/tests/gl_tests/MipmapTest.cpp @@ -0,0 +1,1135 @@ +// +// 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" + +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(nullptr), + mLevelZeroWhiteInitData(nullptr), + mLevelOneInitData(nullptr), + mLevelTwoInitData(nullptr), + mOffscreenFramebuffer(0) + { + setWindowWidth(128); + setWindowHeight(128); + setConfigRedBits(8); + setConfigGreenBits(8); + setConfigBlueBits(8); + setConfigAlphaBits(8); + } + + void setUp2DProgram() + { + // Vertex Shader source + // clang-format off + const std::string vs = SHADER_SOURCE + ( + attribute vec4 position; + varying vec2 vTexCoord; + + void main() + { + gl_Position = position; + vTexCoord = (position.xy * 0.5) + 0.5; + } + ); + + // Fragment Shader source + const std::string fs = SHADER_SOURCE + ( + precision mediump float; + + uniform sampler2D uTexture; + varying vec2 vTexCoord; + + void main() + { + gl_FragColor = texture2D(uTexture, vTexCoord); + } + ); + // clang-format on + + m2DProgram = CompileProgram(vs, fs); + ASSERT_NE(0u, m2DProgram); + } + + void setUpCubeProgram() + { + // A simple vertex shader for the texture cube + // clang-format off + const std::string cubeVS = SHADER_SOURCE + ( + 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. + const std::string cubeFS = SHADER_SOURCE + ( + precision mediump float; + uniform samplerCube uTexture; + varying vec4 vPosition; + + void main() + { + gl_FragColor = textureCube(uTexture, vec3(vPosition.x, -1, vPosition.y)); + } + ); + // clang-format on + + mCubeProgram = CompileProgram(cubeVS, cubeFS); + ASSERT_NE(0u, mCubeProgram); + } + + void SetUp() override + { + ANGLETest::SetUp(); + + setUp2DProgram(); + + setUpCubeProgram(); + + mLevelZeroBlueInitData = createRGBInitData(getWindowWidth(), getWindowHeight(), 0, 0, 255); // Blue + mLevelZeroWhiteInitData = createRGBInitData(getWindowWidth(), getWindowHeight(), 255, 255, 255); // White + mLevelOneInitData = createRGBInitData((getWindowWidth() / 2), (getWindowHeight() / 2), 0, 255, 0); // Green + mLevelTwoInitData = 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, NULL); + 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); + + // 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 TearDown() override + { + glDeleteProgram(m2DProgram); + glDeleteProgram(mCubeProgram); + glDeleteFramebuffers(1, &mOffscreenFramebuffer); + glDeleteTextures(1, &mTexture2D); + glDeleteTextures(1, &mTextureCube); + + SafeDeleteArray(mLevelZeroBlueInitData); + SafeDeleteArray(mLevelZeroWhiteInitData); + SafeDeleteArray(mLevelOneInitData); + SafeDeleteArray(mLevelTwoInitData); + + ANGLETest::TearDown(); + } + + GLubyte *createRGBInitData(GLint width, GLint height, GLint r, GLint g, GLint b) + { + GLubyte *data = new GLubyte[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; + + GLubyte* mLevelZeroBlueInitData; + GLubyte* mLevelZeroWhiteInitData; + GLubyte* mLevelOneInitData; + GLubyte* mLevelTwoInitData; + + 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); + } + + std::string 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" + // clang-format off + return SHADER_SOURCE + ( #version 300 es\n + 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; + } + ); + // clang-format on + } + + void setUpArrayProgram() + { + const std::string fragmentShaderSourceArray = SHADER_SOURCE + ( #version 300 es\n + 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(), fragmentShaderSourceArray); + 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() + { + const std::string fragmentShaderSource3D = SHADER_SOURCE + ( #version 300 es\n + 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(), fragmentShaderSource3D); + 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() + { + // clang-format off + const std::string fragmentShaderSource2D = SHADER_SOURCE + ( #version 300 es\n + precision highp float; + uniform highp sampler2D tex; + in vec2 texcoord; + out vec4 out_FragColor; + + void main() + { + out_FragColor = texture(tex, texcoord); + } + ); + // clang-format on + + m2DProgram = CompileProgram(vertexShaderSource(), fragmentShaderSource2D); + 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. + // clang-format off + const std::string cubeFS = SHADER_SOURCE + ( #version 300 es\n + 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)); + } + ); + // clang-format on + + mCubeProgram = CompileProgram(vertexShaderSource(), cubeFS); + ASSERT_NE(0u, mCubeProgram); + + ASSERT_GL_NO_ERROR(); + } + + void SetUp() override + { + ANGLETest::SetUp(); + + glGenTextures(1, &mTexture); + ASSERT_GL_NO_ERROR(); + + setUpArrayProgram(); + setUp3DProgram(); + setUp2DProgram(); + setUpCubeProgram(); + } + + void TearDown() override + { + glDeleteTextures(1, &mTexture); + + glDeleteProgram(mArrayProgram); + glDeleteProgram(m3DProgram); + glDeleteProgram(m2DProgram); + glDeleteProgram(mCubeProgram); + + ANGLETest::TearDown(); + } + + GLuint mTexture; + + GLuint mArrayProgram; + GLint mTextureArraySliceUniformLocation; + + GLuint m3DProgram; + GLint mTexture3DSliceUniformLocation; + GLint mTexture3DLODUniformLocation; + + GLuint m2DProgram; + + GLuint mCubeProgram; +}; + +// 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); + 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, NULL); + 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, mLevelOneInitData); + 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, mLevelTwoInitData); + 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); + 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); + + // 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 + if ((IsAMD() || IsIntel()) && getPlatformRenderer() == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE) + { + std::cout << "Test skipped on Intel/AMD OpenGL." << std::endl; + return; + } + + 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 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) +{ + 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) +{ + 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) +{ + 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) +{ + if (IsAMD() && IsDesktopOpenGL()) + { + // Observed incorrect rendering on AMD, sampling level 2 returns black. + std::cout << "Test skipped on AMD OpenGL." << std::endl; + return; + } + + glBindTexture(GL_TEXTURE_2D, mTexture); + + ASSERT(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) +{ + if (IsAMD() && IsDesktopOpenGL()) + { + // Observed incorrect rendering on AMD, sampling level 2 returns black. + std::cout << "Test skipped on AMD OpenGL." << std::endl; + return; + } + + 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); + + if (IsNVIDIA() && IsOpenGL()) + { + // Observed incorrect rendering on NVIDIA, level zero seems to be incorrectly affected by + // GenerateMipmap. + std::cout << "Test partially skipped on NVIDIA OpenGL." << std::endl; + return; + } + + // 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) +{ + 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); +} + +// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against. +// Note: we run these tests against 9_3 on WARP due to hardware driver issues on Win7 +ANGLE_INSTANTIATE_TEST(MipmapTest, + ES2_D3D9(), + ES2_D3D11(EGL_EXPERIMENTAL_PRESENT_PATH_COPY_ANGLE), + ES2_D3D11(EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE), + ES2_D3D11_FL9_3_WARP(), + ES2_OPENGL(), + ES3_OPENGL(), + ES2_OPENGLES(), + ES3_OPENGLES()); +ANGLE_INSTANTIATE_TEST(MipmapTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES()); |