| // |
| // Copyright 2017 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. |
| // |
| // Multiview draw tests: |
| // Test issuing multiview Draw* commands. |
| // |
| |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| using namespace angle; |
| |
| class MultiviewDrawTest : public ANGLETest |
| { |
| protected: |
| MultiviewDrawTest() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setWebGLCompatibilityEnabled(true); |
| } |
| virtual ~MultiviewDrawTest() {} |
| |
| void SetUp() override |
| { |
| ANGLETest::SetUp(); |
| |
| glRequestExtensionANGLE = reinterpret_cast<PFNGLREQUESTEXTENSIONANGLEPROC>( |
| eglGetProcAddress("glRequestExtensionANGLE")); |
| } |
| |
| // Requests the ANGLE_multiview extension and returns true if the operation succeeds. |
| bool requestMultiviewExtension() |
| { |
| if (extensionRequestable("GL_ANGLE_multiview")) |
| { |
| glRequestExtensionANGLE("GL_ANGLE_multiview"); |
| } |
| |
| if (!extensionEnabled("GL_ANGLE_multiview")) |
| { |
| std::cout << "Test skipped due to missing GL_ANGLE_multiview." << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| PFNGLREQUESTEXTENSIONANGLEPROC glRequestExtensionANGLE = nullptr; |
| }; |
| |
| class MultiviewDrawValidationTest : public MultiviewDrawTest |
| { |
| protected: |
| MultiviewDrawValidationTest() {} |
| |
| void SetUp() override |
| { |
| MultiviewDrawTest::SetUp(); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); |
| |
| glBindTexture(GL_TEXTURE_2D, mTex2d); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| |
| glBindVertexArray(mVao); |
| |
| const float kVertexData[3] = {0.0f}; |
| glBindBuffer(GL_ARRAY_BUFFER, mVbo); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3u, &kVertexData[0], GL_STATIC_DRAW); |
| |
| const unsigned int kIndices[3] = {0u, 1u, 2u}; |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIbo); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 3, &kIndices[0], |
| GL_STATIC_DRAW); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| GLTexture mTex2d; |
| GLVertexArray mVao; |
| GLBuffer mVbo; |
| GLBuffer mIbo; |
| GLFramebuffer mFramebuffer; |
| }; |
| |
| class MultiviewSideBySideRenderTest : public MultiviewDrawTest |
| { |
| protected: |
| void createFBO(int width, int height, int numViews) |
| { |
| // Assert that width is a multiple of numViews. |
| ASSERT_TRUE(width % numViews == 0); |
| const int widthPerView = width / numViews; |
| |
| // Create color and depth textures. |
| glBindTexture(GL_TEXTURE_2D, mColorTexture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| ASSERT_GL_NO_ERROR(); |
| |
| glBindTexture(GL_TEXTURE_2D, mDepthTexture); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, |
| GL_FLOAT, NULL); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Create draw framebuffer to be used for side-by-side rendering. |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer); |
| std::vector<GLint> viewportOffsets(numViews * 2u); |
| for (int i = 0u; i < numViews; ++i) |
| { |
| viewportOffsets[i * 2] = i * widthPerView; |
| viewportOffsets[i * 2 + 1] = 0; |
| } |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| mColorTexture, 0, numViews, |
| &viewportOffsets[0]); |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
| mDepthTexture, 0, numViews, |
| &viewportOffsets[0]); |
| |
| GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; |
| glDrawBuffers(1, DrawBuffers); |
| ASSERT_GL_NO_ERROR(); |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)); |
| |
| // Create read framebuffer to be used to retrieve the pixel information for testing |
| // purposes. |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer); |
| glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| mColorTexture, 0); |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); |
| |
| // Clear the buffers. |
| glViewport(0, 0, width, height); |
| glScissor(0, 0, width, height); |
| glClearColor(0, 0, 0, 0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| // Set viewport and scissor of each view. |
| glViewport(0, 0, widthPerView, height); |
| glScissor(0, 0, widthPerView, height); |
| } |
| |
| GLTexture mColorTexture; |
| GLTexture mDepthTexture; |
| GLFramebuffer mDrawFramebuffer; |
| GLFramebuffer mReadFramebuffer; |
| }; |
| |
| class MultiviewSideBySideRenderDualViewTest : public MultiviewSideBySideRenderTest |
| { |
| protected: |
| MultiviewSideBySideRenderDualViewTest() : mProgram(0u) {} |
| ~MultiviewSideBySideRenderDualViewTest() |
| { |
| if (mProgram != 0u) |
| { |
| glDeleteProgram(mProgram); |
| } |
| } |
| |
| void SetUp() override |
| { |
| MultiviewSideBySideRenderTest::SetUp(); |
| |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec4 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position.x = (gl_ViewID_OVR == 0u ? vPosition.x*0.5 + 0.5 : vPosition.x*0.5);\n" |
| " gl_Position.yzw = vPosition.yzw;\n" |
| "}\n"; |
| |
| const std::string fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(1,0,0,0);\n" |
| "}\n"; |
| |
| createFBO(4, 1, 2); |
| createProgram(vsSource, fsSource); |
| } |
| |
| void createProgram(const std::string &vs, const std::string &fs) |
| { |
| mProgram = CompileProgram(vs, fs); |
| if (mProgram == 0) |
| { |
| FAIL() << "shader compilation failed."; |
| } |
| glUseProgram(mProgram); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void checkOutput() |
| { |
| EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0); |
| EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0); |
| EXPECT_PIXEL_EQ(2, 0, 255, 0, 0, 0); |
| EXPECT_PIXEL_EQ(3, 0, 0, 0, 0, 0); |
| } |
| |
| GLuint mProgram; |
| }; |
| |
| // The test verifies that glDraw*Indirect: |
| // 1) generates an INVALID_OPERATION error if the number of views in the draw framebuffer is greater |
| // than 1. |
| // 2) does not generate any error if the draw framebuffer has exactly 1 view. |
| TEST_P(MultiviewDrawValidationTest, IndirectDraw) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const GLint viewportOffsets[4] = {0, 0, 2, 0}; |
| |
| const std::string fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| |
| GLBuffer commandBuffer; |
| glBindBuffer(GL_DRAW_INDIRECT_BUFFER, commandBuffer); |
| const GLuint commandData[] = {1u, 1u, 0u, 0u, 0u}; |
| glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(GLuint) * 5u, &commandData[0], GL_STATIC_DRAW); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check for a GL_INVALID_OPERATION error with the framebuffer having 2 views. |
| { |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 2, &viewportOffsets[0]); |
| |
| glDrawArraysIndirect(GL_TRIANGLES, nullptr); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Check that no errors are generated if the number of views is 1. |
| { |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "layout(num_views = 1) in;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 1, &viewportOffsets[0]); |
| |
| glDrawArraysIndirect(GL_TRIANGLES, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| |
| // The test verifies that glDraw*: |
| // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer and |
| // program differs. |
| // 2) does not generate any error if the number of views is the same. |
| // 3) does not generate any error if the program does not use the multiview extension. |
| TEST_P(MultiviewDrawValidationTest, NumViewsMismatch) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const GLint viewportOffsets[4] = {0, 0, 2, 0}; |
| |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{}\n"; |
| const std::string &fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| // Check for a GL_INVALID_OPERATION error with the framebuffer and program having different |
| // number of views. |
| { |
| // The framebuffer has only 1 view. |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 1, &viewportOffsets[0]); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Check that no errors are generated if the number of views in both program and draw |
| // framebuffer matches. |
| { |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 2, &viewportOffsets[0]); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Check that no errors are generated if the program does not use the multiview extension. |
| { |
| const std::string &vsSourceNoMultiview = |
| "#version 300 es\n" |
| "void main()\n" |
| "{}\n"; |
| const std::string &fsSourceNoMultiview = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(programNoMultiview, vsSourceNoMultiview, fsSourceNoMultiview); |
| glUseProgram(programNoMultiview); |
| |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 2, &viewportOffsets[0]); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| |
| // The test verifies that glDraw*: |
| // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer is |
| // greater than 1 and there is an active transform feedback object. |
| // 2) does not generate any error if the number of views in the draw framebuffer is 1. |
| TEST_P(MultiviewDrawValidationTest, ActiveTransformFeedback) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const GLint viewportOffsets[4] = {0, 0, 2, 0}; |
| |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "void main()\n" |
| "{}\n"; |
| const std::string &fsSource = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| GLBuffer tbo; |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo); |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(float) * 4u, nullptr, GL_STATIC_DRAW); |
| |
| GLTransformFeedback transformFeedback; |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedback); |
| glBeginTransformFeedback(GL_TRIANGLES); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that drawArrays generates an error when there is an active transform feedback object |
| // and the number of views in the draw framebuffer is greater than 1. |
| { |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 2, &viewportOffsets[0]); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Check that drawArrays does not generate an error when the number of views in the draw |
| // framebuffer is 1. |
| { |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 1, &viewportOffsets[0]); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| glEndTransformFeedback(); |
| } |
| |
| // The test verifies that glDraw*: |
| // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer is |
| // greater than 1 and there is an active query for target GL_TIME_ELAPSED_EXT. |
| // 2) does not generate any error if the number of views in the draw framebuffer is 1. |
| TEST_P(MultiviewDrawValidationTest, ActiveTimeElapsedQuery) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| if (!extensionEnabled("GL_EXT_disjoint_timer_query")) |
| { |
| std::cout << "Test skipped because GL_EXT_disjoint_timer_query is not available." |
| << std::endl; |
| return; |
| } |
| |
| const GLint viewportOffsets[4] = {0, 0, 2, 0}; |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "void main()\n" |
| "{}\n"; |
| const std::string &fsSource = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| GLuint query = 0u; |
| glGenQueriesEXT(1, &query); |
| glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query); |
| |
| // Check first case. |
| { |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 2, &viewportOffsets[0]); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Check second case. |
| { |
| glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTex2d, |
| 0, 1, &viewportOffsets[0]); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| glEndQueryEXT(GL_TIME_ELAPSED_EXT); |
| glDeleteQueries(1, &query); |
| } |
| |
| // The test checks that glDrawArrays can be used to render into two views. |
| TEST_P(MultiviewSideBySideRenderDualViewTest, DrawArrays) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| drawQuad(mProgram, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| checkOutput(); |
| } |
| |
| // The test checks that glDrawElements can be used to render into two views. |
| TEST_P(MultiviewSideBySideRenderDualViewTest, DrawElements) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| drawIndexedQuad(mProgram, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| checkOutput(); |
| } |
| |
| // The test checks that glDrawRangeElements can be used to render into two views. |
| TEST_P(MultiviewSideBySideRenderDualViewTest, DrawRangeElements) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| drawIndexedQuad(mProgram, "vPosition", 0.0f, 1.0f, true, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| checkOutput(); |
| } |
| |
| // The test checks that glDrawArrays can be used to render into four views. |
| TEST_P(MultiviewSideBySideRenderTest, DrawArraysFourViews) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "layout(num_views = 4) in;\n" |
| "in vec4 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " if (gl_ViewID_OVR == 0u) {\n" |
| " gl_Position.x = vPosition.x*0.25 - 0.75;\n" |
| " } else if (gl_ViewID_OVR == 1u) {\n" |
| " gl_Position.x = vPosition.x*0.25 - 0.25;\n" |
| " } else if (gl_ViewID_OVR == 2u) {\n" |
| " gl_Position.x = vPosition.x*0.25 + 0.25;\n" |
| " } else {\n" |
| " gl_Position.x = vPosition.x*0.25 + 0.75;\n" |
| " }" |
| " gl_Position.yzw = vPosition.yzw;\n" |
| "}\n"; |
| |
| const std::string fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(1,0,0,0);\n" |
| "}\n"; |
| |
| createFBO(16, 1, 4); |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| for (int i = 0; i < 4; ++i) |
| { |
| for (int j = 0; j < 4; ++j) |
| { |
| const int arrayIndex = i * 4 + j; |
| if (i == j) |
| { |
| EXPECT_PIXEL_EQ(arrayIndex, 0, 255, 0, 0, 0); |
| } |
| else |
| { |
| EXPECT_PIXEL_EQ(arrayIndex, 0, 0, 0, 0, 0); |
| } |
| } |
| } |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // The test checks that glDrawArraysInstanced can be used to render into two views. |
| TEST_P(MultiviewSideBySideRenderTest, DrawArraysInstanced) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec4 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " vec4 p = vPosition;\n" |
| " if (gl_InstanceID == 1){\n" |
| " p.y = .5*p.y + .5;\n" |
| " } else {\n" |
| " p.y = p.y*.5;\n" |
| " }\n" |
| " gl_Position.x = (gl_ViewID_OVR == 0u ? p.x*0.5 + 0.5 : p.x*0.5);\n" |
| " gl_Position.yzw = p.yzw;\n" |
| "}\n"; |
| |
| const std::string fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(1,0,0,0);\n" |
| "}\n"; |
| |
| createFBO(4, 2, 2); |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true, true, 2); |
| ASSERT_GL_NO_ERROR(); |
| |
| const GLubyte expectedResult[4][8] = { |
| {0, 255, 255, 0}, {0, 255, 255, 0}, |
| }; |
| for (int row = 0; row < 2; ++row) |
| { |
| for (int col = 0; col < 4; ++col) |
| { |
| EXPECT_PIXEL_EQ(col, row, expectedResult[row][col], 0, 0, 0); |
| } |
| } |
| } |
| |
| // The test verifies that the attribute divisor is correctly adjusted when drawing with a multi-view |
| // program. The test draws 4 instances of a quad each of which covers a single pixel. The x and y |
| // offset of each quad are passed as separate attributes which are indexed based on the |
| // corresponding attribute divisors. A divisor of 1 is used for the y offset to have all quads |
| // drawn vertically next to each other. A divisor of 3 is used for the x offset to have the last |
| // quad offsetted by one pixel to the right. Note that the number of views is divisible by 1, but |
| // not by 3. |
| TEST_P(MultiviewSideBySideRenderTest, AttribDivisor) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string &vsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "in float offsetX;\n" |
| "in float offsetY;\n" |
| "void main()\n" |
| "{\n" |
| " vec4 p = vec4(vPosition, 1.);\n" |
| " p.xy = p.xy * 0.25 - 0.75 + vec2(offsetX, offsetY);\n" |
| " gl_Position.x = (gl_ViewID_OVR == 0u ? p.x : p.x + 1.0);\n" |
| " gl_Position.yzw = p.yzw;\n" |
| "}\n"; |
| |
| const std::string &fsSource = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(1,0,0,0);\n" |
| "}\n"; |
| createFBO(8, 4, 2); |
| ANGLE_GL_PROGRAM(program, vsSource, fsSource); |
| glUseProgram(program); |
| |
| GLBuffer xOffsetVBO; |
| glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO); |
| const GLfloat xOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.0f}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, xOffsetData, GL_STATIC_DRAW); |
| GLint xOffsetLoc = glGetAttribLocation(program, "offsetX"); |
| glVertexAttribPointer(xOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glVertexAttribDivisor(xOffsetLoc, 3); |
| glEnableVertexAttribArray(xOffsetLoc); |
| |
| GLBuffer yOffsetVBO; |
| glBindBuffer(GL_ARRAY_BUFFER, yOffsetVBO); |
| const GLfloat yOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.5f}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, yOffsetData, GL_STATIC_DRAW); |
| GLint yOffsetLoc = glGetAttribLocation(program, "offsetY"); |
| glVertexAttribDivisor(yOffsetLoc, 1); |
| glVertexAttribPointer(yOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glEnableVertexAttribArray(yOffsetLoc); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true, true, 4); |
| ASSERT_GL_NO_ERROR(); |
| |
| const GLubyte expectedRedChannel[4][8] = {{255, 0, 0, 0, 0, 0, 255, 0}, |
| {255, 0, 0, 0, 0, 0, 255, 0}, |
| {255, 0, 0, 0, 0, 0, 255, 0}, |
| {0, 255, 0, 0, 0, 0, 0, 255}}; |
| for (int row = 0; row < 4; ++row) |
| { |
| for (int col = 0; col < 8; ++col) |
| { |
| EXPECT_PIXEL_EQ(col, row, expectedRedChannel[row][col], 0, 0, 0); |
| } |
| } |
| } |
| |
| // Test that different sequences of vertexAttribDivisor, useProgram and bindVertexArray in a |
| // multi-view context propagate the correct divisor to the driver. |
| TEST_P(MultiviewSideBySideRenderTest, DivisorOrderOfOperation) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| createFBO(2, 1, 2); |
| |
| // Create multiview program. |
| const std::string &vs = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "layout(num_views = 2) in;\n" |
| "layout(location = 0) in vec2 vPosition;\n" |
| "layout(location = 1) in float offsetX;\n" |
| "void main()\n" |
| "{\n" |
| " vec4 p = vec4(vPosition, 0.0, 1.0);\n" |
| " p.x += offsetX;\n" |
| " gl_Position = p;\n" |
| "}\n"; |
| |
| const std::string &fs = |
| "#version 300 es\n" |
| "#extension GL_OVR_multiview2 : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(1,0,0,0);\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(program, vs, fs); |
| |
| const std::string &dummyVS = |
| "#version 300 es\n" |
| "layout(location = 0) in vec2 vPosition;\n" |
| "layout(location = 1) in float offsetX;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(vPosition, 0.0, 1.0);\n" |
| "}\n"; |
| |
| const std::string &dummyFS = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,0,0,0);\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(dummyProgram, dummyVS, dummyFS); |
| |
| GLBuffer xOffsetVBO; |
| glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO); |
| const GLfloat xOffsetData[12] = {0.0f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f, |
| 4.0f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 12, xOffsetData, GL_STATIC_DRAW); |
| |
| GLBuffer vertexVBO; |
| glBindBuffer(GL_ARRAY_BUFFER, vertexVBO); |
| Vector2 kQuadVertices[6] = {Vector2(-1.f, -1.f), Vector2(1.f, -1.f), Vector2(1.f, 1.f), |
| Vector2(-1.f, -1.f), Vector2(1.f, 1.f), Vector2(-1.f, 1.f)}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW); |
| |
| GLVertexArray vao[2]; |
| for (size_t i = 0u; i < 2u; ++i) |
| { |
| glBindVertexArray(vao[i]); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, vertexVBO); |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); |
| glEnableVertexAttribArray(0); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO); |
| glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glEnableVertexAttribArray(1); |
| } |
| ASSERT_GL_NO_ERROR(); |
| |
| glViewport(0, 0, 1, 1); |
| glScissor(0, 0, 1, 1); |
| glClearColor(0, 0, 0, 0); |
| |
| // Clear the buffers, propagate divisor to the driver, bind the vao and keep it active. |
| // It is necessary to call draw, so that the divisor is propagated and to guarantee that dirty |
| // bits are cleared. |
| glUseProgram(dummyProgram); |
| glBindVertexArray(vao[0]); |
| glVertexAttribDivisor(1, 0); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that vertexAttribDivisor uses the number of views to update the divisor. |
| glUseProgram(program); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 0); |
| EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0); |
| |
| // Clear the buffers and propagate divisor to the driver. |
| // We keep the vao active and propagate the divisor to guarantee that there are no unresolved |
| // dirty bits when useProgram is called. |
| glUseProgram(dummyProgram); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that useProgram uses the number of views to update the divisor. |
| glUseProgram(program); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 0); |
| EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0); |
| |
| // We go through similar steps as before. |
| glUseProgram(dummyProgram); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that bindVertexArray uses the number of views to update the divisor. |
| { |
| // Call useProgram with vao[1] being active to guarantee that useProgram will adjust the |
| // divisor for vao[1] only. |
| glBindVertexArray(vao[1]); |
| glUseProgram(program); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glBindVertexArray(0); |
| ASSERT_GL_NO_ERROR(); |
| } |
| // Bind vao[0] after useProgram is called to ensure that bindVertexArray is the call which |
| // adjusts the divisor. |
| glBindVertexArray(vao[0]); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 0); |
| EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0); |
| } |
| |
| ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest, ES31_OPENGL()); |
| ANGLE_INSTANTIATE_TEST(MultiviewSideBySideRenderDualViewTest, ES3_OPENGL()); |
| ANGLE_INSTANTIATE_TEST(MultiviewSideBySideRenderTest, ES3_OPENGL()); |