// // Copyright 2018 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. // // AttributeLayoutTest: // Test various layouts of vertex attribute data: // - in memory, in buffer object, or combination of both // - sequential or interleaved // - various combinations of data types #include #include "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" using namespace angle; namespace { // Test will draw these four triangles. // clang-format off constexpr double kTriangleData[] = { // xy rgb 0,0, 1,1,0, -1,+1, 1,1,0, +1,+1, 1,1,0, 0,0, 0,1,0, +1,+1, 0,1,0, +1,-1, 0,1,0, 0,0, 0,1,1, +1,-1, 0,1,1, -1,-1, 0,1,1, 0,0, 1,0,1, -1,-1, 1,0,1, -1,+1, 1,0,1, }; // clang-format on constexpr size_t kNumVertices = ArraySize(kTriangleData) / 5; // Vertex data source description. class VertexData { public: VertexData(int dimension, const double *data, unsigned offset, unsigned stride, unsigned numVertices) : mNumVertices(numVertices), mDimension(dimension), mData(data), mOffset(offset), mStride(stride) {} int getDimension() const { return mDimension; } unsigned getNumVertices() const { return mNumVertices; } double getValue(unsigned vertexNumber, int component) const { return mData[mOffset + mStride * vertexNumber + component]; } private: unsigned mNumVertices; int mDimension; const double *mData; // offset and stride in doubles unsigned mOffset; unsigned mStride; }; // A container for one or more vertex attributes. class Container { public: static constexpr size_t kSize = 1024; void open(void) { memset(mMemory, 0xff, kSize); } void *getDestination(size_t offset) { return mMemory + offset; } virtual void close(void) {} virtual ~Container() {} virtual const char *getAddress() = 0; virtual GLuint getBuffer() = 0; protected: char mMemory[kSize]; }; // Vertex attribute data in client memory. class Memory : public Container { public: const char *getAddress() override { return mMemory; } GLuint getBuffer() override { return 0; } }; // Vertex attribute data in buffer object. class Buffer : public Container { public: void close(void) override { glBindBuffer(GL_ARRAY_BUFFER, mBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(mMemory), mMemory, GL_STATIC_DRAW); } const char *getAddress() override { return nullptr; } GLuint getBuffer() override { return mBuffer; } protected: GLBuffer mBuffer; }; // Encapsulate the storage, layout, format and data of a vertex attribute. struct Attrib { void openContainer(void) const { mContainer->open(); } void fillContainer(void) const { for (unsigned i = 0; i < mData.getNumVertices(); ++i) { for (int j = 0; j < mData.getDimension(); ++j) { size_t destOffset = mOffset + mStride * i + mCTypeSize * j; if (destOffset + mCTypeSize > Container::kSize) FAIL() << "test case does not fit container"; double value = mData.getValue(i, j); if (mGLType == GL_FIXED) value *= 1 << 16; else if (mNormalized) { if (value < mMinIn || value > mMaxIn) FAIL() << "test data does not fit format"; value = (value - mMinIn) * mScale + mMinOut; } mStore(value, mContainer->getDestination(destOffset)); } } } void closeContainer(void) const { mContainer->close(); } void enable(unsigned index) const { glBindBuffer(GL_ARRAY_BUFFER, mContainer->getBuffer()); if (mPureInteger) { glVertexAttribIPointer(index, mData.getDimension(), mGLType, mStride, mContainer->getAddress() + mOffset); } else { glVertexAttribPointer(index, mData.getDimension(), mGLType, mNormalized, mStride, mContainer->getAddress() + mOffset); } EXPECT_GL_NO_ERROR(); glEnableVertexAttribArray(index); } bool inClientMemory(void) const { return mContainer->getAddress() != nullptr; } std::shared_ptr mContainer; unsigned mOffset; unsigned mStride; const VertexData &mData; void (*mStore)(double value, void *dest); GLenum mGLType; GLboolean mNormalized; GLboolean mPureInteger = GL_FALSE; size_t mCTypeSize; double mMinIn; double mMaxIn; double mMinOut; double mScale; }; // Change type and store. template void Store(double value, void *dest) { T v = static_cast(value); memcpy(dest, &v, sizeof(v)); } // Function object that makes Attrib structs according to a vertex format. template class Format { static_assert(!(Normalized && GLType == GL_FLOAT), "Normalized float does not make sense."); public: Format(bool es3) : mES3(es3) {} Attrib operator()(std::shared_ptr container, unsigned offset, unsigned stride, const VertexData &data) const { double minIn = 0; double maxIn = 1; double minOut = std::numeric_limits::min(); double rangeOut = std::numeric_limits::max() - minOut; if (std::is_signed::value) { minIn = -1; maxIn = +1; if (mES3) { minOut += 1; rangeOut -= 1; } } return { container, offset, stride, data, Store, GLType, Normalized, PureInteger, sizeof(CType), minIn, maxIn, minOut, rangeOut / (maxIn - minIn), }; } protected: const bool mES3; }; typedef std::vector TestCase; void PrepareTestCase(const TestCase &tc) { for (const Attrib &a : tc) a.openContainer(); for (const Attrib &a : tc) a.fillContainer(); for (const Attrib &a : tc) a.closeContainer(); unsigned i = 0; for (const Attrib &a : tc) a.enable(i++); } class AttributeLayoutTest : public ANGLETest<> { protected: AttributeLayoutTest() : mProgram(0), mCoord(2, kTriangleData, 0, 5, kNumVertices), mColor(3, kTriangleData, 2, 5, kNumVertices) { setWindowWidth(128); setWindowHeight(128); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } void GetTestCases(void); void testSetUp() override { glClearColor(.2f, .2f, .2f, .0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); constexpr char kVS[] = "attribute mediump vec2 coord;\n" "attribute mediump vec3 color;\n" "varying mediump vec3 vcolor;\n" "void main(void)\n" "{\n" " gl_Position = vec4(coord, 0, 1);\n" " vcolor = color;\n" "}\n"; constexpr char kFS[] = "varying mediump vec3 vcolor;\n" "void main(void)\n" "{\n" " gl_FragColor = vec4(vcolor, 0);\n" "}\n"; mProgram = CompileProgram(kVS, kFS); ASSERT_NE(0u, mProgram); glUseProgram(mProgram); glGenBuffers(1, &mIndexBuffer); GetTestCases(); } void testTearDown() override { mTestCases.clear(); glDeleteProgram(mProgram); glDeleteBuffers(1, &mIndexBuffer); } virtual bool Skip(const TestCase &) { return false; } virtual void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) = 0; void Run(bool drawFirstTriangle) { glViewport(0, 0, getWindowWidth(), getWindowHeight()); glUseProgram(mProgram); for (unsigned i = 0; i < mTestCases.size(); ++i) { if (mTestCases[i].size() == 0 || Skip(mTestCases[i])) continue; PrepareTestCase(mTestCases[i]); glClear(GL_COLOR_BUFFER_BIT); std::string testCase; if (drawFirstTriangle) { Draw(0, kNumVertices, mIndices); testCase = "draw"; } else { Draw(3, kNumVertices - 3, mIndices + 3); testCase = "skip"; } testCase += " first triangle case "; int w = getWindowWidth() / 4; int h = getWindowHeight() / 4; if (drawFirstTriangle) { EXPECT_PIXEL_EQ(w * 2, h * 3, 255, 255, 0, 0) << testCase << i; } else { EXPECT_PIXEL_EQ(w * 2, h * 3, 51, 51, 51, 0) << testCase << i; } EXPECT_PIXEL_EQ(w * 3, h * 2, 0, 255, 0, 0) << testCase << i; EXPECT_PIXEL_EQ(w * 2, h * 1, 0, 255, 255, 0) << testCase << i; EXPECT_PIXEL_EQ(w * 1, h * 2, 255, 0, 255, 0) << testCase << i; } } static const GLushort mIndices[kNumVertices]; GLuint mProgram; GLuint mIndexBuffer; std::vector mTestCases; VertexData mCoord; VertexData mColor; }; const GLushort AttributeLayoutTest::mIndices[kNumVertices] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; void AttributeLayoutTest::GetTestCases(void) { const bool es3 = getClientMajorVersion() >= 3; Format Float(es3); Format Fixed(es3); Format SByte(es3); Format UByte(es3); Format SShort(es3); Format UShort(es3); Format SInt(es3); Format UInt(es3); Format NormSByte(es3); Format NormUByte(es3); Format NormSShort(es3); Format NormUShort(es3); Format NormSInt(es3); Format NormUInt(es3); std::shared_ptr M0 = std::make_shared(); std::shared_ptr M1 = std::make_shared(); std::shared_ptr B0 = std::make_shared(); std::shared_ptr B1 = std::make_shared(); // 0. two buffers mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(B1, 0, 12, mColor)}); // 1. two memory mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(M1, 0, 12, mColor)}); // 2. one memory, sequential mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(M0, 96, 12, mColor)}); // 3. one memory, interleaved mTestCases.push_back({Float(M0, 0, 20, mCoord), Float(M0, 8, 20, mColor)}); // 4. buffer and memory mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(M0, 0, 12, mColor)}); // 5. stride != size mTestCases.push_back({Float(B0, 0, 16, mCoord), Float(B1, 0, 12, mColor)}); // 6-7. same stride and format, switching data between memory and buffer mTestCases.push_back({Float(M0, 0, 16, mCoord), Float(M1, 0, 12, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), Float(B1, 0, 12, mColor)}); // 8-9. same stride and format, offset change mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(B1, 0, 12, mColor)}); mTestCases.push_back({Float(B0, 3, 8, mCoord), Float(B1, 4, 12, mColor)}); // 10-11. unaligned buffer data mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(B0, 1, 13, mColor)}); mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(B1, 1, 13, mColor)}); // 12-15. byte/short mTestCases.push_back({SByte(M0, 0, 20, mCoord), UByte(M0, 10, 20, mColor)}); mTestCases.push_back({SShort(M0, 0, 20, mCoord), UShort(M0, 8, 20, mColor)}); mTestCases.push_back({NormSByte(M0, 0, 8, mCoord), NormUByte(M0, 4, 8, mColor)}); mTestCases.push_back({NormSShort(M0, 0, 20, mCoord), NormUShort(M0, 8, 20, mColor)}); // 16. one buffer, sequential mTestCases.push_back({Fixed(B0, 0, 8, mCoord), Float(B0, 96, 12, mColor)}); // 17. one buffer, interleaved mTestCases.push_back({Fixed(B0, 0, 20, mCoord), Float(B0, 8, 20, mColor)}); // 18. memory and buffer, float and integer mTestCases.push_back({Float(M0, 0, 8, mCoord), SByte(B0, 0, 12, mColor)}); // 19. buffer and memory, unusual offset and stride mTestCases.push_back({Float(B0, 11, 13, mCoord), Float(M0, 23, 17, mColor)}); // 20-21. remaining ES3 formats if (es3) { mTestCases.push_back({SInt(M0, 0, 40, mCoord), UInt(M0, 16, 40, mColor)}); // Fails on Nexus devices (anglebug.com/42261348) if (!IsNexus5X()) mTestCases.push_back({NormSInt(M0, 0, 40, mCoord), NormUInt(M0, 16, 40, mColor)}); } } class AttributeLayoutNonIndexed : public AttributeLayoutTest { void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override { glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); } }; class AttributeLayoutMemoryIndexed : public AttributeLayoutTest { void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_SHORT, indices); } }; class AttributeLayoutBufferIndexed : public AttributeLayoutTest { void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*mIndices) * vertexCount, indices, GL_STATIC_DRAW); glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_SHORT, nullptr); } }; TEST_P(AttributeLayoutNonIndexed, Test) { Run(true); ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL()); Run(false); } TEST_P(AttributeLayoutMemoryIndexed, Test) { Run(true); ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL()); Run(false); } TEST_P(AttributeLayoutBufferIndexed, Test) { Run(true); ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL()); Run(false); } ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(AttributeLayoutNonIndexed, ES3_VULKAN() .disable(Feature::SupportsExtendedDynamicState) .disable(Feature::SupportsExtendedDynamicState2)); ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(AttributeLayoutMemoryIndexed, ES3_VULKAN() .disable(Feature::SupportsExtendedDynamicState) .disable(Feature::SupportsExtendedDynamicState2)); ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(AttributeLayoutBufferIndexed, ES3_VULKAN() .disable(Feature::SupportsExtendedDynamicState) .disable(Feature::SupportsExtendedDynamicState2)); #define STRINGIFY2(X) #X #define STRINGIFY(X) STRINGIFY2(X) // clang-format off #define VS_SHADER(ColorDataType) \ "#version 300 es\n"\ "in highp vec2 coord;\n"\ "in highp " STRINGIFY(ColorDataType) " color;\n"\ "flat out highp " STRINGIFY(ColorDataType) " vcolor;\n"\ "void main(void)\n"\ "{\n"\ " gl_Position = vec4(coord, 0, 1);\n"\ " vcolor = color;\n"\ "}\n" #define PS_SHADER(ColorDataType) \ "#version 300 es\n"\ "flat in highp " STRINGIFY(ColorDataType) " vcolor;\n"\ "out highp " STRINGIFY(ColorDataType) " outColor;\n"\ "void main(void)\n"\ "{\n"\ " outColor = vcolor;\n"\ "}\n" // clang-format on // clang-format off constexpr double kVertexData[] = { //x y rgba -1, -1, 128, 128, 93, 255, +1, -1, 128, 128, 93, 255, -1, +1, 128, 128, 93, 255, +1, +1, 128, 128, 93, 255, }; // clang-format on template ResType GetRefValue(const void *data, GLenum glType) { switch (glType) { case GL_BYTE: { const int8_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } case GL_SHORT: case GL_HALF_FLOAT: { const int16_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } case GL_INT: case GL_FIXED: { const int32_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } case GL_UNSIGNED_BYTE: { const uint8_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } case GL_UNSIGNED_SHORT: { const uint16_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } case GL_FLOAT: case GL_UNSIGNED_INT: { const uint32_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } default: { ASSERT(0); const uint32_t *p = reinterpret_cast(data); return ResType(p[0], p[1], p[2], p[3]); } } } constexpr size_t kIndexCount = 6; constexpr int kRboSize = 8; GLColor ConvertFloatToUnorm8(const GLColor32F &color32f) { float r = std::clamp(color32f.R, 0.0f, 1.0f); float g = std::clamp(color32f.G, 0.0f, 1.0f); float b = std::clamp(color32f.B, 0.0f, 1.0f); float a = std::clamp(color32f.A, 0.0f, 1.0f); return GLColor(std::round(r * 255), std::round(g * 255), std::round(b * 255), std::round(a * 255)); } void BindAttribLocation(GLuint program) { glBindAttribLocation(program, 0, "coord"); glBindAttribLocation(program, 1, "color"); } class AttributeDataTypeMismatchTest : public ANGLETest<> { public: enum VsInputDataType { FLOAT = 0, INT = 1, UNSIGNED = 2, COUNT = 3, }; protected: AttributeDataTypeMismatchTest() : mCoord(2, kVertexData, 0, 6, 4), mColor(4, kVertexData, 2, 6, 4) { setWindowWidth(128); setWindowHeight(128); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } GLuint createFbo(GLuint rbo) { GLuint fbo = 0; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); glBindFramebuffer(GL_FRAMEBUFFER, 0); return fbo; } GLuint createRbo(VsInputDataType inputDataType) { GLuint rbo = 0; glGenRenderbuffers(1, &rbo); GLenum format = GL_RGBA8; if (inputDataType == VsInputDataType::INT) { format = GL_RGBA32I; } else if (inputDataType == VsInputDataType::UNSIGNED) { format = GL_RGBA32UI; } glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, format, kRboSize, kRboSize); glBindRenderbuffer(GL_RENDERBUFFER, 0); return rbo; } void testSetUp() override { glClearColor(.2f, .2f, .2f, .0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); constexpr const char *kVS[VsInputDataType::COUNT] = { VS_SHADER(vec4), VS_SHADER(ivec4), VS_SHADER(uvec4), }; constexpr const char *kFS[VsInputDataType::COUNT] = { PS_SHADER(vec4), PS_SHADER(ivec4), PS_SHADER(uvec4), }; for (int i = VsInputDataType::FLOAT; i < VsInputDataType::COUNT; ++i) { mProgram[i] = CompileProgram(kVS[i], kFS[i], BindAttribLocation); ASSERT_NE(0u, mProgram[i]); mRbo[i] = createRbo(static_cast(i)); ASSERT_NE(0u, mRbo[i]); mFbo[i] = createFbo(mRbo[i]); ASSERT_NE(0u, mFbo[i]); } glGenBuffers(1, &mIndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(mIndices), mIndices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void GetTestCases(VsInputDataType dataType) { const bool es3 = getClientMajorVersion() >= 3; std::shared_ptr B0 = std::make_shared(); if (dataType != VsInputDataType::FLOAT) { // float and fixed. Format Float(es3); Format Fixed(es3); Format halfFloat(es3); // for UScale, SScale. Format SByte(es3); Format UByte(es3); Format SShort(es3); Format UShort(es3); // UScale32, Scale32 may emulated. testing unsigned<-->int Format SInt(es3); Format UInt(es3); mTestCases.push_back({Float(B0, 0, 12, mCoord), SByte(B0, 8, 12, mColor)}); mTestCases.push_back({Float(B0, 0, 12, mCoord), UByte(B0, 8, 12, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), SShort(B0, 8, 16, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), UShort(B0, 8, 16, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), halfFloat(B0, 8, 16, mColor)}); mTestCases.push_back({Float(B0, 0, 24, mCoord), SInt(B0, 8, 24, mColor)}); mTestCases.push_back({Float(B0, 0, 24, mCoord), UInt(B0, 8, 24, mColor)}); mTestCases.push_back({Float(B0, 0, 24, mCoord), Float(B0, 8, 24, mColor)}); // for GL_FIXED, angle may use GLfloat emulated. // mTestCases.push_back({Float(B0, 0, 24, mCoord), Fixed(B0, 8, 24, mColor)}); } else { Format Float(es3); Format SByte(es3); Format UByte(es3); Format SShort(es3); Format UShort(es3); Format SInt(es3); Format UInt(es3); mTestCases.push_back({Float(B0, 0, 12, mCoord), SByte(B0, 8, 12, mColor)}); mTestCases.push_back({Float(B0, 0, 12, mCoord), UByte(B0, 8, 12, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), SShort(B0, 8, 16, mColor)}); mTestCases.push_back({Float(B0, 0, 16, mCoord), UShort(B0, 8, 16, mColor)}); // UScale32, Scale32 may emulated. // mTestCases.push_back({Float(B0, 0, 24, mCoord), SInt(B0, 8, 24, mColor)}); // mTestCases.push_back({Float(B0, 0, 24, mCoord), UInt(B0, 8, 24, mColor)}); } } void testTearDown() override { mTestCases.clear(); for (int i = 0; i < VsInputDataType::COUNT; ++i) { glDeleteProgram(mProgram[i]); glDeleteFramebuffers(1, &mFbo[i]); glDeleteRenderbuffers(1, &mRbo[i]); } glDeleteBuffers(1, &mIndexBuffer); } GLenum GetMappedGLType(GLenum glType, VsInputDataType vsInputDataType) { switch (glType) { case GL_BYTE: return vsInputDataType != VsInputDataType::UNSIGNED ? GL_BYTE : GL_UNSIGNED_BYTE; case GL_SHORT: case GL_HALF_FLOAT: return vsInputDataType != VsInputDataType::UNSIGNED ? GL_SHORT : GL_UNSIGNED_SHORT; case GL_INT: case GL_FIXED: return vsInputDataType != VsInputDataType::UNSIGNED ? GL_INT : GL_UNSIGNED_INT; case GL_UNSIGNED_BYTE: return vsInputDataType != VsInputDataType::INT ? GL_UNSIGNED_BYTE : GL_BYTE; case GL_UNSIGNED_SHORT: return vsInputDataType != VsInputDataType::INT ? GL_UNSIGNED_SHORT : GL_SHORT; case GL_FLOAT: case GL_UNSIGNED_INT: return vsInputDataType != VsInputDataType::INT ? GL_UNSIGNED_INT : GL_INT; default: ASSERT(0); return vsInputDataType != VsInputDataType::INT ? GL_UNSIGNED_INT : GL_INT; } } void Run(VsInputDataType dataType) { GetTestCases(dataType); ASSERT(dataType < VsInputDataType::COUNT); glBindFramebuffer(GL_FRAMEBUFFER, mFbo[dataType]); glViewport(0, 0, kRboSize, kRboSize); glUseProgram(mProgram[dataType]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); for (unsigned i = 0; i < mTestCases.size(); ++i) { if (mTestCases[i].size() == 0) continue; ASSERT(mTestCases[i].size() == 2); PrepareTestCase(mTestCases[i]); EXPECT_GL_NO_ERROR(); GLint iClearValue[] = {0, 0, 0, 1}; GLfloat fClearValue[] = {1.0f, 0.0f, 0.0f, 1.0f}; switch (dataType) { case VsInputDataType::FLOAT: glClearBufferfv(GL_COLOR, 0, fClearValue); break; case VsInputDataType::INT: glClearBufferiv(GL_COLOR, 0, iClearValue); break; case VsInputDataType::UNSIGNED: glClearBufferuiv(GL_COLOR, 0, reinterpret_cast(iClearValue)); break; default: ASSERT(0); } EXPECT_GL_NO_ERROR(); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); EXPECT_GL_NO_ERROR(); std::shared_ptr container = mTestCases[i][1].mContainer; size_t offset = mTestCases[i][1].mOffset; GLenum glType = GetMappedGLType(mTestCases[i][1].mGLType, dataType); switch (dataType) { case VsInputDataType::FLOAT: EXPECT_PIXEL_COLOR_EQ(0, 0, ConvertFloatToUnorm8(GetRefValue( container->getDestination(offset), glType))); break; case VsInputDataType::INT: EXPECT_PIXEL_RECT32I_EQ( 0, 0, 1, 1, GetRefValue(container->getDestination(offset), glType)); break; case VsInputDataType::UNSIGNED: EXPECT_PIXEL_RECT32UI_EQ( 0, 0, 1, 1, GetRefValue(container->getDestination(offset), glType)); break; default: ASSERT(0); } } mTestCases.clear(); } static const GLushort mIndices[kIndexCount]; GLuint mProgram[VsInputDataType::COUNT]; GLuint mFbo[VsInputDataType::COUNT]; GLuint mRbo[VsInputDataType::COUNT]; GLuint mIndexBuffer; std::vector mTestCases; VertexData mCoord; VertexData mColor; }; const GLushort AttributeDataTypeMismatchTest::mIndices[kIndexCount] = {0, 1, 2, 2, 1, 3}; // Test Attribute input data type mismatch with vertex shader input. // Change the attribute input data type to vertex shader input data type. TEST_P(AttributeDataTypeMismatchTest, Test) { // At some device. UScale and Scale are emulated. // Restrict tests running at nvidia device only. ANGLE_SKIP_TEST_IF(!IsVulkan() || !IsNVIDIA()); Run(VsInputDataType::FLOAT); Run(VsInputDataType::INT); Run(VsInputDataType::UNSIGNED); } ANGLE_INSTANTIATE_TEST_ES3(AttributeDataTypeMismatchTest); } // anonymous namespace