| // | |
| // 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. | |
| // | |
| // ProvkingVertexTest: | |
| // Tests on the conformance of the provoking vertex, which applies to flat | |
| // shading and compatibility with D3D. See the section on 'flatshading' | |
| // in the ES 3 specs. | |
| // | |
| #include "test_utils/ANGLETest.h" | |
| using namespace angle; | |
| namespace | |
| { | |
| class ProvokingVertexTest : public ANGLETest | |
| { | |
| protected: | |
| ProvokingVertexTest() | |
| : mProgram(0), | |
| mFramebuffer(0), | |
| mTexture(0), | |
| mTransformFeedback(0), | |
| mBuffer(0), | |
| mIntAttribLocation(-1) | |
| { | |
| setWindowWidth(64); | |
| setWindowHeight(64); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| setConfigDepthBits(24); | |
| } | |
| void testSetUp() override | |
| { | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in int intAttrib;\n" | |
| "in vec2 position;\n" | |
| "flat out int attrib;\n" | |
| "void main() {\n" | |
| " gl_Position = vec4(position, 0, 1);\n" | |
| " attrib = intAttrib;\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "flat in int attrib;\n" | |
| "out int fragColor;\n" | |
| "void main() {\n" | |
| " fragColor = attrib;\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("attrib"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_SEPARATE_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glGenTextures(1, &mTexture); | |
| glBindTexture(GL_TEXTURE_2D, mTexture); | |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, getWindowWidth(), getWindowHeight()); | |
| glGenFramebuffers(1, &mFramebuffer); | |
| glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0); | |
| mIntAttribLocation = glGetAttribLocation(mProgram, "intAttrib"); | |
| ASSERT_NE(-1, mIntAttribLocation); | |
| glEnableVertexAttribArray(mIntAttribLocation); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| void testTearDown() override | |
| { | |
| if (mProgram != 0) | |
| { | |
| glDeleteProgram(mProgram); | |
| mProgram = 0; | |
| } | |
| if (mFramebuffer != 0) | |
| { | |
| glDeleteFramebuffers(1, &mFramebuffer); | |
| mFramebuffer = 0; | |
| } | |
| if (mTexture != 0) | |
| { | |
| glDeleteTextures(1, &mTexture); | |
| mTexture = 0; | |
| } | |
| if (mTransformFeedback != 0) | |
| { | |
| glDeleteTransformFeedbacks(1, &mTransformFeedback); | |
| mTransformFeedback = 0; | |
| } | |
| if (mBuffer != 0) | |
| { | |
| glDeleteBuffers(1, &mBuffer); | |
| mBuffer = 0; | |
| } | |
| } | |
| GLuint mProgram; | |
| GLuint mFramebuffer; | |
| GLuint mTexture; | |
| GLuint mTransformFeedback; | |
| GLuint mBuffer; | |
| GLint mIntAttribLocation; | |
| }; | |
| // Test drawing a simple triangle with flat shading, and different valued vertices. | |
| TEST_P(ProvokingVertexTest, FlatTriangle) | |
| { | |
| GLint vertexData[] = {1, 2, 3, 1, 2, 3}; | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| drawQuad(mProgram, "position", 0.5f); | |
| GLint pixelValue = 0; | |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &pixelValue); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_EQ(vertexData[2], pixelValue); | |
| } | |
| // Ensure that any provoking vertex shenanigans still gives correct vertex streams. | |
| TEST_P(ProvokingVertexTest, FlatTriWithTransformFeedback) | |
| { | |
| // http://anglebug.com/4092 | |
| ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan()); | |
| // TODO(cwallez) figure out why it is broken on AMD on Mac | |
| ANGLE_SKIP_TEST_IF(IsOSX() && IsAMD()); | |
| glGenTransformFeedbacks(1, &mTransformFeedback); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glGenBuffers(1, &mBuffer); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 128, nullptr, GL_STREAM_DRAW); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mBuffer); | |
| GLint vertexData[] = {1, 2, 3, 1, 2, 3}; | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| GLint pixelValue = 0; | |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &pixelValue); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_EQ(vertexData[2], pixelValue); | |
| void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(int) * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| int *mappedInts = static_cast<int *>(mapPointer); | |
| for (unsigned int cnt = 0; cnt < 6; ++cnt) | |
| { | |
| EXPECT_EQ(vertexData[cnt], mappedInts[cnt]); | |
| } | |
| } | |
| // Test drawing a simple line with flat shading, and different valued vertices. | |
| TEST_P(ProvokingVertexTest, FlatLine) | |
| { | |
| // http://anglebug.com/4092 | |
| ANGLE_SKIP_TEST_IF((IsWindows() || IsLinux()) && IsVulkan()); | |
| GLfloat halfPixel = 1.0f / static_cast<GLfloat>(getWindowWidth()); | |
| GLint vertexData[] = {1, 2}; | |
| GLfloat positionData[] = {-1.0f + halfPixel, -1.0f, -1.0f + halfPixel, 1.0f}; | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glEnableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, positionData); | |
| glUseProgram(mProgram); | |
| glDrawArrays(GL_LINES, 0, 2); | |
| GLint pixelValue = 0; | |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &pixelValue); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_EQ(vertexData[1], pixelValue); | |
| } | |
| // Test drawing a simple triangle strip with flat shading, and different valued vertices. | |
| TEST_P(ProvokingVertexTest, FlatTriStrip) | |
| { | |
| GLint vertexData[] = {1, 2, 3, 4, 5, 6}; | |
| GLfloat positionData[] = {-1.0f, -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, | |
| 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f}; | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glEnableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, positionData); | |
| glUseProgram(mProgram); | |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); | |
| std::vector<GLint> pixelBuffer(getWindowWidth() * getWindowHeight(), 0); | |
| glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RED_INTEGER, GL_INT, | |
| &pixelBuffer[0]); | |
| ASSERT_GL_NO_ERROR(); | |
| for (unsigned int triIndex = 0; triIndex < 4; ++triIndex) | |
| { | |
| GLfloat sumX = positionData[triIndex * 2 + 0] + positionData[triIndex * 2 + 2] + | |
| positionData[triIndex * 2 + 4]; | |
| GLfloat sumY = positionData[triIndex * 2 + 1] + positionData[triIndex * 2 + 3] + | |
| positionData[triIndex * 2 + 5]; | |
| float centerX = sumX / 3.0f * 0.5f + 0.5f; | |
| float centerY = sumY / 3.0f * 0.5f + 0.5f; | |
| unsigned int pixelX = | |
| static_cast<unsigned int>(centerX * static_cast<GLfloat>(getWindowWidth())); | |
| unsigned int pixelY = | |
| static_cast<unsigned int>(centerY * static_cast<GLfloat>(getWindowHeight())); | |
| unsigned int pixelIndex = pixelY * getWindowWidth() + pixelX; | |
| unsigned int provokingVertexIndex = triIndex + 2; | |
| EXPECT_EQ(vertexData[provokingVertexIndex], pixelBuffer[pixelIndex]); | |
| } | |
| } | |
| // Test drawing an indexed triangle strip with flat shading and primitive restart. | |
| TEST_P(ProvokingVertexTest, FlatTriStripPrimitiveRestart) | |
| { | |
| // TODO(jmadill): Implement on the D3D back-end. | |
| ANGLE_SKIP_TEST_IF(IsD3D11()); | |
| GLint indexData[] = {0, 1, 2, -1, 1, 2, 3, 4, -1, 3, 4, 5}; | |
| GLint vertexData[] = {1, 2, 3, 4, 5, 6}; | |
| GLfloat positionData[] = {-1.0f, -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, | |
| 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f}; | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glEnableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, positionData); | |
| glDisable(GL_CULL_FACE); | |
| glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); | |
| glUseProgram(mProgram); | |
| glDrawElements(GL_TRIANGLE_STRIP, 12, GL_UNSIGNED_INT, indexData); | |
| std::vector<GLint> pixelBuffer(getWindowWidth() * getWindowHeight(), 0); | |
| glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RED_INTEGER, GL_INT, | |
| &pixelBuffer[0]); | |
| ASSERT_GL_NO_ERROR(); | |
| // Account for primitive restart when checking the tris. | |
| GLint triOffsets[] = {0, 4, 5, 9}; | |
| for (unsigned int triIndex = 0; triIndex < 4; ++triIndex) | |
| { | |
| GLint vertexA = indexData[triOffsets[triIndex] + 0]; | |
| GLint vertexB = indexData[triOffsets[triIndex] + 1]; | |
| GLint vertexC = indexData[triOffsets[triIndex] + 2]; | |
| GLfloat sumX = | |
| positionData[vertexA * 2] + positionData[vertexB * 2] + positionData[vertexC * 2]; | |
| GLfloat sumY = positionData[vertexA * 2 + 1] + positionData[vertexB * 2 + 1] + | |
| positionData[vertexC * 2 + 1]; | |
| float centerX = sumX / 3.0f * 0.5f + 0.5f; | |
| float centerY = sumY / 3.0f * 0.5f + 0.5f; | |
| unsigned int pixelX = | |
| static_cast<unsigned int>(centerX * static_cast<GLfloat>(getWindowWidth())); | |
| unsigned int pixelY = | |
| static_cast<unsigned int>(centerY * static_cast<GLfloat>(getWindowHeight())); | |
| unsigned int pixelIndex = pixelY * getWindowWidth() + pixelX; | |
| unsigned int provokingVertexIndex = triIndex + 2; | |
| EXPECT_EQ(vertexData[provokingVertexIndex], pixelBuffer[pixelIndex]); | |
| } | |
| } | |
| // Test with FRONT_CONVENTION if we have ANGLE_provoking_vertex. | |
| TEST_P(ProvokingVertexTest, ANGLEProvokingVertex) | |
| { | |
| int32_t vertexData[] = {1, 2, 3}; | |
| float positionData[] = {-1.0f, -1.0f, 3.0f, -1.0f, -1.0f, 3.0f}; | |
| glEnableVertexAttribArray(mIntAttribLocation); | |
| glVertexAttribIPointer(mIntAttribLocation, 1, GL_INT, 0, vertexData); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glEnableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, positionData); | |
| glUseProgram(mProgram); | |
| ASSERT_GL_NO_ERROR(); | |
| const auto &fnExpectId = [&](int id) { | |
| const int32_t zero[4] = {}; | |
| glClearBufferiv(GL_COLOR, 0, zero); | |
| glDrawArrays(GL_TRIANGLES, 0, 3); | |
| int32_t pixelValue = 0; | |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &pixelValue); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_EQ(vertexData[id], pixelValue); | |
| }; | |
| fnExpectId(2); | |
| const bool hasExt = IsGLExtensionEnabled("GL_ANGLE_provoking_vertex"); | |
| if (IsD3D11()) | |
| { | |
| EXPECT_TRUE(hasExt); | |
| } | |
| if (hasExt) | |
| { | |
| glProvokingVertexANGLE(GL_FIRST_VERTEX_CONVENTION); | |
| fnExpectId(0); | |
| } | |
| } | |
| ANGLE_INSTANTIATE_TEST(ProvokingVertexTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES()); | |
| } // anonymous namespace |