| // | |
| // Copyright 2015 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| #include "anglebase/numerics/safe_conversions.h" | |
| #include "common/mathutil.h" | |
| #include "platform/FeaturesVk.h" | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/gl_raii.h" | |
| using namespace angle; | |
| namespace | |
| { | |
| GLsizei TypeStride(GLenum attribType) | |
| { | |
| switch (attribType) | |
| { | |
| case GL_UNSIGNED_BYTE: | |
| case GL_BYTE: | |
| return 1; | |
| case GL_UNSIGNED_SHORT: | |
| case GL_SHORT: | |
| case GL_HALF_FLOAT: | |
| case GL_HALF_FLOAT_OES: | |
| return 2; | |
| case GL_UNSIGNED_INT: | |
| case GL_INT: | |
| case GL_FLOAT: | |
| case GL_UNSIGNED_INT_10_10_10_2_OES: | |
| case GL_INT_10_10_10_2_OES: | |
| return 4; | |
| default: | |
| EXPECT_TRUE(false); | |
| return 0; | |
| } | |
| } | |
| template <typename T> | |
| GLfloat Normalize(T value) | |
| { | |
| static_assert(std::is_integral<T>::value, "Integer required."); | |
| if (std::is_signed<T>::value) | |
| { | |
| typedef typename std::make_unsigned<T>::type unsigned_type; | |
| return (2.0f * static_cast<GLfloat>(value) + 1.0f) / | |
| static_cast<GLfloat>(std::numeric_limits<unsigned_type>::max()); | |
| } | |
| else | |
| { | |
| return static_cast<GLfloat>(value) / static_cast<GLfloat>(std::numeric_limits<T>::max()); | |
| } | |
| } | |
| // Normalization for each channel of signed/unsigned 10_10_10_2 types | |
| template <typename T> | |
| GLfloat Normalize10(T value) | |
| { | |
| static_assert(std::is_integral<T>::value, "Integer required."); | |
| GLfloat floatOutput; | |
| if (std::is_signed<T>::value) | |
| { | |
| const uint32_t signMask = 0x200; // 1 set at the 9th bit | |
| const uint32_t negativeMask = 0xFFFFFC00; // All bits from 10 to 31 set to 1 | |
| if (value & signMask) | |
| { | |
| int negativeNumber = value | negativeMask; | |
| floatOutput = static_cast<GLfloat>(negativeNumber); | |
| } | |
| else | |
| { | |
| floatOutput = static_cast<GLfloat>(value); | |
| } | |
| const int32_t maxValue = 0x1FF; // 1 set in bits 0 through 8 | |
| const int32_t minValue = 0xFFFFFE01; // Inverse of maxValue | |
| // A 10-bit two's complement number has the possibility of being minValue - 1 but | |
| // OpenGL's normalization rules dictate that it should be clamped to minValue in | |
| // this case. | |
| if (floatOutput < minValue) | |
| floatOutput = minValue; | |
| const int32_t halfRange = (maxValue - minValue) >> 1; | |
| floatOutput = ((floatOutput - minValue) / halfRange) - 1.0f; | |
| } | |
| else | |
| { | |
| const GLfloat maxValue = 1023.0f; // 1 set in bits 0 through 9 | |
| floatOutput = static_cast<GLfloat>(value) / maxValue; | |
| } | |
| return floatOutput; | |
| } | |
| template <typename T> | |
| GLfloat Normalize2(T value) | |
| { | |
| static_assert(std::is_integral<T>::value, "Integer required."); | |
| if (std::is_signed<T>::value) | |
| { | |
| GLfloat outputValue = static_cast<float>(value) / 1.0f; | |
| outputValue = (outputValue >= -1.0f) ? (outputValue) : (-1.0f); | |
| return outputValue; | |
| } | |
| else | |
| { | |
| return static_cast<float>(value) / 3.0f; | |
| } | |
| } | |
| template <typename DestT, typename SrcT> | |
| DestT Pack1010102(std::array<SrcT, 4> input) | |
| { | |
| static_assert(std::is_integral<SrcT>::value, "Integer required."); | |
| static_assert(std::is_integral<DestT>::value, "Integer required."); | |
| static_assert(std::is_unsigned<SrcT>::value == std::is_unsigned<DestT>::value, | |
| "Signedness should be equal."); | |
| DestT rOut, gOut, bOut, aOut; | |
| rOut = static_cast<DestT>(input[0]); | |
| gOut = static_cast<DestT>(input[1]); | |
| bOut = static_cast<DestT>(input[2]); | |
| aOut = static_cast<DestT>(input[3]); | |
| if (std::is_unsigned<SrcT>::value) | |
| { | |
| return rOut << 22 | gOut << 12 | bOut << 2 | aOut; | |
| } | |
| else | |
| { | |
| // Need to apply bit mask to account for sign extension | |
| return (0xFFC00000u & rOut << 22) | (0x003FF000u & gOut << 12) | (0x00000FFCu & bOut << 2) | | |
| (0x00000003u & aOut); | |
| } | |
| } | |
| class VertexAttributeTest : public ANGLETest | |
| { | |
| protected: | |
| VertexAttributeTest() | |
| : mProgram(0), mTestAttrib(-1), mExpectedAttrib(-1), mBuffer(0), mQuadBuffer(0) | |
| { | |
| setWindowWidth(128); | |
| setWindowHeight(128); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| setConfigDepthBits(24); | |
| } | |
| enum class Source | |
| { | |
| BUFFER, | |
| IMMEDIATE, | |
| }; | |
| struct TestData final : private angle::NonCopyable | |
| { | |
| TestData(GLenum typeIn, | |
| GLboolean normalizedIn, | |
| Source sourceIn, | |
| const void *inputDataIn, | |
| const GLfloat *expectedDataIn) | |
| : type(typeIn), | |
| normalized(normalizedIn), | |
| bufferOffset(0), | |
| source(sourceIn), | |
| inputData(inputDataIn), | |
| expectedData(expectedDataIn) | |
| {} | |
| GLenum type; | |
| GLboolean normalized; | |
| size_t bufferOffset; | |
| Source source; | |
| const void *inputData; | |
| const GLfloat *expectedData; | |
| }; | |
| void setupTest(const TestData &test, GLint typeSize) | |
| { | |
| if (mProgram == 0) | |
| { | |
| initBasicProgram(); | |
| } | |
| if (test.source == Source::BUFFER) | |
| { | |
| GLsizei dataSize = kVertexCount * TypeStride(test.type); | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, dataSize, test.inputData, GL_STATIC_DRAW); | |
| glVertexAttribPointer(mTestAttrib, typeSize, test.type, test.normalized, 0, | |
| reinterpret_cast<void *>(test.bufferOffset)); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| } | |
| else | |
| { | |
| ASSERT_EQ(Source::IMMEDIATE, test.source); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(mTestAttrib, typeSize, test.type, test.normalized, 0, | |
| test.inputData); | |
| } | |
| glVertexAttribPointer(mExpectedAttrib, typeSize, GL_FLOAT, GL_FALSE, 0, test.expectedData); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| } | |
| void checkPixels() { checkRGBPixels(true); } | |
| void checkRGBPixels(bool checkAlpha) | |
| { | |
| GLint viewportSize[4]; | |
| glGetIntegerv(GL_VIEWPORT, viewportSize); | |
| GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2; | |
| GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2; | |
| // We need to offset our checks from triangle edges to ensure we don't fall on a single tri | |
| // Avoid making assumptions of drawQuad with four checks to check the four possible tri | |
| // regions | |
| if (checkAlpha) | |
| { | |
| EXPECT_PIXEL_EQ((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255); | |
| EXPECT_PIXEL_EQ((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255); | |
| EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255); | |
| EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255); | |
| } | |
| else | |
| { | |
| EXPECT_PIXEL_RGB_EQUAL((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255); | |
| EXPECT_PIXEL_RGB_EQUAL((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255); | |
| EXPECT_PIXEL_RGB_EQUAL(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255); | |
| EXPECT_PIXEL_RGB_EQUAL(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255); | |
| } | |
| } | |
| void checkPixelsUnEqual() | |
| { | |
| GLint viewportSize[4]; | |
| glGetIntegerv(GL_VIEWPORT, viewportSize); | |
| GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2; | |
| GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2; | |
| // We need to offset our checks from triangle edges to ensure we don't fall on a single tri | |
| // Avoid making assumptions of drawQuad with four checks to check the four possible tri | |
| // regions | |
| EXPECT_PIXEL_NE((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255); | |
| EXPECT_PIXEL_NE((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255); | |
| EXPECT_PIXEL_NE(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255); | |
| EXPECT_PIXEL_NE(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255); | |
| } | |
| void runTest(const TestData &test) { runTest(test, true); } | |
| void runTest(const TestData &test, bool checkPixelEqual) | |
| { | |
| // TODO(geofflang): Figure out why this is broken on AMD OpenGL | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| for (GLint i = 0; i < 4; i++) | |
| { | |
| GLint typeSize = i + 1; | |
| setupTest(test, typeSize); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glDisableVertexAttribArray(mTestAttrib); | |
| glDisableVertexAttribArray(mExpectedAttrib); | |
| if (checkPixelEqual) | |
| { | |
| if ((test.type == GL_HALF_FLOAT || test.type == GL_HALF_FLOAT_OES) && IsVulkan() && | |
| typeSize == 3) | |
| { // We need a special case for RGB16F format on a Vulkan backend due to the fact | |
| // that in such a usecase, we need to ignore the alpha channel. | |
| checkRGBPixels(false); | |
| } | |
| else | |
| { | |
| checkPixels(); | |
| } | |
| } | |
| else | |
| { | |
| checkPixelsUnEqual(); | |
| } | |
| } | |
| } | |
| void testSetUp() override | |
| { | |
| glClearColor(0, 0, 0, 0); | |
| glClearDepthf(0.0); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| glDisable(GL_DEPTH_TEST); | |
| glGenBuffers(1, &mBuffer); | |
| } | |
| void testTearDown() override | |
| { | |
| glDeleteProgram(mProgram); | |
| glDeleteBuffers(1, &mBuffer); | |
| glDeleteBuffers(1, &mQuadBuffer); | |
| } | |
| // Override a feature to force emulation of attribute formats. | |
| void overrideFeaturesVk(FeaturesVk *featuresVk) override | |
| { | |
| featuresVk->overrideFeatures({"force_fallback_format"}, true); | |
| } | |
| GLuint compileMultiAttribProgram(GLint attribCount) | |
| { | |
| std::stringstream shaderStream; | |
| shaderStream << "attribute mediump vec4 position;" << std::endl; | |
| for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex) | |
| { | |
| shaderStream << "attribute float a" << attribIndex << ";" << std::endl; | |
| } | |
| shaderStream << "varying mediump float color;" << std::endl | |
| << "void main() {" << std::endl | |
| << " gl_Position = position;" << std::endl | |
| << " color = 0.0;" << std::endl; | |
| for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex) | |
| { | |
| shaderStream << " color += a" << attribIndex << ";" << std::endl; | |
| } | |
| shaderStream << "}" << std::endl; | |
| constexpr char kFS[] = | |
| "varying mediump float color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_FragColor = vec4(color, 0.0, 0.0, 1.0);\n" | |
| "}\n"; | |
| return CompileProgram(shaderStream.str().c_str(), kFS); | |
| } | |
| void setupMultiAttribs(GLuint program, GLint attribCount, GLfloat value) | |
| { | |
| glUseProgram(program); | |
| for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex) | |
| { | |
| std::stringstream attribStream; | |
| attribStream << "a" << attribIndex; | |
| GLint location = glGetAttribLocation(program, attribStream.str().c_str()); | |
| ASSERT_NE(-1, location); | |
| glVertexAttrib1f(location, value); | |
| glDisableVertexAttribArray(location); | |
| } | |
| } | |
| void initBasicProgram() | |
| { | |
| constexpr char kVS[] = | |
| "attribute mediump vec4 position;\n" | |
| "attribute mediump vec4 test;\n" | |
| "attribute mediump vec4 expected;\n" | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_Position = position;\n" | |
| " vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0);\n" | |
| " color = vec4(lessThanEqual(abs(test - expected), threshold));\n" | |
| "}\n"; | |
| constexpr char kFS[] = | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_FragColor = color;\n" | |
| "}\n"; | |
| mProgram = CompileProgram(kVS, kFS); | |
| ASSERT_NE(0u, mProgram); | |
| mTestAttrib = glGetAttribLocation(mProgram, "test"); | |
| ASSERT_NE(-1, mTestAttrib); | |
| mExpectedAttrib = glGetAttribLocation(mProgram, "expected"); | |
| ASSERT_NE(-1, mExpectedAttrib); | |
| glUseProgram(mProgram); | |
| } | |
| static constexpr size_t kVertexCount = 24; | |
| static void InitTestData(std::array<GLfloat, kVertexCount> &inputData, | |
| std::array<GLfloat, kVertexCount> &expectedData) | |
| { | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| inputData[count] = static_cast<GLfloat>(count); | |
| expectedData[count] = inputData[count]; | |
| } | |
| } | |
| GLuint mProgram; | |
| GLint mTestAttrib; | |
| GLint mExpectedAttrib; | |
| GLuint mBuffer; | |
| GLuint mQuadBuffer; | |
| }; | |
| TEST_P(VertexAttributeTest, UnsignedByteUnnormalized) | |
| { | |
| std::array<GLubyte, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = inputData[i]; | |
| } | |
| TestData data(GL_UNSIGNED_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, UnsignedByteNormalized) | |
| { | |
| std::array<GLubyte, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_UNSIGNED_BYTE, GL_TRUE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, ByteUnnormalized) | |
| { | |
| std::array<GLbyte, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 4, -1, -2, -3, -4, 125, 126, 127, -128, -127, -126}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = inputData[i]; | |
| } | |
| TestData data(GL_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, ByteNormalized) | |
| { | |
| std::array<GLbyte, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 4, -1, -2, -3, -4, 125, 126, 127, -128, -127, -126}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_BYTE, GL_TRUE, Source::IMMEDIATE, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, UnsignedShortUnnormalized) | |
| { | |
| std::array<GLushort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 254, 255, 256, 32766, 32767, 32768, 65533, 65534, 65535}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = inputData[i]; | |
| } | |
| TestData data(GL_UNSIGNED_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, UnsignedShortNormalized) | |
| { | |
| std::array<GLushort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 254, 255, 256, 32766, 32767, 32768, 65533, 65534, 65535}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_UNSIGNED_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, ShortUnnormalized) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = inputData[i]; | |
| } | |
| TestData data(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTest, ShortNormalized) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| // Verify that vertex data is updated correctly when using a float/half-float client memory pointer. | |
| TEST_P(VertexAttributeTest, HalfFloatClientMemoryPointer) | |
| { | |
| std::array<GLhalf, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData = { | |
| {0.f, 1.5f, 2.3f, 3.2f, -1.8f, -2.2f, -3.9f, -4.f, 34.5f, 32.2f, -78.8f, -77.4f, -76.1f}}; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| inputData[i] = gl::float32ToFloat16(expectedData[i]); | |
| } | |
| // If the extension is enabled run the test on all contexts | |
| if (IsGLExtensionEnabled("GL_OES_vertex_half_float")) | |
| { | |
| TestData imediateData(GL_HALF_FLOAT_OES, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(imediateData); | |
| } | |
| // Otherwise run the test only if it is an ES3 context | |
| else if (getClientMajorVersion() >= 3) | |
| { | |
| TestData imediateData(GL_HALF_FLOAT, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| runTest(imediateData); | |
| } | |
| } | |
| // Verify that vertex data is updated correctly when using a float/half-float buffer. | |
| TEST_P(VertexAttributeTest, HalfFloatBuffer) | |
| { | |
| std::array<GLhalf, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData = { | |
| {0.f, 1.5f, 2.3f, 3.2f, -1.8f, -2.2f, -3.9f, -4.f, 34.5f, 32.2f, -78.8f, -77.4f, -76.1f}}; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| inputData[i] = gl::float32ToFloat16(expectedData[i]); | |
| } | |
| // If the extension is enabled run the test on all contexts | |
| if (IsGLExtensionEnabled("GL_OES_vertex_half_float")) | |
| { | |
| TestData bufferData(GL_HALF_FLOAT_OES, GL_FALSE, Source::BUFFER, inputData.data(), | |
| expectedData.data()); | |
| runTest(bufferData); | |
| } | |
| // Otherwise run the test only if it is an ES3 context | |
| else if (getClientMajorVersion() >= 3) | |
| { | |
| TestData bufferData(GL_HALF_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(), | |
| expectedData.data()); | |
| runTest(bufferData); | |
| } | |
| } | |
| // Verify that using the same client memory pointer in different format won't mess up the draw. | |
| TEST_P(VertexAttributeTest, UsingDifferentFormatAndSameClientMemoryPointer) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> unnormalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| unnormalizedExpectedData[i] = inputData[i]; | |
| } | |
| TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedData); | |
| std::array<GLfloat, kVertexCount> normalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| inputData[i] = -inputData[i]; | |
| normalizedExpectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), | |
| normalizedExpectedData.data()); | |
| runTest(normalizedData); | |
| } | |
| // Verify that vertex format is updated correctly when the client memory pointer is same. | |
| TEST_P(VertexAttributeTest, NegativeUsingDifferentFormatAndSameClientMemoryPointer) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> unnormalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| unnormalizedExpectedData[i] = inputData[i]; | |
| } | |
| // Use unnormalized short as the format of the data in client memory pointer in the first draw. | |
| TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedData); | |
| // Use normalized short as the format of the data in client memory pointer in the second draw, | |
| // but mExpectedAttrib is the same as the first draw. | |
| TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(normalizedData, false); | |
| } | |
| // Verify that using different vertex format and same buffer won't mess up the draw. | |
| TEST_P(VertexAttributeTest, UsingDifferentFormatAndSameBuffer) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> unnormalizedExpectedData; | |
| std::array<GLfloat, kVertexCount> normalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| unnormalizedExpectedData[i] = inputData[i]; | |
| normalizedExpectedData[i] = Normalize(inputData[i]); | |
| } | |
| // Use unnormalized short as the format of the data in mBuffer in the first draw. | |
| TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedData); | |
| // Use normalized short as the format of the data in mBuffer in the second draw. | |
| TestData normalizedData(GL_SHORT, GL_TRUE, Source::BUFFER, inputData.data(), | |
| normalizedExpectedData.data()); | |
| runTest(normalizedData); | |
| } | |
| // Verify that vertex format is updated correctly when the buffer is same. | |
| TEST_P(VertexAttributeTest, NegativeUsingDifferentFormatAndSameBuffer) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> unnormalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| unnormalizedExpectedData[i] = inputData[i]; | |
| } | |
| // Use unnormalized short as the format of the data in mBuffer in the first draw. | |
| TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedData); | |
| // Use normalized short as the format of the data in mBuffer in the second draw, but | |
| // mExpectedAttrib is the same as the first draw. | |
| TestData normalizedData(GL_SHORT, GL_TRUE, Source::BUFFER, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| // The check should fail because the test data is changed while the expected data is the same. | |
| runTest(normalizedData, false); | |
| } | |
| // Verify that mixed using buffer and client memory pointer won't mess up the draw. | |
| TEST_P(VertexAttributeTest, MixedUsingBufferAndClientMemoryPointer) | |
| { | |
| std::array<GLshort, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}}; | |
| std::array<GLfloat, kVertexCount> unnormalizedExpectedData; | |
| std::array<GLfloat, kVertexCount> normalizedExpectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| unnormalizedExpectedData[i] = inputData[i]; | |
| normalizedExpectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedData); | |
| TestData unnormalizedBufferData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(), | |
| unnormalizedExpectedData.data()); | |
| runTest(unnormalizedBufferData); | |
| TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), | |
| normalizedExpectedData.data()); | |
| runTest(normalizedData); | |
| } | |
| // Verify signed unnormalized INT_10_10_10_2 vertex type | |
| TEST_P(VertexAttributeTest, SignedPacked1010102ExtensionUnnormalized) | |
| { | |
| std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); | |
| ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos)); | |
| // RGB channels are 10-bits, alpha is 2-bits | |
| std::array<std::array<GLshort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0}, | |
| {254, 255, 256, 1}, | |
| {256, 255, 254, -2}, | |
| {511, 510, 509, -1}, | |
| {-512, -511, -500, -2}, | |
| {-1, -2, -3, 1}}}; | |
| std::array<GLint, kVertexCount> packedInput; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize4; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize3; | |
| for (size_t i = 0; i < kVertexCount / 4; i++) | |
| { | |
| packedInput[i] = Pack1010102<GLint, GLshort>(unpackedInput[i]); | |
| expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] = unpackedInput[i][0]; | |
| expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] = unpackedInput[i][1]; | |
| expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] = unpackedInput[i][2]; | |
| // when the type size is 3, alpha will be 1.0f by GLES driver | |
| expectedTypeSize4[i * 4 + 3] = unpackedInput[i][3]; | |
| } | |
| TestData data4(GL_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(), | |
| expectedTypeSize4.data()); | |
| TestData bufferedData4(GL_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(), | |
| expectedTypeSize4.data()); | |
| TestData data3(GL_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(), | |
| expectedTypeSize3.data()); | |
| TestData bufferedData3(GL_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(), | |
| expectedTypeSize3.data()); | |
| std::array<std::pair<const TestData &, GLint>, 4> dataSet = { | |
| {{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}}; | |
| for (auto data : dataSet) | |
| { | |
| setupTest(data.first, data.second); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glDisableVertexAttribArray(mTestAttrib); | |
| glDisableVertexAttribArray(mExpectedAttrib); | |
| checkPixels(); | |
| } | |
| } | |
| // Verify signed normalized INT_10_10_10_2 vertex type | |
| TEST_P(VertexAttributeTest, SignedPacked1010102ExtensionNormalized) | |
| { | |
| std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); | |
| ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos)); | |
| // RGB channels are 10-bits, alpha is 2-bits | |
| std::array<std::array<GLshort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0}, | |
| {254, 255, 256, 1}, | |
| {256, 255, 254, -2}, | |
| {511, 510, 509, -1}, | |
| {-512, -511, -500, -2}, | |
| {-1, -2, -3, 1}}}; | |
| std::array<GLint, kVertexCount> packedInput; | |
| std::array<GLfloat, kVertexCount> expectedNormalizedTypeSize4; | |
| std::array<GLfloat, kVertexCount> expectedNormalizedTypeSize3; | |
| for (size_t i = 0; i < kVertexCount / 4; i++) | |
| { | |
| packedInput[i] = Pack1010102<GLint, GLshort>(unpackedInput[i]); | |
| expectedNormalizedTypeSize3[i * 3 + 0] = expectedNormalizedTypeSize4[i * 4 + 0] = | |
| Normalize10<GLshort>(unpackedInput[i][0]); | |
| expectedNormalizedTypeSize3[i * 3 + 1] = expectedNormalizedTypeSize4[i * 4 + 1] = | |
| Normalize10<GLshort>(unpackedInput[i][1]); | |
| expectedNormalizedTypeSize3[i * 3 + 2] = expectedNormalizedTypeSize4[i * 4 + 2] = | |
| Normalize10<GLshort>(unpackedInput[i][2]); | |
| // when the type size is 3, alpha will be 1.0f by GLES driver | |
| expectedNormalizedTypeSize4[i * 4 + 3] = Normalize2<GLshort>(unpackedInput[i][3]); | |
| } | |
| TestData data4(GL_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(), | |
| expectedNormalizedTypeSize4.data()); | |
| TestData bufferedData4(GL_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, packedInput.data(), | |
| expectedNormalizedTypeSize4.data()); | |
| TestData data3(GL_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(), | |
| expectedNormalizedTypeSize3.data()); | |
| TestData bufferedData3(GL_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, packedInput.data(), | |
| expectedNormalizedTypeSize3.data()); | |
| std::array<std::pair<const TestData &, GLint>, 4> dataSet = { | |
| {{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}}; | |
| for (auto data : dataSet) | |
| { | |
| setupTest(data.first, data.second); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glDisableVertexAttribArray(mTestAttrib); | |
| glDisableVertexAttribArray(mExpectedAttrib); | |
| checkPixels(); | |
| } | |
| } | |
| // Verify unsigned unnormalized INT_10_10_10_2 vertex type | |
| TEST_P(VertexAttributeTest, UnsignedPacked1010102ExtensionUnnormalized) | |
| { | |
| std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); | |
| ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos)); | |
| // RGB channels are 10-bits, alpha is 2-bits | |
| std::array<std::array<GLushort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0}, | |
| {511, 512, 513, 1}, | |
| {1023, 1022, 1021, 3}, | |
| {513, 512, 511, 2}, | |
| {2, 1, 0, 3}, | |
| {1023, 1022, 1022, 0}}}; | |
| std::array<GLuint, kVertexCount> packedInput; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize3; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize4; | |
| for (size_t i = 0; i < kVertexCount / 4; i++) | |
| { | |
| packedInput[i] = Pack1010102<GLuint, GLushort>(unpackedInput[i]); | |
| expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] = unpackedInput[i][0]; | |
| expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] = unpackedInput[i][1]; | |
| expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] = unpackedInput[i][2]; | |
| // when the type size is 3, alpha will be 1.0f by GLES driver | |
| expectedTypeSize4[i * 4 + 3] = unpackedInput[i][3]; | |
| } | |
| TestData data4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(), | |
| expectedTypeSize4.data()); | |
| TestData bufferedData4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, | |
| packedInput.data(), expectedTypeSize4.data()); | |
| TestData data3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(), | |
| expectedTypeSize3.data()); | |
| TestData bufferedData3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, | |
| packedInput.data(), expectedTypeSize3.data()); | |
| std::array<std::pair<const TestData &, GLint>, 4> dataSet = { | |
| {{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}}; | |
| for (auto data : dataSet) | |
| { | |
| setupTest(data.first, data.second); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glDisableVertexAttribArray(mTestAttrib); | |
| glDisableVertexAttribArray(mExpectedAttrib); | |
| checkPixels(); | |
| } | |
| } | |
| // Verify unsigned normalized INT_10_10_10_2 vertex type | |
| TEST_P(VertexAttributeTest, UnsignedPacked1010102ExtensionNormalized) | |
| { | |
| std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); | |
| ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos)); | |
| // RGB channels are 10-bits, alpha is 2-bits | |
| std::array<std::array<GLushort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0}, | |
| {511, 512, 513, 1}, | |
| {1023, 1022, 1021, 3}, | |
| {513, 512, 511, 2}, | |
| {2, 1, 0, 3}, | |
| {1023, 1022, 1022, 0}}}; | |
| std::array<GLuint, kVertexCount> packedInput; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize4; | |
| std::array<GLfloat, kVertexCount> expectedTypeSize3; | |
| for (size_t i = 0; i < kVertexCount / 4; i++) | |
| { | |
| packedInput[i] = Pack1010102<GLuint, GLushort>(unpackedInput[i]); | |
| expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] = | |
| Normalize10<GLushort>(unpackedInput[i][0]); | |
| expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] = | |
| Normalize10<GLushort>(unpackedInput[i][1]); | |
| expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] = | |
| Normalize10<GLushort>(unpackedInput[i][2]); | |
| // when the type size is 3, alpha will be 1.0f by GLES driver | |
| expectedTypeSize4[i * 4 + 3] = Normalize2<GLushort>(unpackedInput[i][3]); | |
| } | |
| TestData data4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(), | |
| expectedTypeSize4.data()); | |
| TestData bufferedData4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, | |
| packedInput.data(), expectedTypeSize4.data()); | |
| TestData data3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(), | |
| expectedTypeSize3.data()); | |
| TestData bufferedData3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, | |
| packedInput.data(), expectedTypeSize3.data()); | |
| std::array<std::pair<const TestData &, GLint>, 4> dataSet = { | |
| {{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}}; | |
| for (auto data : dataSet) | |
| { | |
| setupTest(data.first, data.second); | |
| drawQuad(mProgram, "position", 0.5f); | |
| glDisableVertexAttribArray(mTestAttrib); | |
| glDisableVertexAttribArray(mExpectedAttrib); | |
| checkPixels(); | |
| }; | |
| } | |
| class VertexAttributeTestES3 : public VertexAttributeTest | |
| { | |
| protected: | |
| VertexAttributeTestES3() {} | |
| }; | |
| TEST_P(VertexAttributeTestES3, IntUnnormalized) | |
| { | |
| GLint lo = std::numeric_limits<GLint>::min(); | |
| GLint hi = std::numeric_limits<GLint>::max(); | |
| std::array<GLint, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, -1, hi, hi - 1, lo, lo + 1}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = static_cast<GLfloat>(inputData[i]); | |
| } | |
| TestData data(GL_INT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTestES3, IntNormalized) | |
| { | |
| GLint lo = std::numeric_limits<GLint>::min(); | |
| GLint hi = std::numeric_limits<GLint>::max(); | |
| std::array<GLint, kVertexCount> inputData = { | |
| {0, 1, 2, 3, -1, -2, -3, -4, -1, hi, hi - 1, lo, lo + 1}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTestES3, UnsignedIntUnnormalized) | |
| { | |
| GLuint mid = std::numeric_limits<GLuint>::max() >> 1; | |
| GLuint hi = std::numeric_limits<GLuint>::max(); | |
| std::array<GLuint, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = static_cast<GLfloat>(inputData[i]); | |
| } | |
| TestData data(GL_UNSIGNED_INT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| TEST_P(VertexAttributeTestES3, UnsignedIntNormalized) | |
| { | |
| GLuint mid = std::numeric_limits<GLuint>::max() >> 1; | |
| GLuint hi = std::numeric_limits<GLuint>::max(); | |
| std::array<GLuint, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| TestData data(GL_UNSIGNED_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| runTest(data); | |
| } | |
| void SetupColorsForUnitQuad(GLint location, const GLColor32F &color, GLenum usage, GLBuffer *vbo) | |
| { | |
| glBindBuffer(GL_ARRAY_BUFFER, *vbo); | |
| std::vector<GLColor32F> vertices(6, color); | |
| glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLColor32F), vertices.data(), usage); | |
| glEnableVertexAttribArray(location); | |
| glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, 0); | |
| } | |
| // Tests that rendering works as expected with VAOs. | |
| TEST_P(VertexAttributeTestES3, VertexArrayObjectRendering) | |
| { | |
| constexpr char kVertexShader[] = | |
| "attribute vec4 a_position;\n" | |
| "attribute vec4 a_color;\n" | |
| "varying vec4 v_color;\n" | |
| "void main()\n" | |
| "{\n" | |
| " gl_Position = a_position;\n" | |
| " v_color = a_color;\n" | |
| "}"; | |
| constexpr char kFragmentShader[] = | |
| "precision mediump float;\n" | |
| "varying vec4 v_color;\n" | |
| "void main()\n" | |
| "{\n" | |
| " gl_FragColor = v_color;\n" | |
| "}"; | |
| ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader); | |
| GLint positionLoc = glGetAttribLocation(program, "a_position"); | |
| ASSERT_NE(-1, positionLoc); | |
| GLint colorLoc = glGetAttribLocation(program, "a_color"); | |
| ASSERT_NE(-1, colorLoc); | |
| GLVertexArray vaos[2]; | |
| GLBuffer positionBuffer; | |
| GLBuffer colorBuffers[2]; | |
| const auto &quadVertices = GetQuadVertices(); | |
| glBindVertexArray(vaos[0]); | |
| glBindBuffer(GL_ARRAY_BUFFER, positionBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(Vector3), quadVertices.data(), | |
| GL_STATIC_DRAW); | |
| glEnableVertexAttribArray(positionLoc); | |
| glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); | |
| SetupColorsForUnitQuad(colorLoc, kFloatRed, GL_STREAM_DRAW, &colorBuffers[0]); | |
| glBindVertexArray(vaos[1]); | |
| glBindBuffer(GL_ARRAY_BUFFER, positionBuffer); | |
| glEnableVertexAttribArray(positionLoc); | |
| glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); | |
| SetupColorsForUnitQuad(colorLoc, kFloatGreen, GL_STATIC_DRAW, &colorBuffers[1]); | |
| glUseProgram(program); | |
| ASSERT_GL_NO_ERROR(); | |
| for (int ii = 0; ii < 2; ++ii) | |
| { | |
| glBindVertexArray(vaos[0]); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); | |
| glBindVertexArray(vaos[1]); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| } | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Validate that we can support GL_MAX_ATTRIBS attribs | |
| TEST_P(VertexAttributeTest, MaxAttribs) | |
| { | |
| // TODO(jmadill): Figure out why we get this error on AMD/OpenGL. | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| // TODO: Support this test on Vulkan. http://anglebug.com/2797 | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel()); | |
| GLint maxAttribs; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs); | |
| ASSERT_GL_NO_ERROR(); | |
| // Reserve one attrib for position | |
| GLint drawAttribs = maxAttribs - 1; | |
| GLuint program = compileMultiAttribProgram(drawAttribs); | |
| ASSERT_NE(0u, program); | |
| setupMultiAttribs(program, drawAttribs, 0.5f / static_cast<float>(drawAttribs)); | |
| drawQuad(program, "position", 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 1); | |
| } | |
| // Validate that we cannot support GL_MAX_ATTRIBS+1 attribs | |
| TEST_P(VertexAttributeTest, MaxAttribsPlusOne) | |
| { | |
| // TODO(jmadill): Figure out why we get this error on AMD/ES2/OpenGL | |
| ANGLE_SKIP_TEST_IF(IsAMD() && GetParam() == ES2_OPENGL()); | |
| GLint maxAttribs; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs); | |
| ASSERT_GL_NO_ERROR(); | |
| // Exceed attrib count by one (counting position) | |
| GLint drawAttribs = maxAttribs; | |
| GLuint program = compileMultiAttribProgram(drawAttribs); | |
| ASSERT_EQ(0u, program); | |
| } | |
| // Simple test for when we use glBindAttribLocation | |
| TEST_P(VertexAttributeTest, SimpleBindAttribLocation) | |
| { | |
| // Re-use the multi-attrib program, binding attribute 0 | |
| GLuint program = compileMultiAttribProgram(1); | |
| glBindAttribLocation(program, 2, "position"); | |
| glBindAttribLocation(program, 3, "a0"); | |
| glLinkProgram(program); | |
| // Setup and draw the quad | |
| setupMultiAttribs(program, 1, 0.5f); | |
| drawQuad(program, "position", 0.5f); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 1); | |
| } | |
| class VertexAttributeOORTest : public VertexAttributeTest | |
| { | |
| public: | |
| VertexAttributeOORTest() | |
| { | |
| setWebGLCompatibilityEnabled(true); | |
| setRobustAccess(false); | |
| } | |
| }; | |
| // Verify that drawing with a large out-of-range offset generates INVALID_OPERATION. | |
| // Requires WebGL compatibility with robust access behaviour disabled. | |
| TEST_P(VertexAttributeOORTest, ANGLEDrawArraysBufferTooSmall) | |
| { | |
| // Test skipped due to supporting GL_KHR_robust_buffer_access_behavior | |
| ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior")); | |
| std::array<GLfloat, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| InitTestData(inputData, expectedData); | |
| TestData data(GL_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| data.bufferOffset = kVertexCount * TypeStride(GL_FLOAT); | |
| setupTest(data, 1); | |
| drawQuad(mProgram, "position", 0.5f); | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| } | |
| // Verify that index draw with an out-of-range offset generates INVALID_OPERATION. | |
| // Requires WebGL compatibility with robust access behaviour disabled. | |
| TEST_P(VertexAttributeOORTest, ANGLEDrawElementsBufferTooSmall) | |
| { | |
| // Test skipped due to supporting GL_KHR_robust_buffer_access_behavior | |
| ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior")); | |
| std::array<GLfloat, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| InitTestData(inputData, expectedData); | |
| TestData data(GL_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| data.bufferOffset = (kVertexCount - 3) * TypeStride(GL_FLOAT); | |
| setupTest(data, 1); | |
| drawIndexedQuad(mProgram, "position", 0.5f); | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| } | |
| // Verify that DrawArarys with an out-of-range offset generates INVALID_OPERATION. | |
| // Requires WebGL compatibility with robust access behaviour disabled. | |
| TEST_P(VertexAttributeOORTest, ANGLEDrawArraysOutOfBoundsCases) | |
| { | |
| // Test skipped due to supporting GL_KHR_robust_buffer_access_behavior | |
| ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior")); | |
| initBasicProgram(); | |
| GLfloat singleFloat = 1.0f; | |
| GLsizei dataSize = TypeStride(GL_FLOAT); | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, dataSize, &singleFloat, GL_STATIC_DRAW); | |
| glVertexAttribPointer(mTestAttrib, 2, GL_FLOAT, GL_FALSE, 8, 0); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| drawIndexedQuad(mProgram, "position", 0.5f); | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| } | |
| // Verify that using a different start vertex doesn't mess up the draw. | |
| TEST_P(VertexAttributeTest, DrawArraysWithBufferOffset) | |
| { | |
| // anglebug.com/4163 | |
| ANGLE_SKIP_TEST_IF(IsD3D11() && IsNVIDIA() && IsWindows7()); | |
| // TODO(jmadill): Diagnose this failure. | |
| ANGLE_SKIP_TEST_IF(IsD3D11_FL93()); | |
| // TODO(geofflang): Figure out why this is broken on AMD OpenGL | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| // TODO(cnorthrop): Test this again on more recent drivers. http://anglebug.com/3951 | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsNVIDIA() && IsVulkan()); | |
| initBasicProgram(); | |
| glUseProgram(mProgram); | |
| std::array<GLfloat, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| InitTestData(inputData, expectedData); | |
| auto quadVertices = GetQuadVertices(); | |
| GLsizei quadVerticesSize = static_cast<GLsizei>(quadVertices.size() * sizeof(quadVertices[0])); | |
| glGenBuffers(1, &mQuadBuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, quadVerticesSize + sizeof(Vector3), nullptr, GL_STATIC_DRAW); | |
| glBufferSubData(GL_ARRAY_BUFFER, 0, quadVerticesSize, quadVertices.data()); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| ASSERT_NE(-1, positionLocation); | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(positionLocation); | |
| GLsizei dataSize = kVertexCount * TypeStride(GL_FLOAT); | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, dataSize + TypeStride(GL_FLOAT), nullptr, GL_STATIC_DRAW); | |
| glBufferSubData(GL_ARRAY_BUFFER, 0, dataSize, inputData.data()); | |
| glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data()); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| // Vertex draw with no start vertex offset (second argument is zero). | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| // Draw offset by one vertex. | |
| glDrawArrays(GL_TRIANGLES, 1, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Verify that when we pass a client memory pointer to a disabled attribute the draw is still | |
| // correct. | |
| TEST_P(VertexAttributeTest, DrawArraysWithDisabledAttribute) | |
| { | |
| initBasicProgram(); | |
| std::array<GLfloat, kVertexCount> inputData; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| InitTestData(inputData, expectedData); | |
| auto quadVertices = GetQuadVertices(); | |
| GLsizei quadVerticesSize = static_cast<GLsizei>(quadVertices.size() * sizeof(quadVertices[0])); | |
| glGenBuffers(1, &mQuadBuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, quadVerticesSize, quadVertices.data(), GL_STATIC_DRAW); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| ASSERT_NE(-1, positionLocation); | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(positionLocation); | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(inputData), inputData.data(), GL_STATIC_DRAW); | |
| glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data()); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| // mProgram2 adds an attribute 'disabled' on the basis of mProgram. | |
| constexpr char testVertexShaderSource2[] = | |
| "attribute mediump vec4 position;\n" | |
| "attribute mediump vec4 test;\n" | |
| "attribute mediump vec4 expected;\n" | |
| "attribute mediump vec4 disabled;\n" | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_Position = position;\n" | |
| " vec4 threshold = max(abs(expected + disabled) * 0.005, 1.0 / 64.0);\n" | |
| " color = vec4(lessThanEqual(abs(test - expected), threshold));\n" | |
| "}\n"; | |
| constexpr char testFragmentShaderSource[] = | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_FragColor = color;\n" | |
| "}\n"; | |
| ANGLE_GL_PROGRAM(program, testVertexShaderSource2, testFragmentShaderSource); | |
| GLuint mProgram2 = program.get(); | |
| ASSERT_EQ(positionLocation, glGetAttribLocation(mProgram2, "position")); | |
| ASSERT_EQ(mTestAttrib, glGetAttribLocation(mProgram2, "test")); | |
| ASSERT_EQ(mExpectedAttrib, glGetAttribLocation(mProgram2, "expected")); | |
| // Pass a client memory pointer to disabledAttribute and disable it. | |
| GLint disabledAttribute = glGetAttribLocation(mProgram2, "disabled"); | |
| ASSERT_EQ(-1, glGetAttribLocation(mProgram, "disabled")); | |
| glVertexAttribPointer(disabledAttribute, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data()); | |
| glDisableVertexAttribArray(disabledAttribute); | |
| glUseProgram(mProgram); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| // Now enable disabledAttribute which should be used in mProgram2. | |
| glEnableVertexAttribArray(disabledAttribute); | |
| glUseProgram(mProgram2); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test based on WebGL Test attribs/gl-disabled-vertex-attrib.html | |
| TEST_P(VertexAttributeTest, DisabledAttribArrays) | |
| { | |
| // Known failure on Retina MBP: http://crbug.com/635081 | |
| ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA()); | |
| // TODO: Support this test on Vulkan. http://anglebug.com/2797 | |
| ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel()); | |
| constexpr char kVS[] = | |
| "attribute vec4 a_position;\n" | |
| "attribute vec4 a_color;\n" | |
| "varying vec4 v_color;\n" | |
| "bool isCorrectColor(vec4 v) {\n" | |
| " return v.x == 0.0 && v.y == 0.0 && v.z == 0.0 && v.w == 1.0;\n" | |
| "}" | |
| "void main() {\n" | |
| " gl_Position = a_position;\n" | |
| " v_color = isCorrectColor(a_color) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);\n" | |
| "}"; | |
| constexpr char kFS[] = | |
| "varying mediump vec4 v_color;\n" | |
| "void main() {\n" | |
| " gl_FragColor = v_color;\n" | |
| "}"; | |
| GLint maxVertexAttribs = 0; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs); | |
| for (GLint colorIndex = 0; colorIndex < maxVertexAttribs; ++colorIndex) | |
| { | |
| GLuint program = CompileProgram(kVS, kFS, [&](GLuint program) { | |
| glBindAttribLocation(program, colorIndex, "a_color"); | |
| }); | |
| ASSERT_NE(0u, program); | |
| drawQuad(program, "a_position", 0.5f); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| glDeleteProgram(program); | |
| } | |
| } | |
| class VertexAttributeTestES31 : public VertexAttributeTestES3 | |
| { | |
| protected: | |
| VertexAttributeTestES31() {} | |
| void initTest() | |
| { | |
| initBasicProgram(); | |
| glUseProgram(mProgram); | |
| glGenVertexArrays(1, &mVAO); | |
| glBindVertexArray(mVAO); | |
| auto quadVertices = GetQuadVertices(); | |
| GLsizeiptr quadVerticesSize = | |
| static_cast<GLsizeiptr>(quadVertices.size() * sizeof(quadVertices[0])); | |
| glGenBuffers(1, &mQuadBuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, quadVerticesSize, quadVertices.data(), GL_STATIC_DRAW); | |
| GLint positionLocation = glGetAttribLocation(mProgram, "position"); | |
| ASSERT_NE(-1, positionLocation); | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(positionLocation); | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| expectedData[count] = static_cast<GLfloat>(count); | |
| } | |
| const GLsizei kExpectedDataSize = kVertexCount * kFloatStride; | |
| glGenBuffers(1, &mExpectedBuffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, mExpectedBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, kExpectedDataSize, expectedData.data(), GL_STATIC_DRAW); | |
| glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| } | |
| void testTearDown() override | |
| { | |
| VertexAttributeTestES3::testTearDown(); | |
| glDeleteBuffers(1, &mExpectedBuffer); | |
| glDeleteVertexArrays(1, &mVAO); | |
| } | |
| void drawArraysWithStrideAndRelativeOffset(GLint stride, GLuint relativeOffset) | |
| { | |
| initTest(); | |
| GLint floatStride = std::max(stride / kFloatStride, 1); | |
| GLuint floatRelativeOffset = relativeOffset / kFloatStride; | |
| size_t floatCount = static_cast<size_t>(floatRelativeOffset) + kVertexCount * floatStride; | |
| GLsizeiptr inputSize = static_cast<GLsizeiptr>(floatCount) * kFloatStride; | |
| std::vector<GLfloat> inputData(floatCount); | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| inputData[floatRelativeOffset + count * floatStride] = static_cast<GLfloat>(count); | |
| } | |
| // Ensure inputSize, inputStride and inputOffset are multiples of TypeStride(GL_FLOAT). | |
| GLsizei inputStride = floatStride * kFloatStride; | |
| GLsizeiptr inputRelativeOffset = floatRelativeOffset * kFloatStride; | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, inputSize, nullptr, GL_STATIC_DRAW); | |
| glBufferSubData(GL_ARRAY_BUFFER, 0, inputSize, inputData.data()); | |
| glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE, | |
| base::checked_cast<GLuint>(inputRelativeOffset)); | |
| glBindVertexBuffer(mTestAttrib, mBuffer, 0, inputStride); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| void initOnlyUpdateBindingTest(GLint bindingToUpdate) | |
| { | |
| initTest(); | |
| constexpr GLuint kTestFloatOffset1 = kVertexCount; | |
| std::array<GLfloat, kTestFloatOffset1 + kVertexCount> inputData1 = {}; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| GLfloat value = static_cast<GLfloat>(count); | |
| inputData1[kTestFloatOffset1 + count] = value; | |
| } | |
| GLBuffer testBuffer1; | |
| glBindBuffer(GL_ARRAY_BUFFER, testBuffer1); | |
| glBufferData(GL_ARRAY_BUFFER, inputData1.size() * kFloatStride, inputData1.data(), | |
| GL_STATIC_DRAW); | |
| ASSERT_NE(bindingToUpdate, mTestAttrib); | |
| ASSERT_NE(bindingToUpdate, mExpectedAttrib); | |
| // Set mTestAttrib using the binding bindingToUpdate. | |
| glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0); | |
| glBindVertexBuffer(bindingToUpdate, testBuffer1, kTestFloatOffset1 * kFloatStride, | |
| kFloatStride); | |
| glVertexAttribBinding(mTestAttrib, bindingToUpdate); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| // In the first draw the current VAO states are set to driver. | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| // We need the second draw to ensure all VAO dirty bits are reset. | |
| // e.g. On D3D11 back-ends, Buffer11::resize is called in the first draw, where the related | |
| // binding is set to dirty again. | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| GLuint mVAO; | |
| GLuint mExpectedBuffer; | |
| const GLsizei kFloatStride = TypeStride(GL_FLOAT); | |
| // Set the maximum value for stride and relativeOffset in case they are too large. | |
| const GLint MAX_STRIDE_FOR_TEST = 4095; | |
| const GLint MAX_RELATIVE_OFFSET_FOR_TEST = 4095; | |
| }; | |
| // Verify that MAX_VERTEX_ATTRIB_STRIDE is no less than the minimum required value (2048) in ES3.1. | |
| TEST_P(VertexAttributeTestES31, MaxVertexAttribStride) | |
| { | |
| GLint maxStride; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_GE(maxStride, 2048); | |
| } | |
| // Verify that GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET is no less than the minimum required value | |
| // (2047) in ES3.1. | |
| TEST_P(VertexAttributeTestES31, MaxVertexAttribRelativeOffset) | |
| { | |
| GLint maxRelativeOffset; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxRelativeOffset); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_GE(maxRelativeOffset, 2047); | |
| } | |
| // Verify using MAX_VERTEX_ATTRIB_STRIDE as stride doesn't mess up the draw. | |
| // Use default value if the value of MAX_VERTEX_ATTRIB_STRIDE is too large for this test. | |
| TEST_P(VertexAttributeTestES31, DrawArraysWithLargeStride) | |
| { | |
| GLint maxStride; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride); | |
| ASSERT_GL_NO_ERROR(); | |
| GLint largeStride = std::min(maxStride, MAX_STRIDE_FOR_TEST); | |
| drawArraysWithStrideAndRelativeOffset(largeStride, 0); | |
| } | |
| // Verify using MAX_VERTEX_ATTRIB_RELATIVE_OFFSET as relativeOffset doesn't mess up the draw. | |
| // Use default value if the value of MAX_VERTEX_ATTRIB_RELATIVE_OFFSSET is too large for this test. | |
| TEST_P(VertexAttributeTestES31, DrawArraysWithLargeRelativeOffset) | |
| { | |
| GLint maxRelativeOffset; | |
| glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxRelativeOffset); | |
| ASSERT_GL_NO_ERROR(); | |
| GLint largeRelativeOffset = std::min(maxRelativeOffset, MAX_RELATIVE_OFFSET_FOR_TEST); | |
| drawArraysWithStrideAndRelativeOffset(0, largeRelativeOffset); | |
| } | |
| // Test that vertex array object works correctly when render pipeline and compute pipeline are | |
| // crossly executed. | |
| TEST_P(VertexAttributeTestES31, MixedComputeAndRenderPipelines) | |
| { | |
| constexpr char kComputeShader[] = | |
| R"(#version 310 es | |
| layout(local_size_x=1) in; | |
| void main() | |
| { | |
| })"; | |
| ANGLE_GL_COMPUTE_PROGRAM(computePogram, kComputeShader); | |
| glViewport(0, 0, getWindowWidth(), getWindowHeight()); | |
| glClearColor(0, 0, 0, 0); | |
| constexpr char kVertexShader[] = | |
| R"(#version 310 es | |
| precision mediump float; | |
| layout(location = 0) in vec4 position; | |
| layout(location = 2) in vec2 aOffset; | |
| layout(location = 3) in vec4 aColor; | |
| out vec4 vColor; | |
| void main() { | |
| vColor = aColor; | |
| gl_Position = position + vec4(aOffset, 0.0, 0.0); | |
| })"; | |
| constexpr char kFragmentShader[] = | |
| R"(#version 310 es | |
| precision mediump float; | |
| in vec4 vColor; | |
| out vec4 color; | |
| void main() { | |
| color = vColor; | |
| })"; | |
| ANGLE_GL_PROGRAM(renderProgram, kVertexShader, kFragmentShader); | |
| constexpr char kVertexShader1[] = | |
| R"(#version 310 es | |
| precision mediump float; | |
| layout(location = 1) in vec4 position; | |
| layout(location = 2) in vec2 aOffset; | |
| layout(location = 3) in vec4 aColor; | |
| out vec4 vColor; | |
| void main() { | |
| vColor = aColor; | |
| gl_Position = position + vec4(aOffset, 0.0, 0.0); | |
| })"; | |
| ANGLE_GL_PROGRAM(renderProgram1, kVertexShader1, kFragmentShader); | |
| std::array<GLfloat, 8> offsets = { | |
| -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, | |
| }; | |
| GLBuffer offsetBuffer; | |
| glBindBuffer(GL_ARRAY_BUFFER, offsetBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, offsets.size() * sizeof(GLfloat), offsets.data(), GL_STATIC_DRAW); | |
| std::array<GLfloat, 16> colors0 = { | |
| 1.0, 0.0, 0.0, 1.0, // Red | |
| 0.0, 1.0, 0.0, 1.0, // Green | |
| 0.0, 0.0, 1.0, 1.0, // Blue | |
| 1.0, 1.0, 0.0, 1.0, // Yellow | |
| }; | |
| std::array<GLfloat, 16> colors1 = { | |
| 1.0, 1.0, 0.0, 1.0, // Yellow | |
| 0.0, 0.0, 1.0, 1.0, // Blue | |
| 0.0, 1.0, 0.0, 1.0, // Green | |
| 1.0, 0.0, 0.0, 1.0, // Red | |
| }; | |
| GLBuffer colorBuffers[2]; | |
| glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[0]); | |
| glBufferData(GL_ARRAY_BUFFER, colors0.size() * sizeof(GLfloat), colors0.data(), GL_STATIC_DRAW); | |
| glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[1]); | |
| glBufferData(GL_ARRAY_BUFFER, colors1.size() * sizeof(GLfloat), colors1.data(), GL_STATIC_DRAW); | |
| std::array<GLfloat, 16> positions = {1.0, 1.0, -1.0, 1.0, -1.0, -1.0, | |
| 1.0, 1.0, -1.0, -1.0, 1.0, -1.0}; | |
| GLBuffer positionBuffer; | |
| glBindBuffer(GL_ARRAY_BUFFER, positionBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(GLfloat), positions.data(), | |
| GL_STATIC_DRAW); | |
| const int kInstanceCount = 4; | |
| GLVertexArray vao[2]; | |
| for (size_t i = 0u; i < 2u; ++i) | |
| { | |
| glBindVertexArray(vao[i]); | |
| glBindBuffer(GL_ARRAY_BUFFER, offsetBuffer); | |
| glEnableVertexAttribArray(2); | |
| glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, 0); | |
| glVertexAttribDivisor(2, 1); | |
| glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[i]); | |
| glEnableVertexAttribArray(3); | |
| glVertexAttribPointer(3, 4, GL_FLOAT, false, 0, 0); | |
| glVertexAttribDivisor(3, 1); | |
| glBindBuffer(GL_ARRAY_BUFFER, positionBuffer); | |
| glEnableVertexAttribArray(i); | |
| glVertexAttribPointer(i, 2, GL_FLOAT, false, 0, 0); | |
| } | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| for (int i = 0; i < 3; i++) | |
| { | |
| glUseProgram(renderProgram.get()); | |
| glBindVertexArray(vao[0]); | |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, kInstanceCount); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 2, GLColor::red); | |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); | |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, 0, GLColor::yellow); | |
| glBindVertexArray(vao[1]); | |
| glUseProgram(computePogram.get()); | |
| glDispatchCompute(1, 1, 1); | |
| glUseProgram(renderProgram1.get()); | |
| glBindVertexArray(vao[1]); | |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6, kInstanceCount); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 2, GLColor::yellow); | |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, 0, GLColor::red); | |
| } | |
| } | |
| TEST_P(VertexAttributeTestES31, UseComputeShaderToUpdateVertexBuffer) | |
| { | |
| initTest(); | |
| constexpr char kComputeShader[] = | |
| R"(#version 310 es | |
| layout(local_size_x=24) in; | |
| layout(std430, binding = 0) buffer buf { | |
| uint outData[24]; | |
| }; | |
| void main() | |
| { | |
| outData[gl_LocalInvocationIndex] = gl_LocalInvocationIndex; | |
| })"; | |
| ANGLE_GL_COMPUTE_PROGRAM(computeProgram, kComputeShader); | |
| glUseProgram(mProgram); | |
| GLuint mid = std::numeric_limits<GLuint>::max() >> 1; | |
| GLuint hi = std::numeric_limits<GLuint>::max(); | |
| std::array<GLuint, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}}; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = Normalize(inputData[i]); | |
| } | |
| // Normalized unsigned int attribute will be classified as translated static attribute. | |
| TestData data(GL_UNSIGNED_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data()); | |
| GLint typeSize = 4; | |
| GLsizei dataSize = kVertexCount * TypeStride(data.type); | |
| GLBuffer testBuffer; | |
| glBindBuffer(GL_ARRAY_BUFFER, testBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, dataSize, data.inputData, GL_STATIC_DRAW); | |
| glVertexAttribPointer(mTestAttrib, typeSize, data.type, data.normalized, 0, | |
| reinterpret_cast<void *>(data.bufferOffset)); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glBindBuffer(GL_ARRAY_BUFFER, mExpectedBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, dataSize, data.expectedData, GL_STATIC_DRAW); | |
| glVertexAttribPointer(mExpectedAttrib, typeSize, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| // Draw twice to make sure that all static attributes dirty bits are synced. | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| // Modify the testBuffer using a raw buffer | |
| glUseProgram(computeProgram); | |
| glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, testBuffer); | |
| glDispatchCompute(1, 1, 1); | |
| glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT); | |
| // Draw again to verify that testBuffer has been changed. | |
| glUseProgram(mProgram); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| EXPECT_GL_NO_ERROR(); | |
| checkPixelsUnEqual(); | |
| } | |
| // Verify that using VertexAttribBinding after VertexAttribPointer won't mess up the draw. | |
| TEST_P(VertexAttributeTestES31, ChangeAttribBindingAfterVertexAttribPointer) | |
| { | |
| initTest(); | |
| constexpr GLint kInputStride = 2; | |
| constexpr GLint kFloatOffset = 10; | |
| std::array<GLfloat, kVertexCount + kFloatOffset> inputData1; | |
| std::array<GLfloat, kVertexCount * kInputStride> inputData2; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| inputData1[kFloatOffset + count] = static_cast<GLfloat>(count); | |
| inputData2[count * kInputStride] = static_cast<GLfloat>(count); | |
| } | |
| GLBuffer mBuffer1; | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer1); | |
| glBufferData(GL_ARRAY_BUFFER, inputData1.size() * kFloatStride, inputData1.data(), | |
| GL_STATIC_DRAW); | |
| // Update the format indexed mTestAttrib and the binding indexed mTestAttrib by | |
| // VertexAttribPointer. | |
| const GLintptr kOffset = static_cast<GLintptr>(kFloatStride * kFloatOffset); | |
| glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, | |
| reinterpret_cast<const GLvoid *>(kOffset)); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| constexpr GLint kTestBinding = 10; | |
| ASSERT_NE(mTestAttrib, kTestBinding); | |
| GLBuffer mBuffer2; | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer2); | |
| glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(), | |
| GL_STATIC_DRAW); | |
| glBindVertexBuffer(kTestBinding, mBuffer2, 0, kFloatStride * kInputStride); | |
| // The attribute indexed mTestAttrib is using the binding indexed kTestBinding in the first | |
| // draw. | |
| glVertexAttribBinding(mTestAttrib, kTestBinding); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| // The attribute indexed mTestAttrib is using the binding indexed mTestAttrib which should be | |
| // set after the call VertexAttribPointer before the first draw. | |
| glVertexAttribBinding(mTestAttrib, mTestAttrib); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Verify that using VertexAttribFormat after VertexAttribPointer won't mess up the draw. | |
| TEST_P(VertexAttributeTestES31, ChangeAttribFormatAfterVertexAttribPointer) | |
| { | |
| initTest(); | |
| constexpr GLuint kFloatOffset = 10; | |
| std::array<GLfloat, kVertexCount + kFloatOffset> inputData; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| inputData[kFloatOffset + count] = static_cast<GLfloat>(count); | |
| } | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glBufferData(GL_ARRAY_BUFFER, inputData.size() * kFloatStride, inputData.data(), | |
| GL_STATIC_DRAW); | |
| // Call VertexAttribPointer on mTestAttrib. Now the relativeOffset of mTestAttrib should be 0. | |
| const GLuint kOffset = static_cast<GLuint>(kFloatStride * kFloatOffset); | |
| glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| // Call VertexAttribFormat on mTestAttrib to modify the relativeOffset to kOffset. | |
| glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE, kOffset); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Verify that only updating a binding without updating the bound format won't mess up this draw. | |
| TEST_P(VertexAttributeTestES31, OnlyUpdateBindingByBindVertexBuffer) | |
| { | |
| // Default binding index for test | |
| constexpr GLint kTestBinding = 10; | |
| initOnlyUpdateBindingTest(kTestBinding); | |
| constexpr GLuint kTestFloatOffset2 = kVertexCount * 2; | |
| std::array<GLfloat, kVertexCount> expectedData2 = {}; | |
| std::array<GLfloat, kTestFloatOffset2 + kVertexCount> inputData2 = {}; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| GLfloat value2 = static_cast<GLfloat>(count) * 2; | |
| expectedData2[count] = value2; | |
| inputData2[count + kTestFloatOffset2] = value2; | |
| } | |
| // Set another set of data for mExpectedAttrib. | |
| GLBuffer expectedBuffer2; | |
| glBindBuffer(GL_ARRAY_BUFFER, expectedBuffer2); | |
| glBufferData(GL_ARRAY_BUFFER, expectedData2.size() * kFloatStride, expectedData2.data(), | |
| GL_STATIC_DRAW); | |
| glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| GLBuffer testBuffer2; | |
| glBindBuffer(GL_ARRAY_BUFFER, testBuffer2); | |
| glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(), | |
| GL_STATIC_DRAW); | |
| // Only update the binding kTestBinding in the second draw by BindVertexBuffer. | |
| glBindVertexBuffer(kTestBinding, testBuffer2, kTestFloatOffset2 * kFloatStride, kFloatStride); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Verify that only updating a binding without updating the bound format won't mess up this draw. | |
| TEST_P(VertexAttributeTestES31, OnlyUpdateBindingByVertexAttribPointer) | |
| { | |
| // Default binding index for test | |
| constexpr GLint kTestBinding = 10; | |
| initOnlyUpdateBindingTest(kTestBinding); | |
| constexpr GLuint kTestFloatOffset2 = kVertexCount * 3; | |
| std::array<GLfloat, kVertexCount> expectedData2 = {}; | |
| std::array<GLfloat, kTestFloatOffset2 + kVertexCount> inputData2 = {}; | |
| for (size_t count = 0; count < kVertexCount; ++count) | |
| { | |
| GLfloat value2 = static_cast<GLfloat>(count) * 3; | |
| expectedData2[count] = value2; | |
| inputData2[count + kTestFloatOffset2] = value2; | |
| } | |
| // Set another set of data for mExpectedAttrib. | |
| GLBuffer expectedBuffer2; | |
| glBindBuffer(GL_ARRAY_BUFFER, expectedBuffer2); | |
| glBufferData(GL_ARRAY_BUFFER, expectedData2.size() * kFloatStride, expectedData2.data(), | |
| GL_STATIC_DRAW); | |
| glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| GLBuffer testBuffer2; | |
| glBindBuffer(GL_ARRAY_BUFFER, testBuffer2); | |
| glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(), | |
| GL_STATIC_DRAW); | |
| // Only update the binding kTestBinding in the second draw by VertexAttribPointer. | |
| glVertexAttribPointer(kTestBinding, 1, GL_FLOAT, GL_FALSE, 0, | |
| reinterpret_cast<const void *>(kTestFloatOffset2 * kFloatStride)); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| checkPixels(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| class VertexAttributeCachingTest : public VertexAttributeTest | |
| { | |
| protected: | |
| VertexAttributeCachingTest() {} | |
| void testSetUp() override; | |
| template <typename DestT> | |
| static std::vector<GLfloat> GetExpectedData(const std::vector<GLubyte> &srcData, | |
| GLenum attribType, | |
| GLboolean normalized); | |
| void initDoubleAttribProgram() | |
| { | |
| constexpr char kVS[] = | |
| "attribute mediump vec4 position;\n" | |
| "attribute mediump vec4 test;\n" | |
| "attribute mediump vec4 expected;\n" | |
| "attribute mediump vec4 test2;\n" | |
| "attribute mediump vec4 expected2;\n" | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_Position = position;\n" | |
| " vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0);\n" | |
| " color = vec4(lessThanEqual(abs(test - expected), threshold));\n" | |
| " vec4 threshold2 = max(abs(expected2) * 0.01, 1.0 / 64.0);\n" | |
| " color += vec4(lessThanEqual(abs(test2 - expected2), threshold2));\n" | |
| "}\n"; | |
| constexpr char kFS[] = | |
| "varying mediump vec4 color;\n" | |
| "void main(void)\n" | |
| "{\n" | |
| " gl_FragColor = color;\n" | |
| "}\n"; | |
| mProgram = CompileProgram(kVS, kFS); | |
| ASSERT_NE(0u, mProgram); | |
| mTestAttrib = glGetAttribLocation(mProgram, "test"); | |
| ASSERT_NE(-1, mTestAttrib); | |
| mExpectedAttrib = glGetAttribLocation(mProgram, "expected"); | |
| ASSERT_NE(-1, mExpectedAttrib); | |
| glUseProgram(mProgram); | |
| } | |
| struct AttribData | |
| { | |
| AttribData(GLenum typeIn, GLint sizeIn, GLboolean normalizedIn, GLsizei strideIn); | |
| GLenum type; | |
| GLint size; | |
| GLboolean normalized; | |
| GLsizei stride; | |
| }; | |
| std::vector<AttribData> mTestData; | |
| std::map<GLenum, std::vector<GLfloat>> mExpectedData; | |
| std::map<GLenum, std::vector<GLfloat>> mNormExpectedData; | |
| }; | |
| VertexAttributeCachingTest::AttribData::AttribData(GLenum typeIn, | |
| GLint sizeIn, | |
| GLboolean normalizedIn, | |
| GLsizei strideIn) | |
| : type(typeIn), size(sizeIn), normalized(normalizedIn), stride(strideIn) | |
| {} | |
| // static | |
| template <typename DestT> | |
| std::vector<GLfloat> VertexAttributeCachingTest::GetExpectedData( | |
| const std::vector<GLubyte> &srcData, | |
| GLenum attribType, | |
| GLboolean normalized) | |
| { | |
| std::vector<GLfloat> expectedData; | |
| const DestT *typedSrcPtr = reinterpret_cast<const DestT *>(srcData.data()); | |
| size_t iterations = srcData.size() / TypeStride(attribType); | |
| if (normalized) | |
| { | |
| for (size_t index = 0; index < iterations; ++index) | |
| { | |
| expectedData.push_back(Normalize(typedSrcPtr[index])); | |
| } | |
| } | |
| else | |
| { | |
| for (size_t index = 0; index < iterations; ++index) | |
| { | |
| expectedData.push_back(static_cast<GLfloat>(typedSrcPtr[index])); | |
| } | |
| } | |
| return expectedData; | |
| } | |
| void VertexAttributeCachingTest::testSetUp() | |
| { | |
| VertexAttributeTest::testSetUp(); | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| std::vector<GLubyte> srcData; | |
| for (size_t count = 0; count < 4; ++count) | |
| { | |
| for (GLubyte i = 0; i < std::numeric_limits<GLubyte>::max(); ++i) | |
| { | |
| srcData.push_back(i); | |
| } | |
| } | |
| glBufferData(GL_ARRAY_BUFFER, srcData.size(), srcData.data(), GL_STATIC_DRAW); | |
| GLint viewportSize[4]; | |
| glGetIntegerv(GL_VIEWPORT, viewportSize); | |
| std::vector<GLenum> attribTypes; | |
| attribTypes.push_back(GL_BYTE); | |
| attribTypes.push_back(GL_UNSIGNED_BYTE); | |
| attribTypes.push_back(GL_SHORT); | |
| attribTypes.push_back(GL_UNSIGNED_SHORT); | |
| if (getClientMajorVersion() >= 3) | |
| { | |
| attribTypes.push_back(GL_INT); | |
| attribTypes.push_back(GL_UNSIGNED_INT); | |
| } | |
| constexpr GLint kMaxSize = 4; | |
| constexpr GLsizei kMaxStride = 4; | |
| for (GLenum attribType : attribTypes) | |
| { | |
| for (GLint attribSize = 1; attribSize <= kMaxSize; ++attribSize) | |
| { | |
| for (GLsizei stride = 1; stride <= kMaxStride; ++stride) | |
| { | |
| mTestData.push_back(AttribData(attribType, attribSize, GL_FALSE, stride)); | |
| if (attribType != GL_FLOAT) | |
| { | |
| mTestData.push_back(AttribData(attribType, attribSize, GL_TRUE, stride)); | |
| } | |
| } | |
| } | |
| } | |
| mExpectedData[GL_BYTE] = GetExpectedData<GLbyte>(srcData, GL_BYTE, GL_FALSE); | |
| mExpectedData[GL_UNSIGNED_BYTE] = GetExpectedData<GLubyte>(srcData, GL_UNSIGNED_BYTE, GL_FALSE); | |
| mExpectedData[GL_SHORT] = GetExpectedData<GLshort>(srcData, GL_SHORT, GL_FALSE); | |
| mExpectedData[GL_UNSIGNED_SHORT] = | |
| GetExpectedData<GLushort>(srcData, GL_UNSIGNED_SHORT, GL_FALSE); | |
| mExpectedData[GL_INT] = GetExpectedData<GLint>(srcData, GL_INT, GL_FALSE); | |
| mExpectedData[GL_UNSIGNED_INT] = GetExpectedData<GLuint>(srcData, GL_UNSIGNED_INT, GL_FALSE); | |
| mNormExpectedData[GL_BYTE] = GetExpectedData<GLbyte>(srcData, GL_BYTE, GL_TRUE); | |
| mNormExpectedData[GL_UNSIGNED_BYTE] = | |
| GetExpectedData<GLubyte>(srcData, GL_UNSIGNED_BYTE, GL_TRUE); | |
| mNormExpectedData[GL_SHORT] = GetExpectedData<GLshort>(srcData, GL_SHORT, GL_TRUE); | |
| mNormExpectedData[GL_UNSIGNED_SHORT] = | |
| GetExpectedData<GLushort>(srcData, GL_UNSIGNED_SHORT, GL_TRUE); | |
| mNormExpectedData[GL_INT] = GetExpectedData<GLint>(srcData, GL_INT, GL_TRUE); | |
| mNormExpectedData[GL_UNSIGNED_INT] = GetExpectedData<GLuint>(srcData, GL_UNSIGNED_INT, GL_TRUE); | |
| } | |
| // In D3D11, we must sometimes translate buffer data into static attribute caches. We also use a | |
| // cache management scheme which garbage collects old attributes after we start using too much | |
| // cache data. This test tries to make as many attribute caches from a single buffer as possible | |
| // to stress-test the caching code. | |
| TEST_P(VertexAttributeCachingTest, BufferMulticaching) | |
| { | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL()); | |
| initBasicProgram(); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| ASSERT_GL_NO_ERROR(); | |
| for (const AttribData &data : mTestData) | |
| { | |
| const auto &expected = | |
| (data.normalized) ? mNormExpectedData[data.type] : mExpectedData[data.type]; | |
| GLsizei baseStride = static_cast<GLsizei>(data.size) * data.stride; | |
| GLsizei stride = TypeStride(data.type) * baseStride; | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glVertexAttribPointer(mTestAttrib, data.size, data.type, data.normalized, stride, nullptr); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(mExpectedAttrib, data.size, GL_FLOAT, GL_FALSE, | |
| sizeof(GLfloat) * baseStride, expected.data()); | |
| drawQuad(mProgram, "position", 0.5f); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white); | |
| } | |
| } | |
| // With D3D11 dirty bits for VertxArray11, we can leave vertex state unchanged if there aren't any | |
| // GL calls that affect it. This test targets leaving one vertex attribute unchanged between draw | |
| // calls while changing another vertex attribute enough that it clears the static buffer cache | |
| // after enough iterations. It validates the unchanged attributes don't get deleted incidentally. | |
| TEST_P(VertexAttributeCachingTest, BufferMulticachingWithOneUnchangedAttrib) | |
| { | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL()); | |
| initDoubleAttribProgram(); | |
| GLint testAttrib2Location = glGetAttribLocation(mProgram, "test2"); | |
| ASSERT_NE(-1, testAttrib2Location); | |
| GLint expectedAttrib2Location = glGetAttribLocation(mProgram, "expected2"); | |
| ASSERT_NE(-1, expectedAttrib2Location); | |
| glEnableVertexAttribArray(mTestAttrib); | |
| glEnableVertexAttribArray(mExpectedAttrib); | |
| glEnableVertexAttribArray(testAttrib2Location); | |
| glEnableVertexAttribArray(expectedAttrib2Location); | |
| ASSERT_GL_NO_ERROR(); | |
| // Use an attribute that we know must be converted. This is a bit sensitive. | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glVertexAttribPointer(testAttrib2Location, 3, GL_UNSIGNED_SHORT, GL_FALSE, 6, nullptr); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(expectedAttrib2Location, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, | |
| mExpectedData[GL_UNSIGNED_SHORT].data()); | |
| for (const auto &data : mTestData) | |
| { | |
| const auto &expected = | |
| (data.normalized) ? mNormExpectedData[data.type] : mExpectedData[data.type]; | |
| GLsizei baseStride = static_cast<GLsizei>(data.size) * data.stride; | |
| GLsizei stride = TypeStride(data.type) * baseStride; | |
| glBindBuffer(GL_ARRAY_BUFFER, mBuffer); | |
| glVertexAttribPointer(mTestAttrib, data.size, data.type, data.normalized, stride, nullptr); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glVertexAttribPointer(mExpectedAttrib, data.size, GL_FLOAT, GL_FALSE, | |
| sizeof(GLfloat) * baseStride, expected.data()); | |
| drawQuad(mProgram, "position", 0.5f); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 255, 255, 255); | |
| } | |
| } | |
| // Test that if there are gaps in the attribute indices, the attributes have their correct values. | |
| TEST_P(VertexAttributeTest, UnusedVertexAttribWorks) | |
| { | |
| constexpr char kVertexShader[] = R"(attribute vec2 position; | |
| attribute float actualValue; | |
| uniform float expectedValue; | |
| varying float result; | |
| void main() | |
| { | |
| result = (actualValue == expectedValue) ? 1.0 : 0.0; | |
| gl_Position = vec4(position, 0, 1); | |
| })"; | |
| constexpr char kFragmentShader[] = R"(varying mediump float result; | |
| void main() | |
| { | |
| gl_FragColor = result > 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1); | |
| })"; | |
| ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader); | |
| // Force a gap in attributes by using location 0 and 3 | |
| GLint positionLocation = 0; | |
| glBindAttribLocation(program, positionLocation, "position"); | |
| GLint attribLoc = 3; | |
| glBindAttribLocation(program, attribLoc, "actualValue"); | |
| // Re-link the program to update the attribute locations | |
| glLinkProgram(program); | |
| ASSERT_TRUE(CheckLinkStatusAndReturnProgram(program, true)); | |
| glUseProgram(program); | |
| GLint uniLoc = glGetUniformLocation(program, "expectedValue"); | |
| ASSERT_NE(-1, uniLoc); | |
| glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| ASSERT_NE(-1, positionLocation); | |
| setupQuadVertexBuffer(0.5f, 1.0f); | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0); | |
| glEnableVertexAttribArray(positionLocation); | |
| std::array<GLfloat, 4> testValues = {{1, 2, 3, 4}}; | |
| for (GLfloat testValue : testValues) | |
| { | |
| glUniform1f(uniLoc, testValue); | |
| glVertexAttrib1f(attribLoc, testValue); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| } | |
| } | |
| // Tests that repeatedly updating a disabled vertex attribute works as expected. | |
| // This covers an ANGLE bug where dirty bits for current values were ignoring repeated updates. | |
| TEST_P(VertexAttributeTest, DisabledAttribUpdates) | |
| { | |
| constexpr char kVertexShader[] = R"(attribute vec2 position; | |
| attribute float actualValue; | |
| uniform float expectedValue; | |
| varying float result; | |
| void main() | |
| { | |
| result = (actualValue == expectedValue) ? 1.0 : 0.0; | |
| gl_Position = vec4(position, 0, 1); | |
| })"; | |
| constexpr char kFragmentShader[] = R"(varying mediump float result; | |
| void main() | |
| { | |
| gl_FragColor = result > 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1); | |
| })"; | |
| ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader); | |
| glUseProgram(program); | |
| GLint attribLoc = glGetAttribLocation(program, "actualValue"); | |
| ASSERT_NE(-1, attribLoc); | |
| GLint uniLoc = glGetUniformLocation(program, "expectedValue"); | |
| ASSERT_NE(-1, uniLoc); | |
| glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| GLint positionLocation = glGetAttribLocation(program, "position"); | |
| ASSERT_NE(-1, positionLocation); | |
| setupQuadVertexBuffer(0.5f, 1.0f); | |
| glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0); | |
| glEnableVertexAttribArray(positionLocation); | |
| std::array<GLfloat, 4> testValues = {{1, 2, 3, 4}}; | |
| for (GLfloat testValue : testValues) | |
| { | |
| glUniform1f(uniLoc, testValue); | |
| glVertexAttrib1f(attribLoc, testValue); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| } | |
| } | |
| // Test that even inactive attributes are taken into account when checking for aliasing in case the | |
| // shader version is >= 3.00. GLSL ES 3.00.6 section 12.46. | |
| TEST_P(VertexAttributeTestES3, InactiveAttributeAliasing) | |
| { | |
| constexpr char vertexShader[] = | |
| R"(#version 300 es | |
| precision mediump float; | |
| in vec4 input_active; | |
| in vec4 input_unused; | |
| void main() | |
| { | |
| gl_Position = input_active; | |
| })"; | |
| constexpr char fragmentShader[] = | |
| R"(#version 300 es | |
| precision mediump float; | |
| out vec4 color; | |
| void main() | |
| { | |
| color = vec4(0.0); | |
| })"; | |
| ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader); | |
| glBindAttribLocation(program, 0, "input_active"); | |
| glBindAttribLocation(program, 0, "input_unused"); | |
| glLinkProgram(program); | |
| EXPECT_GL_NO_ERROR(); | |
| GLint linkStatus = 0; | |
| glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); | |
| EXPECT_GL_FALSE(linkStatus); | |
| } | |
| // Test that enabling inactive attributes doesn't cause a crash | |
| // shader version is >= 3.00 | |
| TEST_P(VertexAttributeTestES3, EnabledButInactiveAttributes) | |
| { | |
| // This is similar to runtest(), and the test is disabled there | |
| ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL()); | |
| constexpr char testVertexShaderSource[] = | |
| R"(#version 300 es | |
| precision mediump float; | |
| in vec4 position; | |
| layout(location = 1) in vec4 test; | |
| layout(location = 2) in vec4 unused1; | |
| layout(location = 3) in vec4 unused2; | |
| layout(location = 4) in vec4 unused3; | |
| layout(location = 5) in vec4 expected; | |
| out vec4 color; | |
| void main(void) | |
| { | |
| gl_Position = position; | |
| vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0); | |
| color = vec4(lessThanEqual(abs(test - expected), threshold)); | |
| })"; | |
| // Same as previous one, except it uses unused1/2 instead of test/expected, leaving unused3 | |
| // unused | |
| constexpr char testVertexShader2Source[] = | |
| R"(#version 300 es | |
| precision mediump float; | |
| in vec4 position; | |
| layout(location = 1) in vec4 test; | |
| layout(location = 2) in vec4 unused1; | |
| layout(location = 3) in vec4 unused2; | |
| layout(location = 4) in vec4 unused3; | |
| layout(location = 5) in vec4 expected; | |
| out vec4 color; | |
| void main(void) | |
| { | |
| gl_Position = position; | |
| vec4 threshold = max(abs(unused2) * 0.01, 1.0 / 64.0); | |
| color = vec4(lessThanEqual(abs(unused1 - unused2), threshold)); | |
| })"; | |
| constexpr char testFragmentShaderSource[] = | |
| R"(#version 300 es | |
| precision mediump float; | |
| in vec4 color; | |
| out vec4 out_color; | |
| void main() | |
| { | |
| out_color = color; | |
| })"; | |
| std::array<GLubyte, kVertexCount> inputData = { | |
| {0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}}; | |
| std::array<GLubyte, kVertexCount> inputData2; | |
| std::array<GLfloat, kVertexCount> expectedData; | |
| std::array<GLfloat, kVertexCount> expectedData2; | |
| for (size_t i = 0; i < kVertexCount; i++) | |
| { | |
| expectedData[i] = inputData[i]; | |
| inputData2[i] = inputData[i] > 128 ? inputData[i] - 1 : inputData[i] + 1; | |
| expectedData2[i] = inputData2[i]; | |
| } | |
| // Setup the program | |
| mProgram = CompileProgram(testVertexShaderSource, testFragmentShaderSource); | |
| ASSERT_NE(0u, mProgram); | |
| mTestAttrib = glGetAttribLocation(mProgram, "test"); | |
| ASSERT_EQ(1, mTestAttrib); | |
| mExpectedAttrib = glGetAttribLocation(mProgram, "expected"); | |
| ASSERT_EQ(5, mExpectedAttrib); | |
| GLint unused1Attrib = 2; | |
| GLint unused2Attrib = 3; | |
| GLint unused3Attrib = 4; | |
| // Test enabling an unused attribute before glUseProgram | |
| glEnableVertexAttribArray(unused3Attrib); | |
| glUseProgram(mProgram); | |
| // Setup the test data | |
| TestData data(GL_UNSIGNED_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(), | |
| expectedData.data()); | |
| setupTest(data, 1); | |
| // Test enabling an unused attribute after glUseProgram | |
| glVertexAttribPointer(unused1Attrib, 1, data.type, data.normalized, 0, inputData2.data()); | |
| glEnableVertexAttribArray(unused1Attrib); | |
| glVertexAttribPointer(unused2Attrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData2.data()); | |
| glEnableVertexAttribArray(unused2Attrib); | |
| // Run the test. This shouldn't use the unused attributes. Note that one of them is nullptr | |
| // which can cause a crash on certain platform-driver combination. | |
| drawQuad(mProgram, "position", 0.5f); | |
| checkPixels(); | |
| // Now test with the same attributes enabled, but with a program with different attributes | |
| // active | |
| mProgram = CompileProgram(testVertexShader2Source, testFragmentShaderSource); | |
| ASSERT_NE(0u, mProgram); | |
| // Make sure all the attributes are in the same location | |
| ASSERT_EQ(glGetAttribLocation(mProgram, "unused1"), unused1Attrib); | |
| ASSERT_EQ(glGetAttribLocation(mProgram, "unused2"), unused2Attrib); | |
| glUseProgram(mProgram); | |
| // Run the test again. unused1/2 were disabled in the previous run (as they were inactive in | |
| // the shader), but should be re-enabled now. | |
| drawQuad(mProgram, "position", 0.5f); | |
| checkPixels(); | |
| } | |
| // Use this to select which configurations (e.g. which renderer, which GLES major version) these | |
| // tests should be run against. | |
| // D3D11 Feature Level 9_3 uses different D3D formats for vertex attribs compared to Feature Levels | |
| // 10_0+, so we should test them separately. | |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeTest); | |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeOORTest); | |
| ANGLE_INSTANTIATE_TEST_ES3(VertexAttributeTestES3); | |
| ANGLE_INSTANTIATE_TEST_ES31(VertexAttributeTestES31); | |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeCachingTest); | |
| } // anonymous namespace |