1 //
2 // Copyright 2018 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // AttributeLayoutTest:
7 //   Test various layouts of vertex attribute data:
8 //   - in memory, in buffer object, or combination of both
9 //   - sequential or interleaved
10 //   - various combinations of data types
11 
12 #include <vector>
13 
14 #include "test_utils/ANGLETest.h"
15 #include "test_utils/gl_raii.h"
16 
17 using namespace angle;
18 
19 namespace
20 {
21 
22 // Test will draw these four triangles.
23 // clang-format off
24 constexpr double kTriangleData[] = {
25     // xy       rgb
26     0,0,        1,1,0,
27     -1,+1,      1,1,0,
28     +1,+1,      1,1,0,
29 
30     0,0,        0,1,0,
31     +1,+1,      0,1,0,
32     +1,-1,      0,1,0,
33 
34     0,0,        0,1,1,
35     +1,-1,      0,1,1,
36     -1,-1,      0,1,1,
37 
38     0,0,        1,0,1,
39     -1,-1,      1,0,1,
40     -1,+1,      1,0,1,
41 };
42 // clang-format on
43 
44 constexpr size_t kNumVertices = ArraySize(kTriangleData) / 5;
45 
46 // Vertex data source description.
47 class VertexData
48 {
49   public:
VertexData(int dimension,const double * data,unsigned offset,unsigned stride)50     VertexData(int dimension, const double *data, unsigned offset, unsigned stride)
51         : mDimension(dimension), mData(data), mOffset(offset), mStride(stride)
52     {}
getDimension() const53     int getDimension() const { return mDimension; }
getValue(unsigned vertexNumber,int component) const54     double getValue(unsigned vertexNumber, int component) const
55     {
56         return mData[mOffset + mStride * vertexNumber + component];
57     }
58 
59   private:
60     int mDimension;
61     const double *mData;
62     // offset and stride in doubles
63     unsigned mOffset;
64     unsigned mStride;
65 };
66 
67 // A container for one or more vertex attributes.
68 class Container
69 {
70   public:
71     static constexpr size_t kSize = 1024;
72 
open(void)73     void open(void) { memset(mMemory, 0xff, kSize); }
getDestination(size_t offset)74     void *getDestination(size_t offset) { return mMemory + offset; }
close(void)75     virtual void close(void) {}
~Container()76     virtual ~Container() {}
77     virtual const char *getAddress() = 0;
78     virtual GLuint getBuffer()       = 0;
79 
80   protected:
81     char mMemory[kSize];
82 };
83 
84 // Vertex attribute data in client memory.
85 class Memory : public Container
86 {
87   public:
getAddress()88     const char *getAddress() override { return mMemory; }
getBuffer()89     GLuint getBuffer() override { return 0; }
90 };
91 
92 // Vertex attribute data in buffer object.
93 class Buffer : public Container
94 {
95   public:
close(void)96     void close(void) override
97     {
98         glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
99         glBufferData(GL_ARRAY_BUFFER, sizeof(mMemory), mMemory, GL_STATIC_DRAW);
100     }
101 
getAddress()102     const char *getAddress() override { return nullptr; }
getBuffer()103     GLuint getBuffer() override { return mBuffer; }
104 
105   protected:
106     GLBuffer mBuffer;
107 };
108 
109 // Encapsulate the storage, layout, format and data of a vertex attribute.
110 struct Attrib
111 {
openContainer__anon24fb52440111::Attrib112     void openContainer(void) const { mContainer->open(); }
113 
fillContainer__anon24fb52440111::Attrib114     void fillContainer(void) const
115     {
116         for (unsigned i = 0; i < kNumVertices; ++i)
117         {
118             for (int j = 0; j < mData.getDimension(); ++j)
119             {
120                 size_t destOffset = mOffset + mStride * i + mCTypeSize * j;
121                 if (destOffset + mCTypeSize > Container::kSize)
122                     FAIL() << "test case does not fit container";
123 
124                 double value = mData.getValue(i, j);
125                 if (mGLType == GL_FIXED)
126                     value *= 1 << 16;
127                 else if (mNormalized)
128                 {
129                     if (value < mMinIn || value > mMaxIn)
130                         FAIL() << "test data does not fit format";
131                     value = (value - mMinIn) * mScale + mMinOut;
132                 }
133 
134                 mStore(value, mContainer->getDestination(destOffset));
135             }
136         }
137     }
138 
closeContainer__anon24fb52440111::Attrib139     void closeContainer(void) const { mContainer->close(); }
140 
enable__anon24fb52440111::Attrib141     void enable(unsigned index) const
142     {
143         glBindBuffer(GL_ARRAY_BUFFER, mContainer->getBuffer());
144         glVertexAttribPointer(index, mData.getDimension(), mGLType, mNormalized, mStride,
145                               mContainer->getAddress() + mOffset);
146         EXPECT_GL_NO_ERROR();
147         glEnableVertexAttribArray(index);
148     }
149 
inClientMemory__anon24fb52440111::Attrib150     bool inClientMemory(void) const { return mContainer->getAddress() != nullptr; }
151 
152     std::shared_ptr<Container> mContainer;
153     unsigned mOffset;
154     unsigned mStride;
155     const VertexData &mData;
156     void (*mStore)(double value, void *dest);
157     GLenum mGLType;
158     GLboolean mNormalized;
159     size_t mCTypeSize;
160     double mMinIn;
161     double mMaxIn;
162     double mMinOut;
163     double mScale;
164 };
165 
166 // Change type and store.
167 template <class T>
Store(double value,void * dest)168 void Store(double value, void *dest)
169 {
170     T v = static_cast<T>(value);
171     memcpy(dest, &v, sizeof(v));
172 }
173 
174 // Function object that makes Attrib structs according to a vertex format.
175 template <class CType, GLenum GLType, bool Normalized>
176 class Format
177 {
178     static_assert(!(Normalized && GLType == GL_FLOAT), "Normalized float does not make sense.");
179 
180   public:
Format(bool es3)181     Format(bool es3) : mES3(es3) {}
182 
operator ()(std::shared_ptr<Container> container,unsigned offset,unsigned stride,const VertexData & data) const183     Attrib operator()(std::shared_ptr<Container> container,
184                       unsigned offset,
185                       unsigned stride,
186                       const VertexData &data) const
187     {
188         double minIn    = 0;
189         double maxIn    = 1;
190         double minOut   = std::numeric_limits<CType>::min();
191         double rangeOut = std::numeric_limits<CType>::max() - minOut;
192 
193         if (std::is_signed<CType>::value)
194         {
195             minIn = -1;
196             maxIn = +1;
197             if (mES3)
198             {
199                 minOut += 1;
200                 rangeOut -= 1;
201             }
202         }
203 
204         return {
205             container,  offset,        stride, data,  Store<CType>, GLType,
206             Normalized, sizeof(CType), minIn,  maxIn, minOut,       rangeOut / (maxIn - minIn),
207         };
208     }
209 
210   protected:
211     const bool mES3;
212 };
213 
214 typedef std::vector<Attrib> TestCase;
215 
PrepareTestCase(const TestCase & tc)216 void PrepareTestCase(const TestCase &tc)
217 {
218     for (const Attrib &a : tc)
219         a.openContainer();
220     for (const Attrib &a : tc)
221         a.fillContainer();
222     for (const Attrib &a : tc)
223         a.closeContainer();
224     unsigned i = 0;
225     for (const Attrib &a : tc)
226         a.enable(i++);
227 }
228 
229 class AttributeLayoutTest : public ANGLETest
230 {
231   protected:
AttributeLayoutTest()232     AttributeLayoutTest()
233         : mProgram(0), mCoord(2, kTriangleData, 0, 5), mColor(3, kTriangleData, 2, 5)
234     {
235         setWindowWidth(128);
236         setWindowHeight(128);
237         setConfigRedBits(8);
238         setConfigGreenBits(8);
239         setConfigBlueBits(8);
240         setConfigAlphaBits(8);
241     }
242 
243     void GetTestCases(void);
244 
testSetUp()245     void testSetUp() override
246     {
247         glClearColor(.2f, .2f, .2f, .0f);
248         glClear(GL_COLOR_BUFFER_BIT);
249 
250         glDisable(GL_DEPTH_TEST);
251 
252         constexpr char kVS[] =
253             "attribute mediump vec2 coord;\n"
254             "attribute mediump vec3 color;\n"
255             "varying mediump vec3 vcolor;\n"
256             "void main(void)\n"
257             "{\n"
258             "    gl_Position = vec4(coord, 0, 1);\n"
259             "    vcolor = color;\n"
260             "}\n";
261 
262         constexpr char kFS[] =
263             "varying mediump vec3 vcolor;\n"
264             "void main(void)\n"
265             "{\n"
266             "    gl_FragColor = vec4(vcolor, 0);\n"
267             "}\n";
268 
269         mProgram = CompileProgram(kVS, kFS);
270         ASSERT_NE(0u, mProgram);
271         glUseProgram(mProgram);
272 
273         glGenBuffers(1, &mIndexBuffer);
274 
275         GetTestCases();
276     }
277 
testTearDown()278     void testTearDown() override
279     {
280         mTestCases.clear();
281         glDeleteProgram(mProgram);
282         glDeleteBuffers(1, &mIndexBuffer);
283     }
284 
Skip(const TestCase &)285     virtual bool Skip(const TestCase &) { return false; }
286     virtual void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) = 0;
287 
Run(bool drawFirstTriangle)288     void Run(bool drawFirstTriangle)
289     {
290         glViewport(0, 0, getWindowWidth(), getWindowHeight());
291         glUseProgram(mProgram);
292 
293         for (unsigned i = 0; i < mTestCases.size(); ++i)
294         {
295             if (mTestCases[i].size() == 0 || Skip(mTestCases[i]))
296                 continue;
297 
298             PrepareTestCase(mTestCases[i]);
299 
300             glClear(GL_COLOR_BUFFER_BIT);
301 
302             std::string testCase;
303             if (drawFirstTriangle)
304             {
305                 Draw(0, kNumVertices, mIndices);
306                 testCase = "draw";
307             }
308             else
309             {
310                 Draw(3, kNumVertices - 3, mIndices + 3);
311                 testCase = "skip";
312             }
313 
314             testCase += " first triangle case ";
315             int w = getWindowWidth() / 4;
316             int h = getWindowHeight() / 4;
317             if (drawFirstTriangle)
318             {
319                 EXPECT_PIXEL_EQ(w * 2, h * 3, 255, 255, 0, 0) << testCase << i;
320             }
321             else
322             {
323                 EXPECT_PIXEL_EQ(w * 2, h * 3, 51, 51, 51, 0) << testCase << i;
324             }
325             EXPECT_PIXEL_EQ(w * 3, h * 2, 0, 255, 0, 0) << testCase << i;
326             EXPECT_PIXEL_EQ(w * 2, h * 1, 0, 255, 255, 0) << testCase << i;
327             EXPECT_PIXEL_EQ(w * 1, h * 2, 255, 0, 255, 0) << testCase << i;
328         }
329     }
330 
331     static const GLushort mIndices[kNumVertices];
332 
333     GLuint mProgram;
334     GLuint mIndexBuffer;
335 
336     std::vector<TestCase> mTestCases;
337 
338     VertexData mCoord;
339     VertexData mColor;
340 };
341 const GLushort AttributeLayoutTest::mIndices[kNumVertices] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
342 
GetTestCases(void)343 void AttributeLayoutTest::GetTestCases(void)
344 {
345     const bool es3 = getClientMajorVersion() >= 3;
346 
347     Format<GLfloat, GL_FLOAT, false> Float(es3);
348     Format<GLint, GL_FIXED, false> Fixed(es3);
349 
350     Format<GLbyte, GL_BYTE, false> SByte(es3);
351     Format<GLubyte, GL_UNSIGNED_BYTE, false> UByte(es3);
352     Format<GLshort, GL_SHORT, false> SShort(es3);
353     Format<GLushort, GL_UNSIGNED_SHORT, false> UShort(es3);
354     Format<GLint, GL_INT, false> SInt(es3);
355     Format<GLuint, GL_UNSIGNED_INT, false> UInt(es3);
356 
357     Format<GLbyte, GL_BYTE, true> NormSByte(es3);
358     Format<GLubyte, GL_UNSIGNED_BYTE, true> NormUByte(es3);
359     Format<GLshort, GL_SHORT, true> NormSShort(es3);
360     Format<GLushort, GL_UNSIGNED_SHORT, true> NormUShort(es3);
361     Format<GLint, GL_INT, true> NormSInt(es3);
362     Format<GLuint, GL_UNSIGNED_INT, true> NormUInt(es3);
363 
364     std::shared_ptr<Container> M0 = std::make_shared<Memory>();
365     std::shared_ptr<Container> M1 = std::make_shared<Memory>();
366     std::shared_ptr<Container> B0 = std::make_shared<Buffer>();
367     std::shared_ptr<Container> B1 = std::make_shared<Buffer>();
368 
369     // 0. two buffers
370     mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(B1, 0, 12, mColor)});
371 
372     // 1. two memory
373     mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(M1, 0, 12, mColor)});
374 
375     // 2. one memory, sequential
376     mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(M0, 96, 12, mColor)});
377 
378     // 3. one memory, interleaved
379     mTestCases.push_back({Float(M0, 0, 20, mCoord), Float(M0, 8, 20, mColor)});
380 
381     // 4. buffer and memory
382     mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(M0, 0, 12, mColor)});
383 
384     // 5. stride != size
385     mTestCases.push_back({Float(B0, 0, 16, mCoord), Float(B1, 0, 12, mColor)});
386 
387     // 6-7. same stride and format, switching data between memory and buffer
388     mTestCases.push_back({Float(M0, 0, 16, mCoord), Float(M1, 0, 12, mColor)});
389     mTestCases.push_back({Float(B0, 0, 16, mCoord), Float(B1, 0, 12, mColor)});
390 
391     // 8-9. same stride and format, offset change
392     mTestCases.push_back({Float(B0, 0, 8, mCoord), Float(B1, 0, 12, mColor)});
393     mTestCases.push_back({Float(B0, 3, 8, mCoord), Float(B1, 4, 12, mColor)});
394 
395     // 10-11. unaligned buffer data
396     mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(B0, 1, 13, mColor)});
397     mTestCases.push_back({Float(M0, 0, 8, mCoord), Float(B1, 1, 13, mColor)});
398 
399     // 12-15. byte/short
400     mTestCases.push_back({SByte(M0, 0, 20, mCoord), UByte(M0, 10, 20, mColor)});
401     mTestCases.push_back({SShort(M0, 0, 20, mCoord), UShort(M0, 8, 20, mColor)});
402     mTestCases.push_back({NormSByte(M0, 0, 8, mCoord), NormUByte(M0, 4, 8, mColor)});
403     mTestCases.push_back({NormSShort(M0, 0, 20, mCoord), NormUShort(M0, 8, 20, mColor)});
404 
405     // 16. one buffer, sequential
406     mTestCases.push_back({Fixed(B0, 0, 8, mCoord), Float(B0, 96, 12, mColor)});
407 
408     // 17. one buffer, interleaved
409     mTestCases.push_back({Fixed(B0, 0, 20, mCoord), Float(B0, 8, 20, mColor)});
410 
411     // 18. memory and buffer, float and integer
412     mTestCases.push_back({Float(M0, 0, 8, mCoord), SByte(B0, 0, 12, mColor)});
413 
414     // 19. buffer and memory, unusual offset and stride
415     mTestCases.push_back({Float(B0, 11, 13, mCoord), Float(M0, 23, 17, mColor)});
416 
417     // 20-21. remaining ES3 formats
418     if (es3)
419     {
420         mTestCases.push_back({SInt(M0, 0, 40, mCoord), UInt(M0, 16, 40, mColor)});
421         // Fails on Nexus devices (anglebug.com/2641)
422         if (!IsNexus5X() && !IsNexus6P())
423             mTestCases.push_back({NormSInt(M0, 0, 40, mCoord), NormUInt(M0, 16, 40, mColor)});
424     }
425 }
426 
427 class AttributeLayoutNonIndexed : public AttributeLayoutTest
428 {
Draw(int firstVertex,unsigned vertexCount,const GLushort * indices)429     void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override
430     {
431         glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
432     }
433 };
434 
435 class AttributeLayoutMemoryIndexed : public AttributeLayoutTest
436 {
Draw(int firstVertex,unsigned vertexCount,const GLushort * indices)437     void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override
438     {
439         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
440         glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_SHORT, indices);
441     }
442 };
443 
444 class AttributeLayoutBufferIndexed : public AttributeLayoutTest
445 {
Draw(int firstVertex,unsigned vertexCount,const GLushort * indices)446     void Draw(int firstVertex, unsigned vertexCount, const GLushort *indices) override
447     {
448         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
449         glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*mIndices) * vertexCount, indices,
450                      GL_STATIC_DRAW);
451         glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_SHORT, nullptr);
452     }
453 };
454 
TEST_P(AttributeLayoutNonIndexed,Test)455 TEST_P(AttributeLayoutNonIndexed, Test)
456 {
457     Run(true);
458     ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL());
459     Run(false);
460 }
461 
TEST_P(AttributeLayoutMemoryIndexed,Test)462 TEST_P(AttributeLayoutMemoryIndexed, Test)
463 {
464     Run(true);
465     ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && (IsOpenGL() || IsD3D11_FL93()));
466     Run(false);
467 }
468 
TEST_P(AttributeLayoutBufferIndexed,Test)469 TEST_P(AttributeLayoutBufferIndexed, Test)
470 {
471     Run(true);
472     ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && (IsOpenGL() || IsD3D11_FL93()));
473     Run(false);
474 }
475 
476 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutNonIndexed);
477 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutMemoryIndexed);
478 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutBufferIndexed);
479 
480 }  // anonymous namespace
481