Re-re-land "Move sampler validation to the GL layer."
This previously was D3D-only, but is required for every draw call.
This completes the work of removing the D3D-specific Impl methods
from ProgramImpl.
Also add several regression tests to cover texture and sampler
validation.
Re-land with a fix for duplicate sampler active uniforms.
Re-re-land with a fix for a test comparison warning on Linux.
BUG=angleproject:1123
Change-Id: Iaf7b33861c07b9ceed4bd53ac2f010d35f05df45
Reviewed-on: https://chromium-review.googlesource.com/301712
Tryjob-Request: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index e9f871d..acb511f 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -703,8 +703,366 @@
ASSERT_GL_NO_ERROR();
}
+class TextureLimitsTest : public ANGLETest
+{
+ protected:
+ struct RGBA8
+ {
+ uint8_t R, G, B, A;
+ };
+
+ TextureLimitsTest()
+ : mProgram(0), mMaxVertexTextures(0), mMaxFragmentTextures(0), mMaxCombinedTextures(0)
+ {
+ setWindowWidth(128);
+ setWindowHeight(128);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ }
+
+ ~TextureLimitsTest()
+ {
+ if (mProgram != 0)
+ {
+ glDeleteProgram(mProgram);
+ mProgram = 0;
+
+ if (!mTextures.empty())
+ {
+ glDeleteTextures(static_cast<GLsizei>(mTextures.size()), &mTextures[0]);
+ }
+ }
+ }
+
+ void SetUp() override
+ {
+ ANGLETest::SetUp();
+
+ glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mMaxVertexTextures);
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxFragmentTextures);
+ glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mMaxCombinedTextures);
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void compileProgramWithTextureCounts(const std::string &vertexPrefix,
+ GLint vertexTextureCount,
+ GLint vertexActiveTextureCount,
+ const std::string &fragPrefix,
+ GLint fragmentTextureCount,
+ GLint fragmentActiveTextureCount)
+ {
+ std::stringstream vertexShaderStr;
+ vertexShaderStr << "attribute vec2 position;\n"
+ << "varying vec4 color;\n"
+ << "varying vec2 texCoord;\n";
+
+ for (GLint textureIndex = 0; textureIndex < vertexTextureCount; ++textureIndex)
+ {
+ vertexShaderStr << "uniform sampler2D " << vertexPrefix << textureIndex << ";\n";
+ }
+
+ vertexShaderStr << "void main() {\n"
+ << " gl_Position = vec4(position, 0, 1);\n"
+ << " texCoord = (position * 0.5) + 0.5;\n"
+ << " color = vec4(0);\n";
+
+ for (GLint textureIndex = 0; textureIndex < vertexActiveTextureCount; ++textureIndex)
+ {
+ vertexShaderStr << " color += texture2D(" << vertexPrefix << textureIndex
+ << ", texCoord);\n";
+ }
+
+ vertexShaderStr << "}";
+
+ std::stringstream fragmentShaderStr;
+ fragmentShaderStr << "varying mediump vec4 color;\n"
+ << "varying mediump vec2 texCoord;\n";
+
+ for (GLint textureIndex = 0; textureIndex < fragmentTextureCount; ++textureIndex)
+ {
+ fragmentShaderStr << "uniform sampler2D " << fragPrefix << textureIndex << ";\n";
+ }
+
+ fragmentShaderStr << "void main() {\n"
+ << " gl_FragColor = color;\n";
+
+ for (GLint textureIndex = 0; textureIndex < fragmentActiveTextureCount; ++textureIndex)
+ {
+ fragmentShaderStr << " gl_FragColor += texture2D(" << fragPrefix << textureIndex
+ << ", texCoord);\n";
+ }
+
+ fragmentShaderStr << "}";
+
+ const std::string &vertexShaderSource = vertexShaderStr.str();
+ const std::string &fragmentShaderSource = fragmentShaderStr.str();
+
+ mProgram = CompileProgram(vertexShaderSource, fragmentShaderSource);
+ }
+
+ RGBA8 getPixel(GLint texIndex)
+ {
+ RGBA8 pixel = {static_cast<uint8_t>(texIndex & 0x7u), static_cast<uint8_t>(texIndex >> 3),
+ 0, 255u};
+ return pixel;
+ }
+
+ void initTextures(GLint tex2DCount, GLint texCubeCount)
+ {
+ GLint totalCount = tex2DCount + texCubeCount;
+ mTextures.assign(totalCount, 0);
+ glGenTextures(totalCount, &mTextures[0]);
+ ASSERT_GL_NO_ERROR();
+
+ std::vector<RGBA8> texData(16 * 16);
+
+ GLint texIndex = 0;
+ for (; texIndex < tex2DCount; ++texIndex)
+ {
+ texData.assign(texData.size(), getPixel(texIndex));
+ glActiveTexture(GL_TEXTURE0 + texIndex);
+ glBindTexture(GL_TEXTURE_2D, mTextures[texIndex]);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ &texData[0]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+
+ ASSERT_GL_NO_ERROR();
+
+ for (; texIndex < texCubeCount; ++texIndex)
+ {
+ texData.assign(texData.size(), getPixel(texIndex));
+ glActiveTexture(GL_TEXTURE0 + texIndex);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[texIndex]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &texData[0]);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void testWithTextures(GLint vertexTextureCount,
+ const std::string &vertexTexturePrefix,
+ GLint fragmentTextureCount,
+ const std::string &fragmentTexturePrefix)
+ {
+ // Generate textures
+ initTextures(vertexTextureCount + fragmentTextureCount, 0);
+
+ glUseProgram(mProgram);
+ RGBA8 expectedSum = {0};
+ for (GLint texIndex = 0; texIndex < vertexTextureCount; ++texIndex)
+ {
+ std::stringstream uniformNameStr;
+ uniformNameStr << vertexTexturePrefix << texIndex;
+ const std::string &uniformName = uniformNameStr.str();
+ GLint location = glGetUniformLocation(mProgram, uniformName.c_str());
+ ASSERT_NE(-1, location);
+
+ glUniform1i(location, texIndex);
+ RGBA8 contribution = getPixel(texIndex);
+ expectedSum.R += contribution.R;
+ expectedSum.G += contribution.G;
+ }
+
+ for (GLint texIndex = 0; texIndex < fragmentTextureCount; ++texIndex)
+ {
+ std::stringstream uniformNameStr;
+ uniformNameStr << fragmentTexturePrefix << texIndex;
+ const std::string &uniformName = uniformNameStr.str();
+ GLint location = glGetUniformLocation(mProgram, uniformName.c_str());
+ ASSERT_NE(-1, location);
+
+ glUniform1i(location, texIndex + vertexTextureCount);
+ RGBA8 contribution = getPixel(texIndex + vertexTextureCount);
+ expectedSum.R += contribution.R;
+ expectedSum.G += contribution.G;
+ }
+
+ ASSERT_GE(256u, expectedSum.G);
+
+ drawQuad(mProgram, "position", 0.5f);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_EQ(0, 0, expectedSum.R, expectedSum.G, 0, 255);
+ }
+
+ GLuint mProgram;
+ std::vector<GLuint> mTextures;
+ GLint mMaxVertexTextures;
+ GLint mMaxFragmentTextures;
+ GLint mMaxCombinedTextures;
+};
+
+// Test rendering with the maximum vertex texture units.
+TEST_P(TextureLimitsTest, MaxVertexTextures)
+{
+ compileProgramWithTextureCounts("tex", mMaxVertexTextures, mMaxVertexTextures, "tex", 0, 0);
+ ASSERT_NE(0u, mProgram);
+ ASSERT_GL_NO_ERROR();
+
+ testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
+}
+
+// Test rendering with the maximum fragment texture units.
+TEST_P(TextureLimitsTest, MaxFragmentTextures)
+{
+ compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures, mMaxFragmentTextures);
+ ASSERT_NE(0u, mProgram);
+ ASSERT_GL_NO_ERROR();
+
+ testWithTextures(mMaxFragmentTextures, "tex", 0, "tex");
+}
+
+// Test rendering with maximum combined texture units.
+TEST_P(TextureLimitsTest, MaxCombinedTextures)
+{
+ GLint vertexTextures = mMaxVertexTextures;
+
+ if (vertexTextures + mMaxFragmentTextures > mMaxCombinedTextures)
+ {
+ vertexTextures = mMaxCombinedTextures - mMaxFragmentTextures;
+ }
+
+ compileProgramWithTextureCounts("vtex", vertexTextures, vertexTextures, "ftex",
+ mMaxFragmentTextures, mMaxFragmentTextures);
+ ASSERT_NE(0u, mProgram);
+ ASSERT_GL_NO_ERROR();
+
+ testWithTextures(vertexTextures, "vtex", mMaxFragmentTextures, "ftex");
+}
+
+// Negative test for exceeding the number of vertex textures
+TEST_P(TextureLimitsTest, ExcessiveVertexTextures)
+{
+ compileProgramWithTextureCounts("tex", mMaxVertexTextures + 1, mMaxVertexTextures + 1, "tex", 0,
+ 0);
+ ASSERT_EQ(0u, mProgram);
+}
+
+// Negative test for exceeding the number of fragment textures
+TEST_P(TextureLimitsTest, ExcessiveFragmentTextures)
+{
+ compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 1,
+ mMaxFragmentTextures + 1);
+ ASSERT_EQ(0u, mProgram);
+}
+
+// Test active vertex textures under the limit, but excessive textures specified.
+TEST_P(TextureLimitsTest, MaxActiveVertexTextures)
+{
+ compileProgramWithTextureCounts("tex", mMaxVertexTextures + 4, mMaxVertexTextures, "tex", 0, 0);
+ ASSERT_NE(0u, mProgram);
+ ASSERT_GL_NO_ERROR();
+
+ testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
+}
+
+// Test active fragment textures under the limit, but excessive textures specified.
+TEST_P(TextureLimitsTest, MaxActiveFragmentTextures)
+{
+ compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 4,
+ mMaxFragmentTextures);
+ ASSERT_NE(0u, mProgram);
+ ASSERT_GL_NO_ERROR();
+
+ testWithTextures(0, "tex", mMaxFragmentTextures, "tex");
+}
+
+// Negative test for pointing two sampler uniforms of different types to the same texture.
+TEST_P(TextureLimitsTest, TextureTypeConflict)
+{
+ const std::string &vertexShader =
+ "attribute vec2 position;\n"
+ "varying float color;\n"
+ "uniform sampler2D tex2D;\n"
+ "uniform samplerCube texCube;\n"
+ "void main() {\n"
+ " gl_Position = vec4(position, 0, 1);\n"
+ " vec2 texCoord = (position * 0.5) + 0.5;\n"
+ " color = texture2D(tex2D, texCoord).x;\n"
+ " color += textureCube(texCube, vec3(texCoord, 0)).x;\n"
+ "}";
+ const std::string &fragmentShader =
+ "varying mediump float color;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(color, 0, 0, 1);\n"
+ "}";
+
+ mProgram = CompileProgram(vertexShader, fragmentShader);
+ ASSERT_NE(0u, mProgram);
+
+ initTextures(1, 0);
+
+ glUseProgram(mProgram);
+ GLint tex2DLocation = glGetUniformLocation(mProgram, "tex2D");
+ ASSERT_NE(-1, tex2DLocation);
+ GLint texCubeLocation = glGetUniformLocation(mProgram, "texCube");
+ ASSERT_NE(-1, texCubeLocation);
+
+ glUniform1i(tex2DLocation, 0);
+ glUniform1i(texCubeLocation, 0);
+ ASSERT_GL_NO_ERROR();
+
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+// Negative test for rendering with texture outside the valid range.
+// TODO(jmadill): Research if this is correct.
+TEST_P(TextureLimitsTest, DrawWithTexturePastMaximum)
+{
+ const std::string &vertexShader =
+ "attribute vec2 position;\n"
+ "varying float color;\n"
+ "uniform sampler2D tex2D;\n"
+ "void main() {\n"
+ " gl_Position = vec4(position, 0, 1);\n"
+ " vec2 texCoord = (position * 0.5) + 0.5;\n"
+ " color = texture2D(tex2D, texCoord).x;\n"
+ "}";
+ const std::string &fragmentShader =
+ "varying mediump float color;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(color, 0, 0, 1);\n"
+ "}";
+
+ mProgram = CompileProgram(vertexShader, fragmentShader);
+ ASSERT_NE(0u, mProgram);
+
+ glUseProgram(mProgram);
+ GLint tex2DLocation = glGetUniformLocation(mProgram, "tex2D");
+ ASSERT_NE(-1, tex2DLocation);
+
+ glUniform1i(tex2DLocation, mMaxCombinedTextures);
+ ASSERT_GL_NO_ERROR();
+
+ drawQuad(mProgram, "position", 0.5f);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
ANGLE_INSTANTIATE_TEST(TextureTest, ES2_D3D9(), ES2_D3D11(), ES2_D3D11_FL9_3()); // TODO(geofflang): Figure out why this test fails on Intel OpenGL
ANGLE_INSTANTIATE_TEST(TextureTestES3, ES3_D3D11(), ES3_OPENGL());
+ANGLE_INSTANTIATE_TEST(TextureLimitsTest, ES2_D3D11(), ES2_OPENGL());
} // namespace