blob: 970c8e51124737d03135137e08dbbe37a28d09bc [file] [log] [blame]
//
// 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 &params)
{
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 &params, 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));