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__anond02276220111::Attrib112 void openContainer(void) const { mContainer->open(); }
113
fillContainer__anond02276220111::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__anond02276220111::Attrib139 void closeContainer(void) const { mContainer->close(); }
140
enable__anond02276220111::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__anond02276220111::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 // Flaky on Linux SwANGLE http://anglebug.com/4502
458 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
459
460 Run(true);
461 ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL());
462 Run(false);
463 }
464
TEST_P(AttributeLayoutMemoryIndexed,Test)465 TEST_P(AttributeLayoutMemoryIndexed, Test)
466 {
467 // Flaky on Linux SwANGLE http://anglebug.com/4502
468 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
469
470 Run(true);
471 ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && (IsOpenGL() || IsD3D11_FL93()));
472 Run(false);
473 }
474
TEST_P(AttributeLayoutBufferIndexed,Test)475 TEST_P(AttributeLayoutBufferIndexed, Test)
476 {
477 // Flaky on Linux SwANGLE http://anglebug.com/4502
478 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
479
480 Run(true);
481 ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && (IsOpenGL() || IsD3D11_FL93()));
482 Run(false);
483 }
484
485 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutNonIndexed);
486 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutMemoryIndexed);
487 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(AttributeLayoutBufferIndexed);
488
489 } // anonymous namespace
490