| // | |
| // 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" | |
| #include "util/EGLWindow.h" | |
| #include "util/gles_loader_autogen.h" | |
| #include "util/random_utils.h" | |
| using namespace angle; | |
| namespace | |
| { | |
| class TransformFeedbackTestBase : public ANGLETest | |
| { | |
| protected: | |
| TransformFeedbackTestBase() : mProgram(0), mTransformFeedbackBuffer(0), mTransformFeedback(0) | |
| { | |
| setWindowWidth(128); | |
| setWindowHeight(128); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| } | |
| void testSetUp() override | |
| { | |
| glGenBuffers(1, &mTransformFeedbackBuffer); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, nullptr, | |
| GL_STATIC_DRAW); | |
| glGenTransformFeedbacks(1, &mTransformFeedback); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| void testTearDown() override | |
| { | |
| if (mProgram != 0) | |
| { | |
| glDeleteProgram(mProgram); | |
| mProgram = 0; | |
| } | |
| if (mTransformFeedbackBuffer != 0) | |
| { | |
| glDeleteBuffers(1, &mTransformFeedbackBuffer); | |
| mTransformFeedbackBuffer = 0; | |
| } | |
| if (mTransformFeedback != 0) | |
| { | |
| glDeleteTransformFeedbacks(1, &mTransformFeedback); | |
| mTransformFeedback = 0; | |
| } | |
| } | |
| GLuint mProgram; | |
| static const size_t mTransformFeedbackBufferSize = 1 << 24; | |
| GLuint mTransformFeedbackBuffer; | |
| GLuint mTransformFeedback; | |
| }; | |
| class TransformFeedbackTest : public TransformFeedbackTestBase | |
| { | |
| protected: | |
| void compileDefaultProgram(const std::vector<std::string> &tfVaryings, GLenum bufferMode) | |
| { | |
| ASSERT_EQ(0u, mProgram); | |
| mProgram = CompileProgramWithTransformFeedback( | |
| essl1_shaders::vs::Simple(), essl1_shaders::fs::Red(), tfVaryings, bufferMode); | |
| ASSERT_NE(0u, mProgram); | |
| } | |
| }; | |
| TEST_P(TransformFeedbackTest, ZeroSizedViewport) | |
| { | |
| // Set the program's transform feedback varyings (just gl_Position) | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| glUseProgram(mProgram); | |
| // Bind the buffer for transform feedback output and start transform feedback | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| // Create a query to check how many primitives were written | |
| GLuint primitivesWrittenQuery = 0; | |
| glGenQueries(1, &primitivesWrittenQuery); | |
| glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, primitivesWrittenQuery); | |
| // Set a viewport that would result in no pixels being written to the framebuffer and draw | |
| // a quad | |
| glViewport(0, 0, 0, 0); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| // End the query and transform feedback | |
| glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| // Check how many primitives were written and verify that some were written even if | |
| // no pixels were rendered | |
| GLuint primitivesWritten = 0; | |
| glGetQueryObjectuiv(primitivesWrittenQuery, GL_QUERY_RESULT_EXT, &primitivesWritten); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(2u, primitivesWritten); | |
| } | |
| // Test that rebinding a buffer with the same offset resets the offset (no longer appending from the | |
| // old position) | |
| TEST_P(TransformFeedbackTest, BufferRebinding) | |
| { | |
| glDisable(GL_DEPTH_TEST); | |
| // Set the program's transform feedback varyings (just gl_Position) | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| glUseProgram(mProgram); | |
| // Make sure the buffer has zero'd data | |
| std::vector<float> data(mTransformFeedbackBufferSize / sizeof(float), 0.0f); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, data.data(), | |
| GL_STATIC_DRAW); | |
| // Create a query to check how many primitives were written | |
| GLuint primitivesWrittenQuery = 0; | |
| glGenQueries(1, &primitivesWrittenQuery); | |
| glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, primitivesWrittenQuery); | |
| const float finalZ = 0.95f; | |
| RNG rng; | |
| const size_t loopCount = 64; | |
| for (size_t loopIdx = 0; loopIdx < loopCount; loopIdx++) | |
| { | |
| // Bind the buffer for transform feedback output and start transform feedback | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| float z = (loopIdx + 1 == loopCount) ? finalZ : rng.randomFloatBetween(0.1f, 0.5f); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), z); | |
| glEndTransformFeedback(); | |
| } | |
| // End the query and transform feedback | |
| glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); | |
| glUseProgram(0); | |
| // Check how many primitives were written and verify that some were written even if | |
| // no pixels were rendered | |
| GLuint primitivesWritten = 0; | |
| glGetQueryObjectuiv(primitivesWrittenQuery, GL_QUERY_RESULT_EXT, &primitivesWritten); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(loopCount * 2, primitivesWritten); | |
| // Check the buffer data | |
| const float *bufferData = static_cast<float *>(glMapBufferRange( | |
| GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBufferSize, GL_MAP_READ_BIT)); | |
| for (size_t vertexIdx = 0; vertexIdx < 6; vertexIdx++) | |
| { | |
| // Check the third (Z) component of each vertex written and make sure it has the final | |
| // value | |
| EXPECT_NEAR(finalZ, bufferData[vertexIdx * 4 + 2], 0.0001); | |
| } | |
| for (size_t dataIdx = 24; dataIdx < mTransformFeedbackBufferSize / sizeof(float); dataIdx++) | |
| { | |
| EXPECT_EQ(data[dataIdx], bufferData[dataIdx]) << "Buffer overrun detected."; | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test that XFB can write back vertices to a buffer and that we can draw from this buffer | |
| // afterward. | |
| TEST_P(TransformFeedbackTest, RecordAndDraw) | |
| { | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| // Set the program's transform feedback varyings (just gl_Position) | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| glUseProgram(mProgram); | |
| GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); | |
| // First pass: draw 6 points to the XFB buffer | |
| glEnable(GL_RASTERIZER_DISCARD); | |
| const GLfloat vertices[] = { | |
| -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f, | |
| -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f, | |
| }; | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); | |
| glEnableVertexAttribArray(positionLocation); | |
| // Bind the buffer for transform feedback output and start transform feedback | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glBeginTransformFeedback(GL_POINTS); | |
| // Create a query to check how many primitives were written | |
| GLuint primitivesWrittenQuery = 0; | |
| glGenQueries(1, &primitivesWrittenQuery); | |
| glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, primitivesWrittenQuery); | |
| glDrawArrays(GL_POINTS, 0, 6); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| // End the query and transform feedback | |
| glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); | |
| glEndTransformFeedback(); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); | |
| glDisable(GL_RASTERIZER_DISCARD); | |
| // Check how many primitives were written and verify that some were written even if | |
| // no pixels were rendered | |
| GLuint primitivesWritten = 0; | |
| glGetQueryObjectuiv(primitivesWrittenQuery, GL_QUERY_RESULT_EXT, &primitivesWritten); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(6u, primitivesWritten); | |
| // Nothing should have been drawn to the framebuffer | |
| EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 0); | |
| // Second pass: draw from the feedback buffer | |
| glBindBuffer(GL_ARRAY_BUFFER, mTransformFeedbackBuffer); | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, 0); | |
| glEnableVertexAttribArray(positionLocation); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 0, 0, 255); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test that XFB does not allow writing more vertices than fit in the bound buffers. | |
| // TODO(jmadill): Enable this test after fixing the last case where the buffer size changes after | |
| // calling glBeginTransformFeedback. | |
| TEST_P(TransformFeedbackTest, DISABLED_TooSmallBuffers) | |
| { | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| glEnable(GL_RASTERIZER_DISCARD); | |
| // Set the program's transform feedback varyings (just gl_Position) | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); | |
| glUseProgram(mProgram); | |
| const GLfloat vertices[] = { | |
| -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f, | |
| -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f, | |
| }; | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); | |
| glEnableVertexAttribArray(positionLocation); | |
| const size_t verticesToDraw = 6; | |
| const size_t stride = sizeof(float) * 4; | |
| const size_t bytesNeeded = stride * verticesToDraw; | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| // Set up the buffer to be the right size | |
| uint8_t tfData[stride * verticesToDraw] = {0}; | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bytesNeeded, &tfData, GL_STATIC_DRAW); | |
| glBeginTransformFeedback(GL_POINTS); | |
| glDrawArrays(GL_POINTS, 0, verticesToDraw); | |
| EXPECT_GL_NO_ERROR(); | |
| glEndTransformFeedback(); | |
| // Set up the buffer to be too small | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bytesNeeded - 1, &tfData, GL_STATIC_DRAW); | |
| glBeginTransformFeedback(GL_POINTS); | |
| EXPECT_GL_NO_ERROR(); | |
| glDrawArrays(GL_POINTS, 0, verticesToDraw); | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glEndTransformFeedback(); | |
| // Set up the buffer to be the right size but make it smaller after glBeginTransformFeedback | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bytesNeeded, &tfData, GL_STATIC_DRAW); | |
| glBeginTransformFeedback(GL_POINTS); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bytesNeeded - 1, &tfData, GL_STATIC_DRAW); | |
| EXPECT_GL_NO_ERROR(); | |
| glDrawArrays(GL_POINTS, 0, verticesToDraw); | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glEndTransformFeedback(); | |
| } | |
| // Test that buffer binding happens only on the current transform feedback object | |
| TEST_P(TransformFeedbackTest, BufferBinding) | |
| { | |
| // Reset any state | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); | |
| // Generate a new buffer | |
| GLuint scratchBuffer = 0; | |
| glGenBuffers(1, &scratchBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Bind TF 0 and a buffer | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Check that the buffer ID matches the one that was just bound | |
| GLint currentBufferBinding = 0; | |
| glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, ¤tBufferBinding); | |
| EXPECT_EQ(static_cast<GLuint>(currentBufferBinding), mTransformFeedbackBuffer); | |
| glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, ¤tBufferBinding); | |
| EXPECT_EQ(static_cast<GLuint>(currentBufferBinding), mTransformFeedbackBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Check that the buffer ID for the newly bound transform feedback is zero | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, ¤tBufferBinding); | |
| EXPECT_EQ(0, currentBufferBinding); | |
| // But the generic bind point is unaffected by glBindTransformFeedback. | |
| glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, ¤tBufferBinding); | |
| EXPECT_EQ(static_cast<GLuint>(currentBufferBinding), mTransformFeedbackBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Bind a buffer to this TF | |
| glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, scratchBuffer, 0, 32); | |
| glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, ¤tBufferBinding); | |
| EXPECT_EQ(static_cast<GLuint>(currentBufferBinding), scratchBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Rebind the original TF and check it's bindings | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); | |
| glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, ¤tBufferBinding); | |
| EXPECT_EQ(static_cast<GLuint>(currentBufferBinding), mTransformFeedbackBuffer); | |
| EXPECT_GL_NO_ERROR(); | |
| // Clean up | |
| glDeleteBuffers(1, &scratchBuffer); | |
| } | |
| // Test that we can capture varyings only used in the vertex shader. | |
| TEST_P(TransformFeedbackTest, VertexOnly) | |
| { | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec2 position;\n" | |
| "in float attrib;\n" | |
| "out float varyingAttrib;\n" | |
| "void main() {\n" | |
| " gl_Position = vec4(position, 0, 1);\n" | |
| " varyingAttrib = attrib;\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "out mediump vec4 color;\n" | |
| "void main() {\n" | |
| " color = vec4(0.0, 1.0, 0.0, 1.0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("varyingAttrib"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glUseProgram(mProgram); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| std::vector<float> attribData; | |
| for (unsigned int cnt = 0; cnt < 100; ++cnt) | |
| { | |
| attribData.push_back(static_cast<float>(cnt)); | |
| } | |
| GLint attribLocation = glGetAttribLocation(mProgram, "attrib"); | |
| ASSERT_NE(-1, attribLocation); | |
| glVertexAttribPointer(attribLocation, 1, GL_FLOAT, GL_FALSE, 4, &attribData[0]); | |
| glEnableVertexAttribArray(attribLocation); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| ASSERT_GL_NO_ERROR(); | |
| glUseProgram(0); | |
| void *mappedBuffer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(float) * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mappedBuffer); | |
| float *mappedFloats = static_cast<float *>(mappedBuffer); | |
| for (unsigned int cnt = 0; cnt < 6; ++cnt) | |
| { | |
| EXPECT_EQ(attribData[cnt], mappedFloats[cnt]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test that multiple paused transform feedbacks do not generate errors or crash | |
| TEST_P(TransformFeedbackTest, MultiplePaused) | |
| { | |
| const size_t drawSize = 1024; | |
| std::vector<float> transformFeedbackData(drawSize); | |
| for (size_t i = 0; i < drawSize; i++) | |
| { | |
| transformFeedbackData[i] = static_cast<float>(i + 1); | |
| } | |
| // Initialize the buffers to zero | |
| size_t bufferSize = drawSize; | |
| std::vector<float> bufferInitialData(bufferSize, 0); | |
| const size_t transformFeedbackCount = 8; | |
| constexpr char kVS[] = R"(#version 300 es | |
| in highp vec4 position; | |
| in float transformFeedbackInput; | |
| out float transformFeedbackOutput; | |
| void main(void) | |
| { | |
| gl_Position = position; | |
| transformFeedbackOutput = transformFeedbackInput; | |
| })"; | |
| constexpr char kFS[] = R"(#version 300 es | |
| out mediump vec4 color; | |
| void main(void) | |
| { | |
| color = vec4(1.0, 1.0, 1.0, 1.0); | |
| })"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("transformFeedbackOutput"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glUseProgram(mProgram); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); | |
| GLint tfInputLocation = glGetAttribLocation(mProgram, "transformFeedbackInput"); | |
| glEnableVertexAttribArray(tfInputLocation); | |
| glVertexAttribPointer(tfInputLocation, 1, GL_FLOAT, false, 0, &transformFeedbackData[0]); | |
| glDepthMask(GL_FALSE); | |
| glEnable(GL_DEPTH_TEST); | |
| ASSERT_GL_NO_ERROR(); | |
| GLuint transformFeedbacks[transformFeedbackCount]; | |
| glGenTransformFeedbacks(transformFeedbackCount, transformFeedbacks); | |
| GLuint buffers[transformFeedbackCount]; | |
| glGenBuffers(transformFeedbackCount, buffers); | |
| for (size_t i = 0; i < transformFeedbackCount; i++) | |
| { | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[i]); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffers[i]); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize * sizeof(GLfloat), | |
| &bufferInitialData[0], GL_DYNAMIC_DRAW); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffers[i]); | |
| ASSERT_GL_NO_ERROR(); | |
| glBeginTransformFeedback(GL_POINTS); | |
| glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(drawSize)); | |
| glPauseTransformFeedback(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| for (size_t i = 0; i < transformFeedbackCount; i++) | |
| { | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[i]); | |
| glEndTransformFeedback(); | |
| glDeleteTransformFeedbacks(1, &transformFeedbacks[i]); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| } | |
| // Test that running multiple simultaneous queries and transform feedbacks from multiple EGL | |
| // contexts returns the correct results. Helps expose bugs in ANGLE's virtual contexts. | |
| TEST_P(TransformFeedbackTest, MultiContext) | |
| { | |
| // These tests are flaky, do not lift these unless you find the root cause and the fix. | |
| ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL()); | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsOpenGL()); | |
| EGLint contextAttributes[] = { | |
| EGL_CONTEXT_MAJOR_VERSION_KHR, | |
| GetParam().majorVersion, | |
| EGL_CONTEXT_MINOR_VERSION_KHR, | |
| GetParam().minorVersion, | |
| EGL_NONE, | |
| }; | |
| EGLWindow *window = getEGLWindow(); | |
| EGLDisplay display = window->getDisplay(); | |
| EGLConfig config = window->getConfig(); | |
| EGLSurface surface = window->getSurface(); | |
| const size_t passCount = 5; | |
| struct ContextInfo | |
| { | |
| EGLContext context; | |
| GLuint program; | |
| GLuint query; | |
| GLuint buffer; | |
| size_t primitiveCounts[passCount]; | |
| }; | |
| ContextInfo contexts[32]; | |
| const size_t maxDrawSize = 1024; | |
| std::vector<float> transformFeedbackData(maxDrawSize); | |
| for (size_t i = 0; i < maxDrawSize; i++) | |
| { | |
| transformFeedbackData[i] = static_cast<float>(i + 1); | |
| } | |
| // Initialize the buffers to zero | |
| size_t bufferSize = maxDrawSize * passCount; | |
| std::vector<float> bufferInitialData(bufferSize, 0); | |
| constexpr char kVS[] = R"(#version 300 es | |
| in highp vec4 position; | |
| in float transformFeedbackInput; | |
| out float transformFeedbackOutput; | |
| void main(void) | |
| { | |
| gl_Position = position; | |
| transformFeedbackOutput = transformFeedbackInput; | |
| })"; | |
| constexpr char kFS[] = R"(#version 300 es | |
| out mediump vec4 color; | |
| void main(void) | |
| { | |
| color = vec4(1.0, 1.0, 1.0, 1.0); | |
| })"; | |
| for (ContextInfo &context : contexts) | |
| { | |
| context.context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes); | |
| ASSERT_NE(context.context, EGL_NO_CONTEXT); | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("transformFeedbackOutput"); | |
| context.program = | |
| CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(context.program, 0u); | |
| glUseProgram(context.program); | |
| GLint positionLocation = glGetAttribLocation(context.program, "position"); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); | |
| GLint tfInputLocation = glGetAttribLocation(context.program, "transformFeedbackInput"); | |
| glEnableVertexAttribArray(tfInputLocation); | |
| glVertexAttribPointer(tfInputLocation, 1, GL_FLOAT, false, 0, &transformFeedbackData[0]); | |
| glDepthMask(GL_FALSE); | |
| glEnable(GL_DEPTH_TEST); | |
| glGenQueriesEXT(1, &context.query); | |
| glBeginQueryEXT(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, context.query); | |
| ASSERT_GL_NO_ERROR(); | |
| glGenBuffers(1, &context.buffer); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, context.buffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize * sizeof(GLfloat), | |
| &bufferInitialData[0], GL_DYNAMIC_DRAW); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, context.buffer); | |
| ASSERT_GL_NO_ERROR(); | |
| // For each pass, draw between 0 and maxDrawSize primitives | |
| for (auto &primCount : context.primitiveCounts) | |
| { | |
| primCount = rand() % maxDrawSize; | |
| } | |
| glBeginTransformFeedback(GL_POINTS); | |
| } | |
| for (size_t pass = 0; pass < passCount; pass++) | |
| { | |
| for (const auto &context : contexts) | |
| { | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(context.primitiveCounts[pass])); | |
| } | |
| } | |
| for (const auto &context : contexts) | |
| { | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| glEndTransformFeedback(); | |
| glEndQueryEXT(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); | |
| GLuint result = 0; | |
| glGetQueryObjectuivEXT(context.query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| size_t totalPrimCount = 0; | |
| for (const auto &primCount : context.primitiveCounts) | |
| { | |
| totalPrimCount += primCount; | |
| } | |
| EXPECT_EQ(static_cast<GLuint>(totalPrimCount), result); | |
| const float *bufferData = reinterpret_cast<float *>(glMapBufferRange( | |
| GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufferSize * sizeof(GLfloat), GL_MAP_READ_BIT)); | |
| size_t curBufferIndex = 0; | |
| unsigned int failures = 0; | |
| for (const auto &primCount : context.primitiveCounts) | |
| { | |
| for (size_t prim = 0; prim < primCount; prim++) | |
| { | |
| failures += (bufferData[curBufferIndex] != (prim + 1)) ? 1 : 0; | |
| curBufferIndex++; | |
| } | |
| } | |
| EXPECT_EQ(0u, failures); | |
| while (curBufferIndex < bufferSize) | |
| { | |
| EXPECT_EQ(bufferData[curBufferIndex], 0.0f); | |
| curBufferIndex++; | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| } | |
| eglMakeCurrent(display, surface, surface, window->getContext()); | |
| for (auto &context : contexts) | |
| { | |
| eglDestroyContext(display, context.context); | |
| context.context = EGL_NO_CONTEXT; | |
| } | |
| } | |
| // Test that when two vec2s are packed into the same register, we can still capture both of them. | |
| TEST_P(TransformFeedbackTest, PackingBug) | |
| { | |
| // TODO(jmadill): With points and rasterizer discard? | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec2 inAttrib1;\n" | |
| "in vec2 inAttrib2;\n" | |
| "out vec2 outAttrib1;\n" | |
| "out vec2 outAttrib2;\n" | |
| "in vec2 position;\n" | |
| "void main() {" | |
| " outAttrib1 = inAttrib1;\n" | |
| " outAttrib2 = inAttrib2;\n" | |
| " gl_Position = vec4(position, 0, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttrib1"); | |
| tfVaryings.push_back("outAttrib2"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector2) * 2 * 6, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| GLint attrib1Loc = glGetAttribLocation(mProgram, "inAttrib1"); | |
| GLint attrib2Loc = glGetAttribLocation(mProgram, "inAttrib2"); | |
| std::vector<Vector2> attrib1Data; | |
| std::vector<Vector2> attrib2Data; | |
| int counter = 0; | |
| for (size_t i = 0; i < 6; i++) | |
| { | |
| attrib1Data.push_back(Vector2(counter + 0.0f, counter + 1.0f)); | |
| attrib2Data.push_back(Vector2(counter + 2.0f, counter + 3.0f)); | |
| counter += 4; | |
| } | |
| glEnableVertexAttribArray(attrib1Loc); | |
| glEnableVertexAttribArray(attrib2Loc); | |
| glVertexAttribPointer(attrib1Loc, 2, GL_FLOAT, GL_FALSE, 0, attrib1Data.data()); | |
| glVertexAttribPointer(attrib2Loc, 2, GL_FLOAT, GL_FALSE, 0, attrib2Data.data()); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| ASSERT_GL_NO_ERROR(); | |
| const void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector2) * 2 * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const Vector2 *vecPointer = static_cast<const Vector2 *>(mapPointer); | |
| for (unsigned int vectorIndex = 0; vectorIndex < 3; ++vectorIndex) | |
| { | |
| unsigned int stream1Index = vectorIndex * 2; | |
| unsigned int stream2Index = vectorIndex * 2 + 1; | |
| EXPECT_EQ(attrib1Data[vectorIndex], vecPointer[stream1Index]); | |
| EXPECT_EQ(attrib2Data[vectorIndex], vecPointer[stream2Index]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that transform feedback varyings that can be optimized out yet do not cause program | |
| // compilation to fail | |
| TEST_P(TransformFeedbackTest, OptimizedVaryings) | |
| { | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec4 a_vertex;\n" | |
| "in vec3 a_normal; \n" | |
| "\n" | |
| "uniform Transform\n" | |
| "{\n" | |
| " mat4 u_modelViewMatrix;\n" | |
| " mat4 u_projectionMatrix;\n" | |
| " mat3 u_normalMatrix;\n" | |
| "};\n" | |
| "\n" | |
| "out vec3 normal;\n" | |
| "out vec4 ecPosition;\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " normal = normalize(u_normalMatrix * a_normal);\n" | |
| " ecPosition = u_modelViewMatrix * a_vertex;\n" | |
| " gl_Position = u_projectionMatrix * ecPosition;\n" | |
| "}\n"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "precision mediump float;\n" | |
| "\n" | |
| "in vec3 normal;\n" | |
| "in vec4 ecPosition;\n" | |
| "\n" | |
| "out vec4 fragColor;\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " fragColor = vec4(normal/2.0+vec3(0.5), 1);\n" | |
| "}\n"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("normal"); | |
| tfVaryings.push_back("ecPosition"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| } | |
| // Test an edge case where two varyings are unreferenced in the frag shader. | |
| TEST_P(TransformFeedbackTest, TwoUnreferencedInFragShader) | |
| { | |
| // TODO(jmadill): With points and rasterizer discard? | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 outAttrib1;\n" | |
| "out vec3 outAttrib2;\n" | |
| "void main() {" | |
| " outAttrib1 = position;\n" | |
| " outAttrib2 = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 outAttrib1;\n" | |
| "in vec3 outAttrib2;\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttrib1"); | |
| tfVaryings.push_back("outAttrib2"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector3) * 2 * 6, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| ASSERT_GL_NO_ERROR(); | |
| const void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector3) * 2 * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const auto &quadVertices = GetQuadVertices(); | |
| const Vector3 *vecPointer = static_cast<const Vector3 *>(mapPointer); | |
| for (unsigned int vectorIndex = 0; vectorIndex < 3; ++vectorIndex) | |
| { | |
| unsigned int stream1Index = vectorIndex * 2; | |
| unsigned int stream2Index = vectorIndex * 2 + 1; | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream1Index]); | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream2Index]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that the transform feedback write offset is reset to the buffer's offset when | |
| // glBeginTransformFeedback is called | |
| TEST_P(TransformFeedbackTest, OffsetResetOnBeginTransformFeedback) | |
| { | |
| ANGLE_SKIP_TEST_IF(IsOSX() && IsAMD()); | |
| ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES()); | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec4 position;\n" | |
| "out vec4 outAttrib;\n" | |
| "void main() {" | |
| " outAttrib = position;\n" | |
| " gl_Position = vec4(0);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttrib"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector4) * 2, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| Vector4 drawVertex0(4, 3, 2, 1); | |
| Vector4 drawVertex1(8, 7, 6, 5); | |
| Vector4 drawVertex2(12, 11, 10, 9); | |
| glEnableVertexAttribArray(positionLocation); | |
| glBeginTransformFeedback(GL_POINTS); | |
| // Write vertex 0 at offset 0 | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, false, 0, &drawVertex0); | |
| glDrawArrays(GL_POINTS, 0, 1); | |
| // Append vertex 1 | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, false, 0, &drawVertex1); | |
| glDrawArrays(GL_POINTS, 0, 1); | |
| glEndTransformFeedback(); | |
| glBeginTransformFeedback(GL_POINTS); | |
| // Write vertex 2 at offset 0 | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, false, 0, &drawVertex2); | |
| glDrawArrays(GL_POINTS, 0, 1); | |
| glEndTransformFeedback(); | |
| const void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector4) * 2, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const Vector4 *vecPointer = static_cast<const Vector4 *>(mapPointer); | |
| ASSERT_EQ(drawVertex2, vecPointer[0]); | |
| ASSERT_EQ(drawVertex1, vecPointer[1]); | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that the captured buffer can be copied to other buffers. | |
| TEST_P(TransformFeedbackTest, CaptureAndCopy) | |
| { | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| // Set the program's transform feedback varyings (just gl_Position) | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| glUseProgram(mProgram); | |
| GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); | |
| glEnable(GL_RASTERIZER_DISCARD); | |
| const GLfloat vertices[] = { | |
| -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f, | |
| -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f, | |
| }; | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); | |
| glEnableVertexAttribArray(positionLocation); | |
| // Bind the buffer for transform feedback output and start transform feedback | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glBeginTransformFeedback(GL_POINTS); | |
| glDrawArrays(GL_POINTS, 0, 6); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEndTransformFeedback(); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); | |
| glDisable(GL_RASTERIZER_DISCARD); | |
| // Allocate a buffer with one byte | |
| uint8_t singleByte[] = {0xaa}; | |
| // Create a new buffer and copy the first byte of captured data to it | |
| GLBuffer copyBuffer; | |
| glBindBuffer(GL_COPY_WRITE_BUFFER, copyBuffer); | |
| glBufferData(GL_COPY_WRITE_BUFFER, 1, singleByte, GL_DYNAMIC_DRAW); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glCopyBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 1); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| class TransformFeedbackLifetimeTest : public TransformFeedbackTest | |
| { | |
| protected: | |
| TransformFeedbackLifetimeTest() : mVertexArray(0) {} | |
| void testSetUp() override | |
| { | |
| glGenVertexArrays(1, &mVertexArray); | |
| glBindVertexArray(mVertexArray); | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_SEPARATE_ATTRIBS); | |
| glGenBuffers(1, &mTransformFeedbackBuffer); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, nullptr, | |
| GL_DYNAMIC_DRAW); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); | |
| glGenTransformFeedbacks(1, &mTransformFeedback); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| void testTearDown() override | |
| { | |
| glDeleteVertexArrays(1, &mVertexArray); | |
| TransformFeedbackTest::testTearDown(); | |
| } | |
| GLuint mVertexArray; | |
| }; | |
| // Tests a bug with state syncing and deleted transform feedback buffers. | |
| TEST_P(TransformFeedbackLifetimeTest, DeletedBuffer) | |
| { | |
| // First stream vertex data to mTransformFeedbackBuffer. | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| glEndTransformFeedback(); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); | |
| // TODO(jmadill): Remove this when http://anglebug.com/1351 is fixed. | |
| glBindVertexArray(0); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| glBindVertexArray(1); | |
| // Next, draw vertices with mTransformFeedbackBuffer. This will link to mVertexArray. | |
| glBindBuffer(GL_ARRAY_BUFFER, mTransformFeedbackBuffer); | |
| GLint loc = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib()); | |
| ASSERT_NE(-1, loc); | |
| glVertexAttribPointer(loc, 1, GL_FLOAT, GL_FALSE, 4, nullptr); | |
| glEnableVertexAttribArray(loc); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glDrawArrays(GL_TRIANGLES, 0, 3); | |
| // Delete resources, making a stranded pointer to mVertexArray in mTransformFeedbackBuffer. | |
| glDeleteBuffers(1, &mTransformFeedbackBuffer); | |
| mTransformFeedbackBuffer = 0; | |
| glDeleteVertexArrays(1, &mVertexArray); | |
| mVertexArray = 0; | |
| // Then draw again with transform feedback, dereferencing the stranded pointer. | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| glEndTransformFeedback(); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| class TransformFeedbackTestES31 : public TransformFeedbackTestBase | |
| {}; | |
| // Test that program link fails in case that transform feedback names including same array element. | |
| TEST_P(TransformFeedbackTestES31, SameArrayElementVaryings) | |
| { | |
| constexpr char kVS[] = | |
| "#version 310 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 outAttribs[3];\n" | |
| "void main() {" | |
| " outAttribs[0] = position;\n" | |
| " outAttribs[1] = vec3(0, 0, 0);\n" | |
| " outAttribs[2] = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 310 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 outAttribs[3];\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttribs"); | |
| tfVaryings.push_back("outAttribs[1]"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test that program link fails in case to capture array element on a non-array varying. | |
| TEST_P(TransformFeedbackTestES31, ElementCaptureOnNonArrayVarying) | |
| { | |
| constexpr char kVS[] = | |
| "#version 310 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 outAttrib;\n" | |
| "void main() {" | |
| " outAttrib = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 310 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 outAttrib;\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttrib[1]"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test that program link fails in case to capure an outbound array element. | |
| TEST_P(TransformFeedbackTestES31, CaptureOutboundElement) | |
| { | |
| constexpr char kVS[] = | |
| "#version 310 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 outAttribs[3];\n" | |
| "void main() {" | |
| " outAttribs[0] = position;\n" | |
| " outAttribs[1] = vec3(0, 0, 0);\n" | |
| " outAttribs[2] = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 310 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 outAttribs[3];\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttribs[3]"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test transform feedback names can be specified using array element. | |
| TEST_P(TransformFeedbackTestES31, DifferentArrayElementVaryings) | |
| { | |
| constexpr char kVS[] = | |
| "#version 310 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 outAttribs[3];\n" | |
| "void main() {" | |
| " outAttribs[0] = position;\n" | |
| " outAttribs[1] = vec3(0, 0, 0);\n" | |
| " outAttribs[2] = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 310 es\n" | |
| "precision mediump float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 outAttribs[3];\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("outAttribs[0]"); | |
| tfVaryings.push_back("outAttribs[2]"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector3) * 2 * 6, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| ASSERT_GL_NO_ERROR(); | |
| const GLvoid *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector3) * 2 * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const auto &quadVertices = GetQuadVertices(); | |
| const Vector3 *vecPointer = static_cast<const Vector3 *>(mapPointer); | |
| for (unsigned int vectorIndex = 0; vectorIndex < 3; ++vectorIndex) | |
| { | |
| unsigned int stream1Index = vectorIndex * 2; | |
| unsigned int stream2Index = vectorIndex * 2 + 1; | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream1Index]); | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream2Index]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test transform feedback varying for base-level members of struct. | |
| TEST_P(TransformFeedbackTestES31, StructMemberVaryings) | |
| { | |
| constexpr char kVS[] = R"(#version 310 es | |
| in vec3 position; | |
| struct S { | |
| vec3 field0; | |
| vec3 field1; | |
| vec3 field2; | |
| }; | |
| out S s; | |
| void main() { | |
| s.field0 = position; | |
| s.field1 = vec3(0, 0, 0); | |
| s.field2 = position; | |
| gl_Position = vec4(position, 1); | |
| })"; | |
| constexpr char kFS[] = R"(#version 310 es | |
| precision mediump float; | |
| struct S { | |
| vec3 field0; | |
| vec3 field1; | |
| vec3 field2; | |
| }; | |
| out vec4 color; | |
| in S s; | |
| void main() { | |
| color = vec4(s.field1, 1); | |
| })"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("s.field0"); | |
| tfVaryings.push_back("s.field2"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector3) * 2 * 6, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| ASSERT_GL_NO_ERROR(); | |
| const GLvoid *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector3) * 2 * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const auto &quadVertices = GetQuadVertices(); | |
| const Vector3 *vecPointer = static_cast<const Vector3 *>(mapPointer); | |
| for (unsigned int vectorIndex = 0; vectorIndex < 3; ++vectorIndex) | |
| { | |
| unsigned int stream1Index = vectorIndex * 2; | |
| unsigned int stream2Index = vectorIndex * 2 + 1; | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream1Index]); | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[stream2Index]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test transform feedback varying for struct is not allowed. | |
| TEST_P(TransformFeedbackTestES31, InvalidStructVaryings) | |
| { | |
| constexpr char kVS[] = R"(#version 310 es | |
| in vec3 position; | |
| struct S { | |
| vec3 field0; | |
| vec3 field1; | |
| }; | |
| out S s; | |
| void main() { | |
| s.field0 = position; | |
| s.field1 = vec3(0, 0, 0); | |
| gl_Position = vec4(position, 1); | |
| })"; | |
| constexpr char kFS[] = R"(#version 310 es | |
| precision mediump float; | |
| struct S { | |
| vec3 field0; | |
| vec3 field1; | |
| }; | |
| out vec4 color; | |
| in S s; | |
| void main() { | |
| color = vec4(s.field1, 1); | |
| })"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("s"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test that nonexistent transform feedback varyings don't assert when linking. | |
| TEST_P(TransformFeedbackTest, NonExistentTransformFeedbackVarying) | |
| { | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("bogus"); | |
| mProgram = CompileProgramWithTransformFeedback( | |
| essl3_shaders::vs::Simple(), essl3_shaders::fs::Red(), tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test that nonexistent transform feedback varyings don't assert when linking. In this test the | |
| // nonexistent varying is prefixed with "gl_". | |
| TEST_P(TransformFeedbackTest, NonExistentTransformFeedbackVaryingWithGLPrefix) | |
| { | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Bogus"); | |
| mProgram = CompileProgramWithTransformFeedback( | |
| essl3_shaders::vs::Simple(), essl3_shaders::fs::Red(), tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_EQ(0u, mProgram); | |
| } | |
| // Test transform feedback names can be reserved names in GLSL, as long as they're not reserved in | |
| // GLSL ES. | |
| TEST_P(TransformFeedbackTest, VaryingReservedOpenGLName) | |
| { | |
| constexpr char kVS[] = | |
| "#version 300 es\n" | |
| "in vec3 position;\n" | |
| "out vec3 buffer;\n" | |
| "void main() {\n" | |
| " buffer = position;\n" | |
| " gl_Position = vec4(position, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "#version 300 es\n" | |
| "precision highp float;\n" | |
| "out vec4 color;\n" | |
| "in vec3 buffer;\n" | |
| "void main() {\n" | |
| " color = vec4(0);\n" | |
| "}"; | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("buffer"); | |
| mProgram = CompileProgramWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ASSERT_NE(0u, mProgram); | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Vector3) * 6, nullptr, GL_STREAM_DRAW); | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glEndTransformFeedback(); | |
| glUseProgram(0); | |
| ASSERT_GL_NO_ERROR(); | |
| const GLvoid *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector3) * 6, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const auto &quadVertices = GetQuadVertices(); | |
| const Vector3 *vecPointer = static_cast<const Vector3 *>(mapPointer); | |
| for (unsigned int vectorIndex = 0; vectorIndex < 3; ++vectorIndex) | |
| { | |
| EXPECT_EQ(quadVertices[vectorIndex], vecPointer[vectorIndex]); | |
| } | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that calling BeginTransformFeedback when no program is currentwill generate an | |
| // INVALID_OPERATION error. | |
| TEST_P(TransformFeedbackTest, NoCurrentProgram) | |
| { | |
| glUseProgram(0); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| // GLES 3.0.5 section 2.15.2: "The error INVALID_OPERATION is also generated by | |
| // BeginTransformFeedback if no binding points would be used, either because no program object | |
| // is active or because the active program object has specified no output variables to record." | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| } | |
| // Test that calling BeginTransformFeedback when no transform feedback varyings are in use will | |
| // generate an INVALID_OPERATION error. | |
| TEST_P(TransformFeedbackTest, NoTransformFeedbackVaryingsInUse) | |
| { | |
| ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); | |
| glUseProgram(program); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| // GLES 3.0.5 section 2.15.2: "The error INVALID_OPERATION is also generated by | |
| // BeginTransformFeedback if no binding points would be used, either because no program object | |
| // is active or because the active program object has specified no output variables to record." | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| } | |
| // Test that you can pause transform feedback without drawing first. | |
| TEST_P(TransformFeedbackTest, SwitchProgramBeforeDraw) | |
| { | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); | |
| // Set up transform feedback, but pause it. | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| glPauseTransformFeedback(); | |
| // Switch programs and draw while transform feedback is paused. | |
| glUseProgram(nonTFProgram); | |
| GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib()); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); | |
| glDrawArrays(GL_TRIANGLES, 0, 3); | |
| glEndTransformFeedback(); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that ending transform feedback with a different program bound does not cause internal | |
| // errors. | |
| TEST_P(TransformFeedbackTest, EndWithDifferentProgram) | |
| { | |
| // AMD drivers fail because they perform transform feedback when it should be paused. | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); | |
| // Set up transform feedback, but pause it. | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| // Make sure the buffer has zero'd data | |
| std::vector<float> data(mTransformFeedbackBufferSize / sizeof(float), 0.0f); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, data.data(), | |
| GL_STATIC_DRAW); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| glPauseTransformFeedback(); | |
| // Transform feedback should not happen | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| // Draw using a different program. | |
| glUseProgram(nonTFProgram); | |
| GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib()); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); | |
| glDrawArrays(GL_TRIANGLES, 0, 3); | |
| // End transform feedback without unpausing and with a different program bound. This triggers | |
| // the bug. | |
| glEndTransformFeedback(); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| // On a buggy driver without the workaround this will cause a GL error because the driver | |
| // thinks transform feedback is still paused, but rendering will still write to the transform | |
| // feedback buffers. | |
| glPauseTransformFeedback(); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| glEndTransformFeedback(); | |
| // Make sure that transform feedback did not happen. We always paused transform feedback before | |
| // rendering, but a buggy driver will fail to pause. | |
| const void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector4) * 4, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const Vector4 *vecPointer = static_cast<const Vector4 *>(mapPointer); | |
| ASSERT_EQ(vecPointer[0], Vector4(0, 0, 0, 0)); | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that switching contexts with paused transform feedback does not cause internal errors. | |
| TEST_P(TransformFeedbackTest, EndWithDifferentProgramContextSwitch) | |
| { | |
| // AMD drivers fail because they perform transform feedback when it should be paused. | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| std::vector<std::string> tfVaryings; | |
| tfVaryings.push_back("gl_Position"); | |
| compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS); | |
| EGLWindow *window = getEGLWindow(); | |
| EGLDisplay display = window->getDisplay(); | |
| EGLConfig config = window->getConfig(); | |
| EGLSurface surface = window->getSurface(); | |
| EGLint contextAttributes[] = { | |
| EGL_CONTEXT_MAJOR_VERSION_KHR, | |
| GetParam().majorVersion, | |
| EGL_CONTEXT_MINOR_VERSION_KHR, | |
| GetParam().minorVersion, | |
| EGL_NONE, | |
| }; | |
| auto context1 = eglGetCurrentContext(); | |
| auto context2 = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes); | |
| ASSERT_NE(context2, EGL_NO_CONTEXT); | |
| // Compile a program on the second context. | |
| eglMakeCurrent(display, surface, surface, context2); | |
| ANGLE_GL_PROGRAM(nonTFProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red()); | |
| eglMakeCurrent(display, surface, surface, context1); | |
| // Set up transform feedback, but pause it. | |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback); | |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); | |
| // Make sure the buffer has zero'd data | |
| std::vector<float> data(mTransformFeedbackBufferSize / sizeof(float), 0.0f); | |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, data.data(), | |
| GL_STATIC_DRAW); | |
| glUseProgram(mProgram); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| glPauseTransformFeedback(); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| // Leave transform feedback active but paused while we switch to a second context and render | |
| // something. | |
| eglMakeCurrent(display, surface, surface, context2); | |
| glUseProgram(nonTFProgram); | |
| GLint positionLocation = glGetAttribLocation(nonTFProgram, essl1_shaders::PositionAttrib()); | |
| glDisableVertexAttribArray(positionLocation); | |
| glVertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); | |
| glDrawArrays(GL_TRIANGLES, 0, 3); | |
| // Switch back to the first context and end transform feedback. On a buggy driver, this will | |
| // cause the transform feedback object to enter an invalid "inactive, but paused" state unless | |
| // the workaround is applied. | |
| eglMakeCurrent(display, surface, surface, context1); | |
| glEndTransformFeedback(); | |
| glBeginTransformFeedback(GL_TRIANGLES); | |
| // On a buggy driver without the workaround this will cause a GL error because the driver | |
| // thinks transform feedback is still paused, but rendering will still write to the transform | |
| // feedback buffers. | |
| glPauseTransformFeedback(); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true); | |
| glEndTransformFeedback(); | |
| // Make sure that transform feedback did not happen. We always paused transform feedback before | |
| // rendering, but a buggy driver will fail to pause. | |
| const void *mapPointer = | |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(Vector4) * 4, GL_MAP_READ_BIT); | |
| ASSERT_NE(nullptr, mapPointer); | |
| const Vector4 *vecPointer = static_cast<const Vector4 *>(mapPointer); | |
| ASSERT_EQ(vecPointer[0], Vector4(0, 0, 0, 0)); | |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); | |
| eglDestroyContext(display, context2); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Use this to select which configurations (e.g. which renderer, which GLES major version) these | |
| // tests should be run against. | |
| ANGLE_INSTANTIATE_TEST_ES3(TransformFeedbackTest); | |
| ANGLE_INSTANTIATE_TEST_ES3(TransformFeedbackLifetimeTest); | |
| ANGLE_INSTANTIATE_TEST_ES31(TransformFeedbackTestES31); | |
| } // anonymous namespace |