1 //
2 // Copyright 2015 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 
7 #include "test_utils/ANGLETest.h"
8 
9 using namespace angle;
10 
11 class InstancingTest : public ANGLETest
12 {
13   protected:
InstancingTest()14     InstancingTest() : mProgram(0), mVertexBuffer(0)
15     {
16         setWindowWidth(256);
17         setWindowHeight(256);
18         setConfigRedBits(8);
19         setConfigGreenBits(8);
20         setConfigBlueBits(8);
21         setConfigAlphaBits(8);
22     }
23 
~InstancingTest()24     ~InstancingTest() override
25     {
26         glDeleteBuffers(1, &mVertexBuffer);
27         glDeleteProgram(mProgram);
28     }
29 
SetUp()30     void SetUp() override
31     {
32         ANGLETest::SetUp();
33 
34         mVertexAttribDivisorANGLE = NULL;
35         mDrawArraysInstancedANGLE = NULL;
36         mDrawElementsInstancedANGLE = NULL;
37 
38         char *extensionString = (char*)glGetString(GL_EXTENSIONS);
39         if (strstr(extensionString, "GL_ANGLE_instanced_arrays"))
40         {
41             mVertexAttribDivisorANGLE = (PFNGLVERTEXATTRIBDIVISORANGLEPROC)eglGetProcAddress("glVertexAttribDivisorANGLE");
42             mDrawArraysInstancedANGLE = (PFNGLDRAWARRAYSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawArraysInstancedANGLE");
43             mDrawElementsInstancedANGLE = (PFNGLDRAWELEMENTSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawElementsInstancedANGLE");
44         }
45 
46         ASSERT_TRUE(mVertexAttribDivisorANGLE != NULL);
47         ASSERT_TRUE(mDrawArraysInstancedANGLE != NULL);
48         ASSERT_TRUE(mDrawElementsInstancedANGLE != NULL);
49 
50         // Initialize the vertex and index vectors
51         GLfloat qvertex1[3] = {-quadRadius,  quadRadius, 0.0f};
52         GLfloat qvertex2[3] = {-quadRadius, -quadRadius, 0.0f};
53         GLfloat qvertex3[3] = { quadRadius, -quadRadius, 0.0f};
54         GLfloat qvertex4[3] = { quadRadius,  quadRadius, 0.0f};
55         mQuadVertices.insert(mQuadVertices.end(), qvertex1, qvertex1 + 3);
56         mQuadVertices.insert(mQuadVertices.end(), qvertex2, qvertex2 + 3);
57         mQuadVertices.insert(mQuadVertices.end(), qvertex3, qvertex3 + 3);
58         mQuadVertices.insert(mQuadVertices.end(), qvertex4, qvertex4 + 3);
59 
60         GLfloat coord1[2] = {0.0f, 0.0f};
61         GLfloat coord2[2] = {0.0f, 1.0f};
62         GLfloat coord3[2] = {1.0f, 1.0f};
63         GLfloat coord4[2] = {1.0f, 0.0f};
64         mTexcoords.insert(mTexcoords.end(), coord1, coord1 + 2);
65         mTexcoords.insert(mTexcoords.end(), coord2, coord2 + 2);
66         mTexcoords.insert(mTexcoords.end(), coord3, coord3 + 2);
67         mTexcoords.insert(mTexcoords.end(), coord4, coord4 + 2);
68 
69         mIndices.push_back(0);
70         mIndices.push_back(1);
71         mIndices.push_back(2);
72         mIndices.push_back(0);
73         mIndices.push_back(2);
74         mIndices.push_back(3);
75 
76         for (size_t vertexIndex = 0; vertexIndex < 6; ++vertexIndex)
77         {
78             mNonIndexedVertices.insert(mNonIndexedVertices.end(),
79                                        mQuadVertices.begin() + mIndices[vertexIndex] * 3,
80                                        mQuadVertices.begin() + mIndices[vertexIndex] * 3 + 3);
81         }
82 
83         for (size_t vertexIndex = 0; vertexIndex < 6; ++vertexIndex)
84         {
85             mNonIndexedVertices.insert(mNonIndexedVertices.end(),
86                                        mQuadVertices.begin() + mIndices[vertexIndex] * 3,
87                                        mQuadVertices.begin() + mIndices[vertexIndex] * 3 + 3);
88         }
89 
90         // Tile a 2x2 grid of the tiles
91         for (float y = -1.0f + quadRadius; y < 1.0f - quadRadius; y += quadRadius * 3)
92         {
93             for (float x = -1.0f + quadRadius; x < 1.0f - quadRadius; x += quadRadius * 3)
94             {
95                 GLfloat instance[3] = {x + quadRadius, y + quadRadius, 0.0f};
96                 mInstances.insert(mInstances.end(), instance, instance + 3);
97             }
98         }
99 
100         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
101 
102         glGenBuffers(1, &mVertexBuffer);
103 
104         ASSERT_GL_NO_ERROR();
105     }
106 
setupDrawArraysTest(const std::string & vs)107     void setupDrawArraysTest(const std::string &vs)
108     {
109         const std::string fs = SHADER_SOURCE
110         (
111             precision mediump float;
112             void main()
113             {
114                 gl_FragColor = vec4(1.0, 0, 0, 1.0);
115             }
116         );
117 
118         mProgram = CompileProgram(vs, fs);
119         ASSERT_NE(0u, mProgram);
120 
121         // Set the viewport
122         glViewport(0, 0, getWindowWidth(), getWindowHeight());
123 
124         // Clear the color buffer
125         glClear(GL_COLOR_BUFFER_BIT);
126 
127         // Use the program object
128         glUseProgram(mProgram);
129     }
130 
setupInstancedPointsTest()131     void setupInstancedPointsTest()
132     {
133         mIndices.clear();
134         mIndices.push_back(0);
135         mIndices.push_back(1);
136         mIndices.push_back(2);
137         mIndices.push_back(3);
138 
139         // clang-format off
140         const std::string vs = SHADER_SOURCE
141         (
142             attribute vec3 a_position;
143             attribute vec3 a_instancePos;
144             void main()
145             {
146                 gl_Position  = vec4(a_position.xyz, 1.0);
147                 gl_Position  = vec4(a_instancePos.xyz, 1.0);
148                 gl_PointSize = 6.0;
149             }
150         );
151 
152         const std::string fs = SHADER_SOURCE
153         (
154             precision mediump float;
155             void main()
156             {
157                 gl_FragColor = vec4(1.0, 0, 0, 1.0);
158             }
159         );
160         // clang-format on
161 
162         mProgram = CompileProgram(vs, fs);
163         ASSERT_NE(0u, mProgram);
164 
165         // Set the viewport
166         glViewport(0, 0, getWindowWidth(), getWindowHeight());
167 
168         // Clear the color buffer
169         glClear(GL_COLOR_BUFFER_BIT);
170 
171         // Use the program object
172         glUseProgram(mProgram);
173     }
174 
runDrawArraysTest(GLint first,GLsizei count,GLsizei instanceCount,float * offset)175     void runDrawArraysTest(GLint first, GLsizei count, GLsizei instanceCount, float *offset)
176     {
177         glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
178         glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0], GL_STATIC_DRAW);
179         glBindBuffer(GL_ARRAY_BUFFER, 0);
180 
181         // Get the attribute locations
182         GLint positionLoc    = glGetAttribLocation(mProgram, "a_position");
183         GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos");
184 
185         // Load the vertex position
186         glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, mNonIndexedVertices.data());
187         glEnableVertexAttribArray(positionLoc);
188 
189         // Load the instance position
190         glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
191         glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
192         glBindBuffer(GL_ARRAY_BUFFER, 0);
193         glEnableVertexAttribArray(instancePosLoc);
194 
195         // Enable instancing
196         mVertexAttribDivisorANGLE(instancePosLoc, 1);
197 
198         // Offset
199         GLint uniformLoc = glGetUniformLocation(mProgram, "u_offset");
200         ASSERT_NE(uniformLoc, -1);
201         glUniform3fv(uniformLoc, 1, offset);
202 
203         // Do the instanced draw
204         mDrawArraysInstancedANGLE(GL_TRIANGLES, first, count, instanceCount);
205 
206         ASSERT_GL_NO_ERROR();
207     }
208 
runDrawElementsTest(std::string vs,bool shouldAttribZeroBeInstanced)209     virtual void runDrawElementsTest(std::string vs, bool shouldAttribZeroBeInstanced)
210     {
211         const std::string fs = SHADER_SOURCE
212         (
213             precision mediump float;
214             void main()
215             {
216                 gl_FragColor = vec4(1.0, 0, 0, 1.0);
217             }
218         );
219 
220         GLuint program = CompileProgram(vs, fs);
221         ASSERT_NE(program, 0u);
222 
223         // Get the attribute locations
224         GLint positionLoc = glGetAttribLocation(program, "a_position");
225         GLint instancePosLoc = glGetAttribLocation(program, "a_instancePos");
226 
227         // If this ASSERT fails then the vertex shader code should be refactored
228         ASSERT_EQ(shouldAttribZeroBeInstanced, (instancePosLoc == 0));
229 
230         // Set the viewport
231         glViewport(0, 0, getWindowWidth(), getWindowHeight());
232 
233         // Clear the color buffer
234         glClear(GL_COLOR_BUFFER_BIT);
235 
236         // Use the program object
237         glUseProgram(program);
238 
239         // Load the vertex position
240         glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, mQuadVertices.data());
241         glEnableVertexAttribArray(positionLoc);
242 
243         // Load the instance position
244         glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, mInstances.data());
245         glEnableVertexAttribArray(instancePosLoc);
246 
247         // Enable instancing
248         mVertexAttribDivisorANGLE(instancePosLoc, 1);
249 
250         // Do the instanced draw
251         mDrawElementsInstancedANGLE(GL_TRIANGLES, static_cast<GLsizei>(mIndices.size()),
252                                     GL_UNSIGNED_SHORT, mIndices.data(),
253                                     static_cast<GLsizei>(mInstances.size()) / 3);
254 
255         ASSERT_GL_NO_ERROR();
256 
257         checkQuads();
258     }
259 
checkQuads()260     void checkQuads()
261     {
262         // Check that various pixels are the expected color.
263         for (unsigned int quadIndex = 0; quadIndex < 4; ++quadIndex)
264         {
265             unsigned int baseOffset = quadIndex * 3;
266 
267             int quadx = static_cast<int>(((mInstances[baseOffset + 0]) * 0.5f + 0.5f) * getWindowWidth());
268             int quady = static_cast<int>(((mInstances[baseOffset + 1]) * 0.5f + 0.5f) * getWindowHeight());
269 
270             EXPECT_PIXEL_EQ(quadx, quady, 255, 0, 0, 255);
271         }
272     }
273 
274     // Loaded entry points
275     PFNGLVERTEXATTRIBDIVISORANGLEPROC mVertexAttribDivisorANGLE;
276     PFNGLDRAWARRAYSINSTANCEDANGLEPROC mDrawArraysInstancedANGLE;
277     PFNGLDRAWELEMENTSINSTANCEDANGLEPROC mDrawElementsInstancedANGLE;
278 
279     // Vertex data
280     std::vector<GLfloat> mQuadVertices;
281     std::vector<GLfloat> mNonIndexedVertices;
282     std::vector<GLfloat> mTexcoords;
283     std::vector<GLfloat> mInstances;
284     std::vector<GLushort> mIndices;
285 
286     const GLfloat quadRadius = 0.30f;
287 
288     GLuint mProgram;
289     GLuint mVertexBuffer;
290 };
291 
292 class InstancingTestAllConfigs : public InstancingTest
293 {
294   protected:
InstancingTestAllConfigs()295     InstancingTestAllConfigs() {}
296 };
297 
298 class InstancingTestNo9_3 : public InstancingTest
299 {
300   protected:
InstancingTestNo9_3()301     InstancingTestNo9_3() {}
302 };
303 
304 class InstancingTestPoints : public InstancingTest
305 {
306   protected:
InstancingTestPoints()307     InstancingTestPoints() {}
308 };
309 
310 // This test uses a vertex shader with the first attribute (attribute zero) instanced.
311 // On D3D9 and D3D11 FL9_3, this triggers a special codepath that rearranges the input layout sent to D3D,
312 // to ensure that slot/stream zero of the input layout doesn't contain per-instance data.
TEST_P(InstancingTestAllConfigs,AttributeZeroInstanced)313 TEST_P(InstancingTestAllConfigs, AttributeZeroInstanced)
314 {
315     const std::string vs = SHADER_SOURCE
316     (
317         attribute vec3 a_instancePos;
318         attribute vec3 a_position;
319         void main()
320         {
321             gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0);
322         }
323     );
324 
325     runDrawElementsTest(vs, true);
326 }
327 
328 // Same as AttributeZeroInstanced, but attribute zero is not instanced.
329 // This ensures the general instancing codepath (i.e. without rearranging the input layout) works as expected.
TEST_P(InstancingTestAllConfigs,AttributeZeroNotInstanced)330 TEST_P(InstancingTestAllConfigs, AttributeZeroNotInstanced)
331 {
332     const std::string vs = SHADER_SOURCE
333     (
334         attribute vec3 a_position;
335         attribute vec3 a_instancePos;
336         void main()
337         {
338             gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0);
339         }
340     );
341 
342     runDrawElementsTest(vs, false);
343 }
344 
345 // Tests that the "first" parameter to glDrawArraysInstancedANGLE is only an offset into
346 // the non-instanced vertex attributes.
TEST_P(InstancingTestNo9_3,DrawArraysWithOffset)347 TEST_P(InstancingTestNo9_3, DrawArraysWithOffset)
348 {
349     const std::string vs = SHADER_SOURCE
350     (
351         attribute vec3 a_position;
352         attribute vec3 a_instancePos;
353         uniform vec3 u_offset;
354         void main()
355         {
356             gl_Position = vec4(a_position.xyz + a_instancePos.xyz + u_offset, 1.0);
357         }
358     );
359 
360     setupDrawArraysTest(vs);
361 
362     float offset1[3] = { 0, 0, 0 };
363     runDrawArraysTest(0, 6, 2, offset1);
364 
365     float offset2[3] = { 0.0f, 1.0f, 0 };
366     runDrawArraysTest(6, 6, 2, offset2);
367 
368     checkQuads();
369 }
370 
371 // This test verifies instancing with GL_POINTS with glDrawArraysInstanced works.
372 // On D3D11 FL9_3, this triggers a special codepath that emulates instanced points rendering.
TEST_P(InstancingTestPoints,DrawArrays)373 TEST_P(InstancingTestPoints, DrawArrays)
374 {
375     // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details
376     // On Win7, the D3D SDK Layers emits a false warning for these tests.
377     // This doesn't occur on Windows 10 (Version 1511) though.
378     ignoreD3D11SDKLayersWarnings();
379 
380     setupInstancedPointsTest();
381 
382     glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
383     glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0],
384                  GL_STATIC_DRAW);
385     glBindBuffer(GL_ARRAY_BUFFER, 0);
386 
387     // Get the attribute locations
388     GLint positionLoc    = glGetAttribLocation(mProgram, "a_position");
389     GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos");
390 
391     // Load the vertex position
392     GLfloat pos[3] = {0, 0, 0};
393     glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, pos);
394     glEnableVertexAttribArray(positionLoc);
395 
396     // Load the instance position
397     glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
398     glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
399     glBindBuffer(GL_ARRAY_BUFFER, 0);
400     glEnableVertexAttribArray(instancePosLoc);
401 
402     // Enable instancing
403     mVertexAttribDivisorANGLE(instancePosLoc, 1);
404 
405     // Do the instanced draw
406     mDrawArraysInstancedANGLE(GL_POINTS, 0, 1, static_cast<GLsizei>(mInstances.size()) / 3);
407 
408     ASSERT_GL_NO_ERROR();
409 
410     checkQuads();
411 }
412 
413 // This test verifies instancing with GL_POINTS with glDrawElementsInstanced works.
414 // On D3D11 FL9_3, this triggers a special codepath that emulates instanced points rendering.
TEST_P(InstancingTestPoints,DrawElements)415 TEST_P(InstancingTestPoints, DrawElements)
416 {
417     // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details
418     // On Win7, the D3D SDK Layers emits a false warning for these tests.
419     // This doesn't occur on Windows 10 (Version 1511) though.
420     ignoreD3D11SDKLayersWarnings();
421 
422     setupInstancedPointsTest();
423 
424     glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
425     glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0],
426                  GL_STATIC_DRAW);
427     glBindBuffer(GL_ARRAY_BUFFER, 0);
428 
429     // Get the attribute locations
430     GLint positionLoc    = glGetAttribLocation(mProgram, "a_position");
431     GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos");
432 
433     // Load the vertex position
434     GLfloat pos[3] = {0, 0, 0};
435     glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, pos);
436     glEnableVertexAttribArray(positionLoc);
437 
438     // Load the instance position
439     glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
440     glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
441     glBindBuffer(GL_ARRAY_BUFFER, 0);
442     glEnableVertexAttribArray(instancePosLoc);
443 
444     // Enable instancing
445     mVertexAttribDivisorANGLE(instancePosLoc, 1);
446 
447     // Do the instanced draw
448     mDrawElementsInstancedANGLE(GL_POINTS, static_cast<GLsizei>(mIndices.size()), GL_UNSIGNED_SHORT,
449                                 mIndices.data(), static_cast<GLsizei>(mInstances.size()) / 3);
450 
451     ASSERT_GL_NO_ERROR();
452 
453     checkQuads();
454 }
455 
456 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
457 // We test on D3D9 and D3D11 9_3 because they use special codepaths when attribute zero is instanced, unlike D3D11.
458 ANGLE_INSTANTIATE_TEST(InstancingTestAllConfigs,
459                        ES2_D3D9(),
460                        ES2_D3D11(),
461                        ES2_D3D11_FL9_3(),
462                        ES2_OPENGL(),
463                        ES2_OPENGLES());
464 
465 // TODO(jmadill): Figure out the situation with DrawInstanced on FL 9_3
466 ANGLE_INSTANTIATE_TEST(InstancingTestNo9_3, ES2_D3D9(), ES2_D3D11());
467 
468 ANGLE_INSTANTIATE_TEST(InstancingTestPoints, ES2_D3D11(), ES2_D3D11_FL9_3());
469