| // |
| // 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 "platform/FeaturesD3D.h" |
| #include "test_utils/MultiviewTest.h" |
| #include "test_utils/gl_raii.h" |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| std::vector<Vector2> ConvertPixelCoordinatesToClipSpace(const std::vector<Vector2I> &pixels, |
| int width, |
| int height) |
| { |
| std::vector<Vector2> result(pixels.size()); |
| for (size_t i = 0; i < pixels.size(); ++i) |
| { |
| const auto &pixel = pixels[i]; |
| float pixelCenterRelativeX = (static_cast<float>(pixel.x()) + .5f) / width; |
| float pixelCenterRelativeY = (static_cast<float>(pixel.y()) + .5f) / height; |
| float xInClipSpace = 2.f * pixelCenterRelativeX - 1.f; |
| float yInClipSpace = 2.f * pixelCenterRelativeY - 1.f; |
| result[i] = Vector2(xInClipSpace, yInClipSpace); |
| } |
| return result; |
| } |
| } // namespace |
| |
| struct MultiviewRenderTestParams final : public MultiviewImplementationParams |
| { |
| MultiviewRenderTestParams(int samples, |
| const MultiviewImplementationParams &implementationParams) |
| : MultiviewImplementationParams(implementationParams), mSamples(samples) |
| {} |
| int mSamples; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const MultiviewRenderTestParams ¶ms) |
| { |
| const MultiviewImplementationParams &base = |
| static_cast<const MultiviewImplementationParams &>(params); |
| os << base; |
| os << "_layered"; |
| |
| if (params.mSamples > 0) |
| { |
| os << "_samples_" << params.mSamples; |
| } |
| return os; |
| } |
| |
| class MultiviewFramebufferTestBase : public MultiviewTestBase, |
| public ::testing::TestWithParam<MultiviewRenderTestParams> |
| { |
| protected: |
| MultiviewFramebufferTestBase(const PlatformParameters ¶ms, int samples) |
| : MultiviewTestBase(params), |
| mViewWidth(0), |
| mViewHeight(0), |
| mNumViews(0), |
| mColorTexture(0u), |
| mDepthTexture(0u), |
| mDrawFramebuffer(0u), |
| mSamples(samples), |
| mResolveTexture(0u) |
| {} |
| |
| void FramebufferTestSetUp() { MultiviewTestBase::MultiviewTestBaseSetUp(); } |
| |
| void FramebufferTestTearDown() |
| { |
| freeFBOs(); |
| MultiviewTestBase::MultiviewTestBaseTearDown(); |
| } |
| |
| void updateFBOs(int viewWidth, int height, int numViews, int numLayers, int baseViewIndex) |
| { |
| ASSERT_TRUE(numViews + baseViewIndex <= numLayers); |
| |
| freeFBOs(); |
| |
| mViewWidth = viewWidth; |
| mViewHeight = height; |
| mNumViews = numViews; |
| |
| glGenTextures(1, &mColorTexture); |
| glGenTextures(1, &mDepthTexture); |
| |
| CreateMultiviewBackingTextures(mSamples, viewWidth, height, numLayers, mColorTexture, |
| mDepthTexture, 0u); |
| |
| glGenFramebuffers(1, &mDrawFramebuffer); |
| |
| // Create draw framebuffer to be used for multiview rendering. |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer); |
| AttachMultiviewTextures(GL_DRAW_FRAMEBUFFER, viewWidth, numViews, baseViewIndex, |
| mColorTexture, mDepthTexture, 0u); |
| |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)); |
| |
| // Create read framebuffer to be used to retrieve the pixel information for testing |
| // purposes. |
| mReadFramebuffer.resize(numLayers); |
| glGenFramebuffers(static_cast<GLsizei>(mReadFramebuffer.size()), mReadFramebuffer.data()); |
| for (int i = 0; i < numLayers; ++i) |
| { |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[i]); |
| glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mColorTexture, 0, |
| i); |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, |
| glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)); |
| } |
| |
| // Clear the buffers. |
| glViewport(0, 0, viewWidth, height); |
| } |
| |
| void updateFBOs(int viewWidth, int height, int numViews) |
| { |
| updateFBOs(viewWidth, height, numViews, numViews, 0); |
| } |
| |
| void bindMemberDrawFramebuffer() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer); } |
| |
| // In case we have a multisampled framebuffer, creates and binds a resolve framebuffer as the |
| // draw framebuffer, and resolves the read framebuffer to it. |
| void resolveMultisampledFBO() |
| { |
| if (mSamples == 0) |
| { |
| return; |
| } |
| int numLayers = mReadFramebuffer.size(); |
| if (mResolveFramebuffer.empty()) |
| { |
| ASSERT_TRUE(mResolveTexture == 0u); |
| glGenTextures(1, &mResolveTexture); |
| CreateMultiviewBackingTextures(0, mViewWidth, mViewHeight, numLayers, mResolveTexture, |
| 0u, 0u); |
| |
| mResolveFramebuffer.resize(numLayers); |
| glGenFramebuffers(static_cast<GLsizei>(mResolveFramebuffer.size()), |
| mResolveFramebuffer.data()); |
| for (int i = 0; i < numLayers; ++i) |
| { |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mResolveFramebuffer[i]); |
| glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| mResolveTexture, 0, i); |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, |
| glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)); |
| } |
| } |
| for (int i = 0; i < numLayers; ++i) |
| { |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[i]); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mResolveFramebuffer[i]); |
| glBlitFramebuffer(0, 0, mViewWidth, mViewHeight, 0, 0, mViewWidth, mViewHeight, |
| GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| } |
| } |
| |
| GLColor GetViewColor(int x, int y, int view) |
| { |
| EXPECT_TRUE(static_cast<size_t>(view) < mReadFramebuffer.size()); |
| if (mSamples > 0) |
| { |
| EXPECT_TRUE(static_cast<size_t>(view) < mResolveFramebuffer.size()); |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, mResolveFramebuffer[view]); |
| } |
| else |
| { |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[view]); |
| } |
| return ReadColor(x, y); |
| } |
| |
| // Requests the OVR_multiview(2) extension and returns true if the operation succeeds. |
| bool requestMultiviewExtension(bool requireMultiviewMultisample) |
| { |
| if (!EnsureGLExtensionEnabled(extensionName())) |
| { |
| std::cout << "Test skipped due to missing " << extensionName() << "." << std::endl; |
| return false; |
| } |
| |
| if (requireMultiviewMultisample) |
| { |
| if (!EnsureGLExtensionEnabled("GL_OES_texture_storage_multisample_2d_array")) |
| { |
| std::cout << "Test skipped due to missing GL_ANGLE_multiview_multisample." |
| << std::endl; |
| return false; |
| } |
| |
| if (!EnsureGLExtensionEnabled("GL_ANGLE_multiview_multisample")) |
| { |
| std::cout << "Test skipped due to missing GL_ANGLE_multiview_multisample." |
| << std::endl; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool requestMultiviewExtension() { return requestMultiviewExtension(false); } |
| std::string extensionName() |
| { |
| switch (GetParam().mMultiviewExtension) |
| { |
| case multiview: |
| return "GL_OVR_multiview"; |
| case multiview2: |
| return "GL_OVR_multiview2"; |
| default: |
| // Ignore unknown. |
| return ""; |
| } |
| } |
| |
| bool isMultisampled() { return mSamples > 0; } |
| |
| int mViewWidth; |
| int mViewHeight; |
| int mNumViews; |
| |
| GLuint mColorTexture; |
| GLuint mDepthTexture; |
| |
| private: |
| GLuint mDrawFramebuffer; |
| std::vector<GLuint> mReadFramebuffer; |
| int mSamples; |
| |
| // For reading back multisampled framebuffer. |
| std::vector<GLuint> mResolveFramebuffer; |
| GLuint mResolveTexture; |
| |
| void freeFBOs() |
| { |
| if (mDrawFramebuffer) |
| { |
| glDeleteFramebuffers(1, &mDrawFramebuffer); |
| mDrawFramebuffer = 0; |
| } |
| if (!mReadFramebuffer.empty()) |
| { |
| GLsizei framebufferCount = static_cast<GLsizei>(mReadFramebuffer.size()); |
| glDeleteFramebuffers(framebufferCount, mReadFramebuffer.data()); |
| mReadFramebuffer.clear(); |
| } |
| if (!mResolveFramebuffer.empty()) |
| { |
| GLsizei framebufferCount = static_cast<GLsizei>(mResolveFramebuffer.size()); |
| glDeleteFramebuffers(framebufferCount, mResolveFramebuffer.data()); |
| mResolveFramebuffer.clear(); |
| } |
| if (mDepthTexture) |
| { |
| glDeleteTextures(1, &mDepthTexture); |
| mDepthTexture = 0; |
| } |
| if (mColorTexture) |
| { |
| glDeleteTextures(1, &mColorTexture); |
| mColorTexture = 0; |
| } |
| if (mResolveTexture) |
| { |
| glDeleteTextures(1, &mResolveTexture); |
| mResolveTexture = 0; |
| } |
| } |
| }; |
| |
| class MultiviewRenderTest : public MultiviewFramebufferTestBase |
| { |
| protected: |
| MultiviewRenderTest() : MultiviewFramebufferTestBase(GetParam(), GetParam().mSamples) {} |
| |
| void overrideWorkaroundsD3D(FeaturesD3D *features) override |
| { |
| features->overrideFeatures({"select_view_in_geometry_shader"}, |
| GetParam().mForceUseGeometryShaderOnD3D); |
| } |
| |
| virtual void testSetUp() {} |
| virtual void testTearDown() {} |
| |
| private: |
| void SetUp() override |
| { |
| MultiviewFramebufferTestBase::FramebufferTestSetUp(); |
| testSetUp(); |
| } |
| void TearDown() override |
| { |
| testTearDown(); |
| MultiviewFramebufferTestBase::FramebufferTestTearDown(); |
| } |
| }; |
| |
| std::string DualViewVS(ExtensionName multiviewExtension) |
| { |
| std::string ext; |
| switch (multiviewExtension) |
| { |
| case multiview: |
| ext = "GL_OVR_multiview"; |
| break; |
| case multiview2: |
| ext = "GL_OVR_multiview2"; |
| break; |
| } |
| |
| std::string dualViewVSSource = |
| "#version 300 es\n" |
| "#extension " + |
| ext + |
| " : 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 - " |
| "0.5);\n" |
| " gl_Position.yzw = vPosition.yzw;\n" |
| "}\n"; |
| return dualViewVSSource; |
| } |
| |
| std::string DualViewFS(ExtensionName multiviewExtension) |
| { |
| std::string ext; |
| switch (multiviewExtension) |
| { |
| case multiview: |
| ext = "GL_OVR_multiview"; |
| break; |
| case multiview2: |
| ext = "GL_OVR_multiview2"; |
| break; |
| } |
| |
| std::string dualViewFSSource = |
| "#version 300 es\n" |
| "#extension " + |
| ext + |
| " : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| return dualViewFSSource; |
| } |
| |
| class MultiviewRenderDualViewTest : public MultiviewRenderTest |
| { |
| protected: |
| MultiviewRenderDualViewTest() : mProgram(0u) {} |
| |
| void testSetUp() override |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| updateFBOs(2, 1, 2); |
| mProgram = CompileProgram(DualViewVS(GetParam().mMultiviewExtension).c_str(), |
| DualViewFS(GetParam().mMultiviewExtension).c_str()); |
| ASSERT_NE(mProgram, 0u); |
| glUseProgram(mProgram); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void testTearDown() override |
| { |
| if (mProgram != 0u) |
| { |
| glDeleteProgram(mProgram); |
| mProgram = 0u; |
| } |
| } |
| |
| void checkOutput() |
| { |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(1, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(1, 0, 1)); |
| } |
| |
| GLuint mProgram; |
| }; |
| |
| // Base class for tests that care mostly about draw call validity and not rendering results. |
| class MultiviewDrawValidationTest : public MultiviewTest |
| { |
| protected: |
| MultiviewDrawValidationTest() : MultiviewTest() {} |
| |
| void initOnePixelColorTexture2DSingleLayered(GLuint texId) |
| { |
| glBindTexture(GL_TEXTURE_2D_ARRAY, texId); |
| glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| nullptr); |
| } |
| |
| void initOnePixelColorTexture2DMultiLayered(GLuint texId) |
| { |
| glBindTexture(GL_TEXTURE_2D_ARRAY, texId); |
| glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| nullptr); |
| } |
| |
| // This initializes a simple VAO with a valid vertex buffer and index buffer with three |
| // vertices. |
| void initVAO(GLuint vao, GLuint vertexBuffer, GLuint indexBuffer) |
| { |
| glBindVertexArray(vao); |
| |
| const float kVertexData[3] = {0.0f}; |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| 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, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 3, &kIndices[0], |
| GL_STATIC_DRAW); |
| ASSERT_GL_NO_ERROR(); |
| } |
| }; |
| |
| class MultiviewOcclusionQueryTest : public MultiviewRenderTest |
| { |
| protected: |
| MultiviewOcclusionQueryTest() {} |
| |
| bool requestOcclusionQueryExtension() |
| { |
| if (!EnsureGLExtensionEnabled("GL_EXT_occlusion_query_boolean")) |
| { |
| std::cout << "Test skipped due to missing GL_EXT_occlusion_query_boolean." << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| GLuint drawAndRetrieveOcclusionQueryResult(GLuint program) |
| { |
| GLQueryEXT query; |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED, query); |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED); |
| |
| GLuint result = GL_TRUE; |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT, &result); |
| return result; |
| } |
| }; |
| |
| class MultiviewProgramGenerationTest : public MultiviewTest |
| { |
| protected: |
| MultiviewProgramGenerationTest() {} |
| }; |
| |
| class MultiviewRenderPrimitiveTest : public MultiviewRenderTest |
| { |
| protected: |
| MultiviewRenderPrimitiveTest() : mVBO(0u) {} |
| |
| void testSetUp() override { glGenBuffers(1, &mVBO); } |
| |
| void testTearDown() override |
| { |
| if (mVBO) |
| { |
| glDeleteBuffers(1, &mVBO); |
| mVBO = 0u; |
| } |
| } |
| |
| void setupGeometry(const std::vector<Vector2> &vertexData) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, mVBO); |
| glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(Vector2), vertexData.data(), |
| GL_STATIC_DRAW); |
| glEnableVertexAttribArray(0); |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); |
| } |
| |
| void checkGreenChannel(const GLubyte expectedGreenChannelData[]) |
| { |
| for (int view = 0; view < mNumViews; ++view) |
| { |
| for (int w = 0; w < mViewWidth; ++w) |
| { |
| for (int h = 0; h < mViewHeight; ++h) |
| { |
| size_t flatIndex = |
| static_cast<size_t>(view * mViewWidth * mViewHeight + mViewWidth * h + w); |
| EXPECT_EQ(GLColor(0, expectedGreenChannelData[flatIndex], 0, |
| expectedGreenChannelData[flatIndex]), |
| GetViewColor(w, h, view)); |
| } |
| } |
| } |
| } |
| GLuint mVBO; |
| }; |
| |
| class MultiviewLayeredRenderTest : public MultiviewFramebufferTestBase |
| { |
| protected: |
| MultiviewLayeredRenderTest() : MultiviewFramebufferTestBase(GetParam(), 0) {} |
| void SetUp() final { MultiviewFramebufferTestBase::FramebufferTestSetUp(); } |
| void TearDown() final { MultiviewFramebufferTestBase::FramebufferTestTearDown(); } |
| void overrideWorkaroundsD3D(FeaturesD3D *features) final |
| { |
| features->overrideFeatures({"select_view_in_geometry_shader"}, |
| GetParam().mForceUseGeometryShaderOnD3D); |
| } |
| }; |
| |
| // The test verifies that glDraw*Indirect works for any number of views. |
| TEST_P(MultiviewDrawValidationTest, IndirectDraw) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 color;\n" |
| "void main()\n" |
| "{color = vec4(1);}\n"; |
| |
| GLVertexArray vao; |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| initVAO(vao, vertexBuffer, indexBuffer); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| 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 that no errors are generated with the framebuffer having 2 views. |
| { |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| GLTexture tex2DArray; |
| initOnePixelColorTexture2DMultiLayered(tex2DArray); |
| |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2); |
| |
| glDrawArraysIndirect(GL_TRIANGLES, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Check that no errors are generated if the number of views is 1. |
| { |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 1) in;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| GLTexture tex2D; |
| initOnePixelColorTexture2DSingleLayered(tex2D); |
| |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1); |
| |
| 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. |
| TEST_P(MultiviewDrawValidationTest, NumViewsMismatch) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{}\n"; |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 color;\n" |
| "void main()\n" |
| "{color = vec4(1);}\n"; |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| GLVertexArray vao; |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| initVAO(vao, vertexBuffer, indexBuffer); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| // Check for a GL_INVALID_OPERATION error with the framebuffer and program having different |
| // number of views. |
| { |
| GLTexture tex2D; |
| initOnePixelColorTexture2DSingleLayered(tex2D); |
| |
| // The framebuffer has only 1 view. |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1); |
| |
| 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. |
| { |
| GLTexture tex2DArray; |
| initOnePixelColorTexture2DMultiLayered(tex2DArray); |
| |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2); |
| |
| 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* generates an INVALID_OPERATION error if the program does not use |
| // the multiview extension, but the active draw framebuffer has more than one view. |
| TEST_P(MultiviewDrawValidationTest, NumViewsMismatchForNonMultiviewProgram) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| constexpr char kVS[] = |
| "#version 300 es\n" |
| "void main()\n" |
| "{}\n"; |
| constexpr char kFS[] = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(programNoMultiview, kVS, kFS); |
| glUseProgram(programNoMultiview); |
| |
| GLVertexArray vao; |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| initVAO(vao, vertexBuffer, indexBuffer); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| GLTexture tex2DArray; |
| initOnePixelColorTexture2DMultiLayered(tex2DArray); |
| |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2); |
| |
| 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); |
| } |
| |
| // 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 not paused transform feedback object. |
| // 2) does not generate any error if the number of views in the draw framebuffer is 1. |
| TEST_P(MultiviewDrawValidationTest, ActiveTransformFeedback) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| |
| constexpr char kVS[] = R"(#version 300 es |
| out float tfVarying; |
| void main() |
| { |
| tfVarying = 1.0; |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| void main() |
| {})"; |
| |
| std::vector<std::string> tfVaryings; |
| tfVaryings.emplace_back("tfVarying"); |
| ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(singleViewProgram, kVS, kFS, tfVaryings, |
| GL_SEPARATE_ATTRIBS); |
| |
| std::vector<std::string> dualViewTFVaryings; |
| dualViewTFVaryings.emplace_back("gl_Position"); |
| ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(dualViewProgram, |
| DualViewVS(GetParam().mMultiviewExtension).c_str(), |
| DualViewFS(GetParam().mMultiviewExtension).c_str(), |
| dualViewTFVaryings, GL_SEPARATE_ATTRIBS); |
| |
| GLVertexArray vao; |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| initVAO(vao, vertexBuffer, indexBuffer); |
| |
| GLBuffer tbo; |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo); |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(float) * 16u, nullptr, GL_STATIC_DRAW); |
| |
| GLTransformFeedback transformFeedback; |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedback); |
| |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo); |
| |
| glUseProgram(dualViewProgram); |
| glBeginTransformFeedback(GL_TRIANGLES); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| GLTexture tex2DArray; |
| initOnePixelColorTexture2DMultiLayered(tex2DArray); |
| |
| GLenum bufs[] = {GL_NONE}; |
| glDrawBuffers(1, bufs); |
| |
| // 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. |
| { |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| glEndTransformFeedback(); |
| |
| // Ending transform feedback should allow the draw to succeed. |
| { |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // A paused transform feedback should not trigger an error. |
| glBeginTransformFeedback(GL_TRIANGLES); |
| glPauseTransformFeedback(); |
| ASSERT_GL_NO_ERROR(); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Unbind transform feedback - should succeed. |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Rebind paused transform feedback - should succeed. |
| glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedback); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_GL_NO_ERROR(); |
| |
| glResumeTransformFeedback(); |
| glEndTransformFeedback(); |
| |
| glUseProgram(singleViewProgram); |
| glBeginTransformFeedback(GL_TRIANGLES); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLTexture tex2D; |
| initOnePixelColorTexture2DSingleLayered(tex2D); |
| |
| // Check that drawArrays does not generate an error when the number of views in the draw |
| // framebuffer is 1. |
| { |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1); |
| 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) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_disjoint_timer_query")); |
| |
| ANGLE_GL_PROGRAM(dualViewProgram, DualViewVS(GetParam().mMultiviewExtension).c_str(), |
| DualViewFS(GetParam().mMultiviewExtension).c_str()); |
| |
| constexpr char kVS[] = |
| "#version 300 es\n" |
| "void main()\n" |
| "{}\n"; |
| constexpr char kFS[] = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{}\n"; |
| ANGLE_GL_PROGRAM(singleViewProgram, kVS, kFS); |
| glUseProgram(singleViewProgram); |
| |
| GLVertexArray vao; |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| initVAO(vao, vertexBuffer, indexBuffer); |
| |
| GLuint query = 0u; |
| glGenQueriesEXT(1, &query); |
| glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| |
| GLTexture tex2DArr; |
| initOnePixelColorTexture2DMultiLayered(tex2DArr); |
| |
| GLenum bufs[] = {GL_NONE}; |
| glDrawBuffers(1, bufs); |
| |
| // Check first case. |
| { |
| glUseProgram(dualViewProgram); |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArr, 0, 0, 2); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| GLTexture tex2D; |
| initOnePixelColorTexture2DSingleLayered(tex2D); |
| |
| // Check second case. |
| { |
| glUseProgram(singleViewProgram); |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_GL_NO_ERROR(); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| glEndQueryEXT(GL_TIME_ELAPSED_EXT); |
| glDeleteQueries(1, &query); |
| |
| // Check starting a query after a successful draw. |
| { |
| glUseProgram(dualViewProgram); |
| glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArr, 0, 0, 2); |
| glClear(GL_COLOR_BUFFER_BIT); |
| EXPECT_GL_NO_ERROR(); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| |
| glGenQueriesEXT(1, &query); |
| glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| glEndQueryEXT(GL_TIME_ELAPSED_EXT); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDeleteQueries(1, &query); |
| } |
| } |
| |
| // The test checks that glDrawArrays can be used to render into two views. |
| TEST_P(MultiviewRenderDualViewTest, DrawArrays) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| 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(MultiviewRenderDualViewTest, DrawElements) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| 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(MultiviewRenderDualViewTest, DrawRangeElements) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| 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(MultiviewRenderTest, DrawArraysFourViews) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| " : 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 FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| " : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| |
| updateFBOs(4, 1, 4); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| for (int i = 0; i < 4; ++i) |
| { |
| for (int j = 0; j < 4; ++j) |
| { |
| if (i == j) |
| { |
| EXPECT_EQ(GLColor::green, GetViewColor(j, 0, i)); |
| } |
| else |
| { |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(j, 0, i)); |
| } |
| } |
| } |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // The test checks that glDrawArraysInstanced can be used to render into two views. |
| TEST_P(MultiviewRenderTest, DrawArraysInstanced) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": 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 = p.y * 0.5 + 0.5;\n" |
| " } else {\n" |
| " p.y = p.y * 0.5 - 0.5;\n" |
| " }\n" |
| " gl_Position.x = (gl_ViewID_OVR == 0u ? p.x * 0.5 + 0.5 : p.x * 0.5 - 0.5);\n" |
| " gl_Position.yzw = p.yzw;\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| |
| const int kViewWidth = 2; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 2u); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| |
| const GLubyte expectedGreenChannel[kNumViews][kViewHeight][kViewWidth] = {{{0, 255}, {0, 255}}, |
| {{255, 0}, {255, 0}}}; |
| |
| for (int view = 0; view < 2; ++view) |
| { |
| for (int y = 0; y < 2; ++y) |
| { |
| for (int x = 0; x < 2; ++x) |
| { |
| EXPECT_EQ(GLColor(0, expectedGreenChannel[view][y][x], 0, |
| expectedGreenChannel[view][y][x]), |
| GetViewColor(x, y, view)); |
| } |
| } |
| } |
| } |
| |
| // 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(MultiviewRenderTest, AttribDivisor) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| // Looks like an incorrect D3D debug layer message is generated on Windows AMD and NVIDIA. |
| // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778 |
| if (IsWindows() && IsD3D11()) |
| { |
| ignoreD3D11SDKLayersWarnings(); |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| " : 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 - vec2(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 FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 4; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| 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); |
| |
| drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| |
| const GLubyte expectedGreenChannel[kNumViews][kViewHeight][kViewWidth] = { |
| {{255, 0, 0, 0}, {255, 0, 0, 0}, {255, 0, 0, 0}, {0, 255, 0, 0}}, |
| {{0, 0, 255, 0}, {0, 0, 255, 0}, {0, 0, 255, 0}, {0, 0, 0, 255}}}; |
| for (int view = 0; view < 2; ++view) |
| { |
| for (int row = 0; row < 4; ++row) |
| { |
| for (int col = 0; col < 4; ++col) |
| { |
| EXPECT_EQ(GLColor(0, expectedGreenChannel[view][row][col], 0, |
| expectedGreenChannel[view][row][col]), |
| GetViewColor(col, row, view)); |
| } |
| } |
| } |
| } |
| |
| // Test that different sequences of vertexAttribDivisor, useProgram and bindVertexArray in a |
| // multi-view context propagate the correct divisor to the driver. |
| TEST_P(MultiviewRenderTest, DivisorOrderOfOperation) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled())); |
| |
| updateFBOs(1, 1, 2); |
| |
| // Create multiview program. |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": 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 " + |
| extensionName() + |
| " : require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| constexpr char kDummyVS[] = |
| "#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"; |
| |
| constexpr char kDummyFS[] = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,0,0,1);\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(dummyProgram, kDummyVS, kDummyFS); |
| |
| 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); |
| glEnable(GL_SCISSOR_TEST); |
| glClearColor(0, 0, 0, 1); |
| |
| // 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); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glBindVertexArray(vao[0]); |
| glVertexAttribDivisor(1, 0); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that vertexAttribDivisor uses the number of views to update the divisor. |
| bindMemberDrawFramebuffer(); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glUseProgram(program); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| |
| // 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); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that useProgram uses the number of views to update the divisor. |
| bindMemberDrawFramebuffer(); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glUseProgram(program); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| |
| // We go through similar steps as before. |
| glUseProgram(dummyProgram); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glVertexAttribDivisor(1, 1); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glUseProgram(0); |
| 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. |
| bindMemberDrawFramebuffer(); |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glBindVertexArray(vao[1]); |
| glUseProgram(program); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| glBindVertexArray(0); |
| ASSERT_GL_NO_ERROR(); |
| } |
| // Bind vao[0] after useProgram is called to ensure that bindVertexArray is the call which |
| // adjusts the divisor. |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glBindVertexArray(vao[0]); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| } |
| |
| // Test that no fragments pass the occlusion query for a multi-view vertex shader which always |
| // transforms geometry to be outside of the clip region. |
| TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryNothingVisible) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension()); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position.x = 2.0;\n" |
| " gl_Position.yzw = vec3(vPosition.yz, 1.);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| " : 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.c_str(), FS.c_str()); |
| updateFBOs(1, 1, 2); |
| |
| GLuint result = drawAndRetrieveOcclusionQueryResult(program); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_GL_FALSE(result); |
| } |
| |
| // Test that there are fragments passing the occlusion query if only view 0 can produce |
| // output. |
| TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryOnlyLeftVisible) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension()); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position.x = gl_ViewID_OVR == 0u ? vPosition.x : 2.0;\n" |
| " gl_Position.yzw = vec3(vPosition.yz, 1.);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": 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.c_str(), FS.c_str()); |
| updateFBOs(1, 1, 2); |
| |
| GLuint result = drawAndRetrieveOcclusionQueryResult(program); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_GL_TRUE(result); |
| } |
| |
| // Test that there are fragments passing the occlusion query if only view 1 can produce |
| // output. |
| TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryOnlyRightVisible) |
| { |
| ANGLE_SKIP_TEST_IF(!requestMultiviewExtension()); |
| ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension()); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position.x = gl_ViewID_OVR == 1u ? vPosition.x : 2.0;\n" |
| " gl_Position.yzw = vec3(vPosition.yz, 1.);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": 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.c_str(), FS.c_str()); |
| updateFBOs(1, 1, 2); |
| |
| GLuint result = drawAndRetrieveOcclusionQueryResult(program); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_GL_TRUE(result); |
| } |
| |
| // Test that a simple multi-view program which doesn't use gl_ViewID_OVR in neither VS nor FS |
| // compiles and links without an error. |
| TEST_P(MultiviewProgramGenerationTest, SimpleProgram) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test that a simple multi-view program which uses gl_ViewID_OVR only in VS compiles and links |
| // without an error. |
| TEST_P(MultiviewProgramGenerationTest, UseViewIDInVertexShader) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{\n" |
| " if (gl_ViewID_OVR == 0u) {\n" |
| " gl_Position = vec4(1,0,0,1);\n" |
| " } else {\n" |
| " gl_Position = vec4(-1,0,0,1);\n" |
| " }\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test that a simple multi-view program which uses gl_ViewID_OVR only in FS compiles and links |
| // without an error. |
| TEST_P(MultiviewProgramGenerationTest, UseViewIDInFragmentShader) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " if (gl_ViewID_OVR == 0u) {\n" |
| " col = vec4(1,0,0,1);\n" |
| " } else {\n" |
| " col = vec4(-1,0,0,1);\n" |
| " }\n" |
| "}\n"; |
| |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // The test checks that GL_POINTS is correctly rendered. |
| TEST_P(MultiviewRenderPrimitiveTest, Points) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| // Test failing on P400 graphics card (anglebug.com/2228) |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11() && IsNVIDIA()); |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "layout(location=0) in vec2 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_PointSize = 1.0;\n" |
| " gl_Position = vec4(vPosition.xy, 0.0, 1.0);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 1)}; |
| std::vector<Vector2> vertexDataInClipSpace = |
| ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2); |
| setupGeometry(vertexDataInClipSpace); |
| |
| glDrawArrays(GL_POINTS, 0, 2); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{255, 0, 0, 0}, {0, 0, 0, 255}}, {{255, 0, 0, 0}, {0, 0, 0, 255}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| } |
| |
| // The test checks that GL_LINES is correctly rendered. |
| // The behavior of this test is not guaranteed by the spec: |
| // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization: |
| // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in |
| // either x or y window coordinates from a corresponding fragment produced by the diamond-exit |
| // rule." |
| TEST_P(MultiviewRenderPrimitiveTest, Lines) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(4, 0)}; |
| std::vector<Vector2> vertexDataInClipSpace = |
| ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2); |
| setupGeometry(vertexDataInClipSpace); |
| |
| glDrawArrays(GL_LINES, 0, 2); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{255, 255, 255, 255}, {0, 0, 0, 0}}, {{255, 255, 255, 255}, {0, 0, 0, 0}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| |
| glDeleteProgram(program); |
| } |
| |
| // The test checks that GL_LINE_STRIP is correctly rendered. |
| // The behavior of this test is not guaranteed by the spec: |
| // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization: |
| // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in |
| // either x or y window coordinates from a corresponding fragment produced by the diamond-exit |
| // rule." |
| TEST_P(MultiviewRenderPrimitiveTest, LineStrip) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 0), Vector2I(3, 2)}; |
| std::vector<Vector2> vertexDataInClipSpace = |
| ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2); |
| setupGeometry(vertexDataInClipSpace); |
| |
| glDrawArrays(GL_LINE_STRIP, 0, 3); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{255, 255, 255, 255}, {0, 0, 0, 255}}, {{255, 255, 255, 255}, {0, 0, 0, 255}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| |
| glDeleteProgram(program); |
| } |
| |
| // The test checks that GL_LINE_LOOP is correctly rendered. |
| // The behavior of this test is not guaranteed by the spec: |
| // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization: |
| // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in |
| // either x or y window coordinates from a corresponding fragment produced by the diamond-exit |
| // rule." |
| TEST_P(MultiviewRenderPrimitiveTest, LineLoop) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| // Only this subtest fails on intel-hd-630-ubuntu-stable. Driver bug? |
| // https://bugs.chromium.org/p/angleproject/issues/detail?id=3472 |
| ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL()); |
| |
| GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 4; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 0), Vector2I(3, 3), |
| Vector2I(0, 3)}; |
| std::vector<Vector2> vertexDataInClipSpace = |
| ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 4); |
| setupGeometry(vertexDataInClipSpace); |
| |
| glDrawArrays(GL_LINE_LOOP, 0, 4); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{255, 255, 255, 255}, {255, 0, 0, 255}, {255, 0, 0, 255}, {255, 255, 255, 255}}, |
| {{255, 255, 255, 255}, {255, 0, 0, 255}, {255, 0, 0, 255}, {255, 255, 255, 255}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| |
| glDeleteProgram(program); |
| } |
| |
| // The test checks that GL_TRIANGLE_STRIP is correctly rendered. |
| TEST_P(MultiviewRenderPrimitiveTest, TriangleStrip) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| std::vector<Vector2> vertexDataInClipSpace = {Vector2(1.0f, 0.0f), Vector2(0.0f, 0.0f), |
| Vector2(1.0f, 1.0f), Vector2(0.0f, 1.0f)}; |
| setupGeometry(vertexDataInClipSpace); |
| |
| const int kViewWidth = 2; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{0, 0}, {0, 255}}, {{0, 0}, {0, 255}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| |
| glDeleteProgram(program); |
| } |
| |
| // The test checks that GL_TRIANGLE_FAN is correctly rendered. |
| TEST_P(MultiviewRenderPrimitiveTest, TriangleFan) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| std::vector<Vector2> vertexDataInClipSpace = {Vector2(0.0f, 0.0f), Vector2(0.0f, 1.0f), |
| Vector2(1.0f, 1.0f), Vector2(1.0f, 0.0f)}; |
| setupGeometry(vertexDataInClipSpace); |
| |
| const int kViewWidth = 2; |
| const int kViewHeight = 2; |
| const int kNumViews = 2; |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
| |
| const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = { |
| {{0, 0}, {0, 255}}, {{0, 0}, {0, 255}}}; |
| checkGreenChannel(expectedGreenChannelData[0][0]); |
| |
| glDeleteProgram(program); |
| } |
| |
| // Verify that re-linking a program adjusts the attribute divisor. |
| // The test uses instacing to draw for each view a strips of two red quads and two blue quads next |
| // to each other. The quads' position and color depend on the corresponding attribute divisors. |
| TEST_P(MultiviewRenderTest, ProgramRelinkUpdatesAttribDivisor) |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| // Looks like an incorrect D3D debug layer message is generated on Windows AMD and NVIDIA. |
| // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778 |
| if (IsWindows() && IsD3D11()) |
| { |
| ignoreD3D11SDKLayersWarnings(); |
| } |
| |
| const int kViewWidth = 4; |
| const int kViewHeight = 1; |
| const int kNumViews = 2; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "in vec4 oColor;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = oColor;\n" |
| "}\n"; |
| |
| auto generateVertexShaderSource = [](int numViews, std::string extensionName) -> std::string { |
| std::string source = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName + |
| ": require\n" |
| "layout(num_views = " + |
| ToString(numViews) + |
| ") in;\n" |
| "in vec3 vPosition;\n" |
| "in float vOffsetX;\n" |
| "in vec4 vColor;\n" |
| "out vec4 oColor;\n" |
| "void main()\n" |
| "{\n" |
| " vec4 p = vec4(vPosition, 1.);\n" |
| " p.x = p.x * 0.25 - 0.75 + vOffsetX;\n" |
| " oColor = vColor;\n" |
| " gl_Position = p;\n" |
| "}\n"; |
| return source; |
| }; |
| |
| std::string vsSource = generateVertexShaderSource(kNumViews, extensionName()); |
| ANGLE_GL_PROGRAM(program, vsSource.c_str(), FS.c_str()); |
| glUseProgram(program); |
| |
| GLint positionLoc; |
| GLBuffer xOffsetVBO; |
| GLint xOffsetLoc; |
| GLBuffer colorVBO; |
| GLint colorLoc; |
| |
| { |
| // Initialize buffers and setup attributes. |
| glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO); |
| const GLfloat kXOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.5f}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, kXOffsetData, GL_STATIC_DRAW); |
| xOffsetLoc = glGetAttribLocation(program, "vOffsetX"); |
| glVertexAttribPointer(xOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glVertexAttribDivisor(xOffsetLoc, 1); |
| glEnableVertexAttribArray(xOffsetLoc); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, colorVBO); |
| const GLColor kColors[2] = {GLColor::red, GLColor::blue}; |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * 2, kColors, GL_STATIC_DRAW); |
| colorLoc = glGetAttribLocation(program, "vColor"); |
| glVertexAttribDivisor(colorLoc, 2); |
| glVertexAttribPointer(colorLoc, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, 0); |
| glEnableVertexAttribArray(colorLoc); |
| |
| positionLoc = glGetAttribLocation(program, "vPosition"); |
| } |
| |
| { |
| updateFBOs(kViewWidth, kViewHeight, kNumViews); |
| |
| drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::red, GetViewColor(1, 0, 0)); |
| EXPECT_EQ(GLColor::blue, GetViewColor(2, 0, 0)); |
| EXPECT_EQ(GLColor::blue, GetViewColor(3, 0, 0)); |
| } |
| |
| { |
| const int kNewNumViews = 3; |
| vsSource = generateVertexShaderSource(kNewNumViews, extensionName()); |
| updateFBOs(kViewWidth, kViewHeight, kNewNumViews); |
| |
| GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource.c_str()); |
| ASSERT_NE(0u, vs); |
| GLuint fs = CompileShader(GL_FRAGMENT_SHADER, FS.c_str()); |
| ASSERT_NE(0u, fs); |
| |
| GLint numAttachedShaders = 0; |
| glGetProgramiv(program, GL_ATTACHED_SHADERS, &numAttachedShaders); |
| |
| GLuint attachedShaders[2] = {0u}; |
| glGetAttachedShaders(program, numAttachedShaders, nullptr, attachedShaders); |
| for (int i = 0; i < 2; ++i) |
| { |
| glDetachShader(program, attachedShaders[i]); |
| } |
| |
| glAttachShader(program, vs); |
| glDeleteShader(vs); |
| |
| glAttachShader(program, fs); |
| glDeleteShader(fs); |
| |
| glBindAttribLocation(program, positionLoc, "vPosition"); |
| glBindAttribLocation(program, xOffsetLoc, "vOffsetX"); |
| glBindAttribLocation(program, colorLoc, "vColor"); |
| |
| glLinkProgram(program); |
| |
| drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| for (int i = 0; i < kNewNumViews; ++i) |
| { |
| EXPECT_EQ(GLColor::red, GetViewColor(0, 0, i)); |
| EXPECT_EQ(GLColor::red, GetViewColor(1, 0, i)); |
| EXPECT_EQ(GLColor::blue, GetViewColor(2, 0, i)); |
| EXPECT_EQ(GLColor::blue, GetViewColor(3, 0, i)); |
| } |
| } |
| } |
| |
| // Test that useProgram applies the number of views in computing the final value of the attribute |
| // divisor. |
| TEST_P(MultiviewRenderTest, DivisorUpdatedOnProgramChange) |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| // Test failing on P400 graphics card (anglebug.com/2228) |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11() && IsNVIDIA()); |
| |
| // Looks like an incorrect D3D debug layer message is generated on Windows / AMD. |
| // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778 |
| if (IsWindows() && IsD3D11()) |
| { |
| ignoreD3D11SDKLayersWarnings(); |
| } |
| |
| GLVertexArray vao; |
| glBindVertexArray(vao); |
| GLBuffer vbo; |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(1, 0), Vector2I(2, 0), |
| Vector2I(3, 0)}; |
| std::vector<Vector2> vertexDataInClipSpace = |
| ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 1); |
| // Fill with x positions so that the resulting clip space coordinate fails the clip test. |
| glBufferData(GL_ARRAY_BUFFER, sizeof(Vector2) * vertexDataInClipSpace.size(), |
| vertexDataInClipSpace.data(), GL_STATIC_DRAW); |
| glEnableVertexAttribArray(0); |
| glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, nullptr); |
| glVertexAttribDivisor(0, 1); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Create a program and fbo with N views and draw N instances of a point horizontally. |
| for (int numViews = 2; numViews <= 4; ++numViews) |
| { |
| updateFBOs(4, 1, numViews); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLuint program = CreateSimplePassthroughProgram(numViews, GetParam().mMultiviewExtension); |
| ASSERT_NE(program, 0u); |
| glUseProgram(program); |
| ASSERT_GL_NO_ERROR(); |
| |
| glDrawArraysInstanced(GL_POINTS, 0, 1, numViews); |
| |
| resolveMultisampledFBO(); |
| for (int view = 0; view < numViews; ++view) |
| { |
| for (int j = 0; j < numViews; ++j) |
| { |
| EXPECT_EQ(GLColor::green, GetViewColor(j, 0, view)); |
| } |
| for (int j = numViews; j < 4; ++j) |
| { |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(j, 0, view)); |
| } |
| } |
| |
| glDeleteProgram(program); |
| } |
| } |
| |
| // The test checks that gl_ViewID_OVR is correctly propagated to the fragment shader. |
| TEST_P(MultiviewRenderTest, SelectColorBasedOnViewIDOVR) |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 3) in;\n" |
| "in vec3 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(vPosition, 1.);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " if (gl_ViewID_OVR == 0u) {\n" |
| " col = vec4(1,0,0,1);\n" |
| " } else if (gl_ViewID_OVR == 1u) {\n" |
| " col = vec4(0,1,0,1);\n" |
| " } else if (gl_ViewID_OVR == 2u) {\n" |
| " col = vec4(0,0,1,1);\n" |
| " } else {\n" |
| " col = vec4(0,0,0,0);\n" |
| " }\n" |
| "}\n"; |
| |
| updateFBOs(1, 1, 3); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| EXPECT_EQ(GLColor::blue, GetViewColor(0, 0, 2)); |
| } |
| |
| // The test checks that the inactive layers of a 2D texture array are not written to by a |
| // multi-view program. |
| TEST_P(MultiviewLayeredRenderTest, RenderToSubrangeOfLayers) |
| { |
| if (!requestMultiviewExtension()) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(vPosition, 1.);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " col = vec4(0,1,0,1);\n" |
| "}\n"; |
| |
| updateFBOs(1, 1, 2, 4, 1); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 2)); |
| EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 3)); |
| } |
| |
| // The D3D11 renderer uses a GS whenever the varyings are flat interpolated which can cause |
| // potential bugs if the view is selected in the VS. The test contains a program in which the |
| // gl_InstanceID is passed as a flat varying to the fragment shader where it is used to discard the |
| // fragment if its value is negative. The gl_InstanceID should never be negative and that branch is |
| // never taken. One quad is drawn and the color is selected based on the ViewID - red for view 0 and |
| // green for view 1. |
| TEST_P(MultiviewRenderTest, FlatInterpolation) |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "flat out int oInstanceID;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(vPosition, 1.);\n" |
| " oInstanceID = gl_InstanceID;\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "flat in int oInstanceID;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " if (oInstanceID < 0) {\n" |
| " discard;\n" |
| " }\n" |
| " if (gl_ViewID_OVR == 0u) {\n" |
| " col = vec4(1,0,0,1);\n" |
| " } else {\n" |
| " col = vec4(0,1,0,1);\n" |
| " }\n" |
| "}\n"; |
| |
| updateFBOs(1, 1, 2); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| } |
| |
| // This test assigns gl_ViewID_OVR to a flat int varying and then sets the color based on that |
| // varying in the fragment shader. |
| TEST_P(MultiviewRenderTest, FlatInterpolation2) |
| { |
| if (!requestMultiviewExtension(isMultisampled())) |
| { |
| return; |
| } |
| |
| const std::string VS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "layout(num_views = 2) in;\n" |
| "in vec3 vPosition;\n" |
| "flat out int flatVarying;\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(vPosition, 1.);\n" |
| " flatVarying = int(gl_ViewID_OVR);\n" |
| "}\n"; |
| |
| const std::string FS = |
| "#version 300 es\n" |
| "#extension " + |
| extensionName() + |
| ": require\n" |
| "precision mediump float;\n" |
| "flat in int flatVarying;\n" |
| "out vec4 col;\n" |
| "void main()\n" |
| "{\n" |
| " if (flatVarying == 0) {\n" |
| " col = vec4(1,0,0,1);\n" |
| " } else {\n" |
| " col = vec4(0,1,0,1);\n" |
| " }\n" |
| "}\n"; |
| |
| updateFBOs(1, 1, 2); |
| ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str()); |
| |
| drawQuad(program, "vPosition", 0.0f, 1.0f, true); |
| ASSERT_GL_NO_ERROR(); |
| |
| resolveMultisampledFBO(); |
| EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0)); |
| EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1)); |
| } |
| |
| MultiviewRenderTestParams VertexShaderOpenGL(ExtensionName multiviewExtension) |
| { |
| return MultiviewRenderTestParams(0, VertexShaderOpenGL(3, 0, multiviewExtension)); |
| } |
| |
| MultiviewRenderTestParams GeomShaderD3D11(ExtensionName multiviewExtension) |
| { |
| return MultiviewRenderTestParams(0, GeomShaderD3D11(3, 0, multiviewExtension)); |
| } |
| |
| MultiviewRenderTestParams VertexShaderD3D11(ExtensionName multiviewExtension) |
| { |
| return MultiviewRenderTestParams(0, VertexShaderD3D11(3, 0, multiviewExtension)); |
| } |
| |
| MultiviewRenderTestParams MultisampledVertexShaderOpenGL(ExtensionName multiviewExtension) |
| { |
| return MultiviewRenderTestParams(2, VertexShaderOpenGL(3, 1, multiviewExtension)); |
| } |
| |
| MultiviewRenderTestParams MultisampledVertexShaderD3D11(ExtensionName multiviewExtension) |
| { |
| return MultiviewRenderTestParams(2, VertexShaderD3D11(3, 1, multiviewExtension)); |
| } |
| |
| ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest, |
| VertexShaderOpenGL(3, 1, ExtensionName::multiview), |
| VertexShaderD3D11(3, 1, ExtensionName::multiview), |
| VertexShaderOpenGL(3, 1, ExtensionName::multiview2), |
| VertexShaderD3D11(3, 1, ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewRenderDualViewTest, |
| VertexShaderOpenGL(ExtensionName::multiview), |
| MultisampledVertexShaderOpenGL(ExtensionName::multiview), |
| GeomShaderD3D11(ExtensionName::multiview), |
| VertexShaderD3D11(ExtensionName::multiview), |
| MultisampledVertexShaderD3D11(ExtensionName::multiview), |
| VertexShaderOpenGL(ExtensionName::multiview2), |
| MultisampledVertexShaderOpenGL(ExtensionName::multiview2), |
| GeomShaderD3D11(ExtensionName::multiview2), |
| VertexShaderD3D11(ExtensionName::multiview2), |
| MultisampledVertexShaderD3D11(ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewRenderTest, |
| VertexShaderOpenGL(ExtensionName::multiview), |
| MultisampledVertexShaderOpenGL(ExtensionName::multiview), |
| GeomShaderD3D11(ExtensionName::multiview), |
| VertexShaderD3D11(ExtensionName::multiview), |
| MultisampledVertexShaderD3D11(ExtensionName::multiview), |
| VertexShaderOpenGL(ExtensionName::multiview2), |
| MultisampledVertexShaderOpenGL(ExtensionName::multiview2), |
| GeomShaderD3D11(ExtensionName::multiview2), |
| VertexShaderD3D11(ExtensionName::multiview2), |
| MultisampledVertexShaderD3D11(ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewOcclusionQueryTest, |
| VertexShaderOpenGL(ExtensionName::multiview), |
| GeomShaderD3D11(ExtensionName::multiview), |
| VertexShaderD3D11(ExtensionName::multiview), |
| VertexShaderOpenGL(ExtensionName::multiview2), |
| GeomShaderD3D11(ExtensionName::multiview2), |
| VertexShaderD3D11(ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewProgramGenerationTest, |
| VertexShaderOpenGL(3, 0, ExtensionName::multiview), |
| GeomShaderD3D11(3, 0, ExtensionName::multiview), |
| VertexShaderD3D11(3, 0, ExtensionName::multiview), |
| VertexShaderOpenGL(3, 0, ExtensionName::multiview2), |
| GeomShaderD3D11(3, 0, ExtensionName::multiview2), |
| VertexShaderD3D11(3, 0, ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewRenderPrimitiveTest, |
| VertexShaderOpenGL(ExtensionName::multiview), |
| GeomShaderD3D11(ExtensionName::multiview), |
| VertexShaderD3D11(ExtensionName::multiview), |
| VertexShaderOpenGL(ExtensionName::multiview2), |
| GeomShaderD3D11(ExtensionName::multiview2), |
| VertexShaderD3D11(ExtensionName::multiview2)); |
| ANGLE_INSTANTIATE_TEST(MultiviewLayeredRenderTest, |
| VertexShaderOpenGL(ExtensionName::multiview), |
| GeomShaderD3D11(ExtensionName::multiview), |
| VertexShaderOpenGL(ExtensionName::multiview2), |
| GeomShaderD3D11(ExtensionName::multiview2)); |