1 //
2 // Copyright 2014 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 // DrawCallPerf:
7 //   Performance tests for ANGLE draw call overhead.
8 //
9 
10 #include "ANGLEPerfTest.h"
11 #include "DrawCallPerfParams.h"
12 #include "common/PackedEnums.h"
13 #include "test_utils/draw_call_perf_utils.h"
14 #include "util/shader_utils.h"
15 
16 namespace
17 {
18 enum class StateChange
19 {
20     NoChange,
21     VertexAttrib,
22     VertexBuffer,
23     ManyVertexBuffers,
24     Texture,
25     Program,
26     VertexBufferCycle,
27     Scissor,
28     InvalidEnum,
29 };
30 
31 constexpr size_t kCycleVBOPoolSize = 200;
32 
33 struct DrawArraysPerfParams : public DrawCallPerfParams
34 {
35     DrawArraysPerfParams() = default;
DrawArraysPerfParams__anoned84aa030111::DrawArraysPerfParams36     DrawArraysPerfParams(const DrawCallPerfParams &base) : DrawCallPerfParams(base) {}
37 
38     std::string story() const override;
39 
40     StateChange stateChange = StateChange::NoChange;
41 };
42 
story() const43 std::string DrawArraysPerfParams::story() const
44 {
45     std::stringstream strstr;
46 
47     strstr << DrawCallPerfParams::story();
48 
49     switch (stateChange)
50     {
51         case StateChange::VertexAttrib:
52             strstr << "_attrib_change";
53             break;
54         case StateChange::VertexBuffer:
55             strstr << "_vbo_change";
56             break;
57         case StateChange::ManyVertexBuffers:
58             strstr << "_manyvbos_change";
59             break;
60         case StateChange::Texture:
61             strstr << "_tex_change";
62             break;
63         case StateChange::Program:
64             strstr << "_prog_change";
65             break;
66         case StateChange::VertexBufferCycle:
67             strstr << "_vbo_cycle";
68             break;
69         case StateChange::Scissor:
70             strstr << "_scissor_change";
71             break;
72         default:
73             break;
74     }
75 
76     return strstr.str();
77 }
78 
operator <<(std::ostream & os,const DrawArraysPerfParams & params)79 std::ostream &operator<<(std::ostream &os, const DrawArraysPerfParams &params)
80 {
81     os << params.backendAndStory().substr(1);
82     return os;
83 }
84 
CreateSimpleTexture2D()85 GLuint CreateSimpleTexture2D()
86 {
87     // Use tightly packed data
88     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
89 
90     // Generate a texture object
91     GLuint texture;
92     glGenTextures(1, &texture);
93 
94     // Bind the texture object
95     glBindTexture(GL_TEXTURE_2D, texture);
96 
97     // Load the texture: 2x2 Image, 3 bytes per pixel (R, G, B)
98     constexpr size_t width             = 2;
99     constexpr size_t height            = 2;
100     GLubyte pixels[width * height * 3] = {
101         255, 0,   0,    // Red
102         0,   255, 0,    // Green
103         0,   0,   255,  // Blue
104         255, 255, 0,    // Yellow
105     };
106     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
107 
108     // Set the filtering mode
109     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
110     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
111 
112     return texture;
113 }
114 
115 class DrawCallPerfBenchmark : public ANGLERenderTest,
116                               public ::testing::WithParamInterface<DrawArraysPerfParams>
117 {
118   public:
119     DrawCallPerfBenchmark();
120 
121     void initializeBenchmark() override;
122     void destroyBenchmark() override;
123     void drawBenchmark() override;
124 
125   private:
126     GLuint mProgram1   = 0;
127     GLuint mProgram2   = 0;
128     GLuint mBuffer1    = 0;
129     GLuint mBuffer2    = 0;
130     GLuint mFBO        = 0;
131     GLuint mFBOTexture = 0;
132     GLuint mTexture1   = 0;
133     GLuint mTexture2   = 0;
134     int mNumTris       = GetParam().numTris;
135     std::vector<GLuint> mVBOPool;
136     size_t mCurrentVBO = 0;
137 };
138 
DrawCallPerfBenchmark()139 DrawCallPerfBenchmark::DrawCallPerfBenchmark() : ANGLERenderTest("DrawCallPerf", GetParam()) {}
140 
initializeBenchmark()141 void DrawCallPerfBenchmark::initializeBenchmark()
142 {
143     const auto &params = GetParam();
144 
145     if (params.stateChange == StateChange::Texture)
146     {
147         mProgram1 = SetupSimpleTextureProgram();
148     }
149     if (params.stateChange == StateChange::Program)
150     {
151         mProgram1 = SetupSimpleTextureProgram();
152         mProgram2 = SetupDoubleTextureProgram();
153 
154         ASSERT_NE(0u, mProgram2);
155     }
156     else if (params.stateChange == StateChange::ManyVertexBuffers)
157     {
158         constexpr char kVS[] = R"(attribute vec2 vPosition;
159 attribute vec2 v0;
160 attribute vec2 v1;
161 attribute vec2 v2;
162 attribute vec2 v3;
163 const float scale = 0.5;
164 const float offset = -0.5;
165 
166 varying vec2 v;
167 
168 void main()
169 {
170     gl_Position = vec4(vPosition * vec2(scale) + vec2(offset), 0, 1);
171     v = (v0 + v1 + v2 + v3) * 0.25;
172 })";
173 
174         constexpr char kFS[] = R"(precision mediump float;
175 varying vec2 v;
176 void main()
177 {
178     gl_FragColor = vec4(v, 0, 1);
179 })";
180 
181         mProgram1 = CompileProgram(kVS, kFS);
182         glBindAttribLocation(mProgram1, 1, "v0");
183         glBindAttribLocation(mProgram1, 2, "v1");
184         glBindAttribLocation(mProgram1, 3, "v2");
185         glBindAttribLocation(mProgram1, 4, "v3");
186         glEnableVertexAttribArray(1);
187         glEnableVertexAttribArray(2);
188         glEnableVertexAttribArray(3);
189         glEnableVertexAttribArray(4);
190     }
191     else if (params.stateChange == StateChange::VertexBufferCycle)
192     {
193         mProgram1 = SetupSimpleDrawProgram();
194 
195         for (size_t bufferIndex = 0; bufferIndex < kCycleVBOPoolSize; ++bufferIndex)
196         {
197             GLuint buffer = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
198             mVBOPool.push_back(buffer);
199         }
200     }
201     else
202     {
203         mProgram1 = SetupSimpleDrawProgram();
204     }
205 
206     ASSERT_NE(0u, mProgram1);
207 
208     // Re-link program to ensure the attrib bindings are used.
209     glBindAttribLocation(mProgram1, 0, "vPosition");
210     glLinkProgram(mProgram1);
211     glUseProgram(mProgram1);
212 
213     if (mProgram2)
214     {
215         glBindAttribLocation(mProgram2, 0, "vPosition");
216         glLinkProgram(mProgram2);
217     }
218 
219     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
220 
221     mBuffer1 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
222     mBuffer2 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
223 
224     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
225     glEnableVertexAttribArray(0);
226 
227     // Set the viewport
228     glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());
229 
230     if (params.surfaceType == SurfaceType::Offscreen)
231     {
232         CreateColorFBO(getWindow()->getWidth(), getWindow()->getHeight(), &mFBOTexture, &mFBO);
233     }
234 
235     mTexture1 = CreateSimpleTexture2D();
236     mTexture2 = CreateSimpleTexture2D();
237 
238     if (params.stateChange == StateChange::Program)
239     {
240         // Bind the textures as appropriate, they are not modified during the test.
241         GLint program1Tex1Loc = glGetUniformLocation(mProgram1, "tex");
242         GLint program2Tex1Loc = glGetUniformLocation(mProgram2, "tex1");
243         GLint program2Tex2Loc = glGetUniformLocation(mProgram2, "tex2");
244 
245         glUseProgram(mProgram1);
246         glUniform1i(program1Tex1Loc, 0);
247 
248         glUseProgram(mProgram2);
249         glUniform1i(program2Tex1Loc, 0);
250         glUniform1i(program2Tex2Loc, 1);
251 
252         glActiveTexture(GL_TEXTURE0);
253         glBindTexture(GL_TEXTURE_2D, mTexture1);
254 
255         glActiveTexture(GL_TEXTURE1);
256         glBindTexture(GL_TEXTURE_2D, mTexture2);
257     }
258 
259     ASSERT_GL_NO_ERROR();
260 }
261 
destroyBenchmark()262 void DrawCallPerfBenchmark::destroyBenchmark()
263 {
264     glDeleteProgram(mProgram1);
265     glDeleteProgram(mProgram2);
266     glDeleteBuffers(1, &mBuffer1);
267     glDeleteBuffers(1, &mBuffer2);
268     glDeleteTextures(1, &mFBOTexture);
269     glDeleteTextures(1, &mTexture1);
270     glDeleteTextures(1, &mTexture2);
271     glDeleteFramebuffers(1, &mFBO);
272 
273     if (!mVBOPool.empty())
274     {
275         glDeleteBuffers(mVBOPool.size(), mVBOPool.data());
276     }
277 }
278 
ClearThenDraw(unsigned int iterations,GLsizei numElements)279 void ClearThenDraw(unsigned int iterations, GLsizei numElements)
280 {
281     glClear(GL_COLOR_BUFFER_BIT);
282 
283     for (unsigned int it = 0; it < iterations; it++)
284     {
285         glDrawArrays(GL_TRIANGLES, 0, numElements);
286     }
287 }
288 
JustDraw(unsigned int iterations,GLsizei numElements)289 void JustDraw(unsigned int iterations, GLsizei numElements)
290 {
291     for (unsigned int it = 0; it < iterations; it++)
292     {
293         glDrawArrays(GL_TRIANGLES, 0, numElements);
294     }
295 }
296 
297 template <int kArrayBufferCount>
ChangeVertexAttribThenDraw(unsigned int iterations,GLsizei numElements,GLuint buffer)298 void ChangeVertexAttribThenDraw(unsigned int iterations, GLsizei numElements, GLuint buffer)
299 {
300     glBindBuffer(GL_ARRAY_BUFFER, buffer);
301     for (unsigned int it = 0; it < iterations; it++)
302     {
303         for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
304         {
305             glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
306         }
307         glDrawArrays(GL_TRIANGLES, 0, numElements);
308 
309         for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
310         {
311             glVertexAttribPointer(arrayIndex, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
312         }
313         glDrawArrays(GL_TRIANGLES, 0, numElements);
314     }
315 }
316 template <int kArrayBufferCount>
ChangeArrayBuffersThenDraw(unsigned int iterations,GLsizei numElements,GLuint buffer1,GLuint buffer2)317 void ChangeArrayBuffersThenDraw(unsigned int iterations,
318                                 GLsizei numElements,
319                                 GLuint buffer1,
320                                 GLuint buffer2)
321 {
322     for (unsigned int it = 0; it < iterations; it++)
323     {
324         glBindBuffer(GL_ARRAY_BUFFER, buffer1);
325         for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
326         {
327             glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
328         }
329         glDrawArrays(GL_TRIANGLES, 0, numElements);
330 
331         glBindBuffer(GL_ARRAY_BUFFER, buffer2);
332         for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
333         {
334             glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
335         }
336         glDrawArrays(GL_TRIANGLES, 0, numElements);
337     }
338 }
339 
ChangeTextureThenDraw(unsigned int iterations,GLsizei numElements,GLuint texture1,GLuint texture2)340 void ChangeTextureThenDraw(unsigned int iterations,
341                            GLsizei numElements,
342                            GLuint texture1,
343                            GLuint texture2)
344 {
345     for (unsigned int it = 0; it < iterations; it++)
346     {
347         glBindTexture(GL_TEXTURE_2D, texture1);
348         glDrawArrays(GL_TRIANGLES, 0, numElements);
349 
350         glBindTexture(GL_TEXTURE_2D, texture2);
351         glDrawArrays(GL_TRIANGLES, 0, numElements);
352     }
353 }
354 
ChangeProgramThenDraw(unsigned int iterations,GLsizei numElements,GLuint program1,GLuint program2)355 void ChangeProgramThenDraw(unsigned int iterations,
356                            GLsizei numElements,
357                            GLuint program1,
358                            GLuint program2)
359 {
360     for (unsigned int it = 0; it < iterations; it++)
361     {
362         glUseProgram(program1);
363         glDrawArrays(GL_TRIANGLES, 0, numElements);
364 
365         glUseProgram(program2);
366         glDrawArrays(GL_TRIANGLES, 0, numElements);
367     }
368 }
369 
CycleVertexBufferThenDraw(unsigned int iterations,GLsizei numElements,const std::vector<GLuint> & vbos,size_t * currentVBO)370 void CycleVertexBufferThenDraw(unsigned int iterations,
371                                GLsizei numElements,
372                                const std::vector<GLuint> &vbos,
373                                size_t *currentVBO)
374 {
375     for (unsigned int it = 0; it < iterations; it++)
376     {
377         GLuint vbo = vbos[*currentVBO];
378         glBindBuffer(GL_ARRAY_BUFFER, vbo);
379         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
380         glDrawArrays(GL_TRIANGLES, 0, numElements);
381         *currentVBO = (*currentVBO + 1) % vbos.size();
382     }
383 }
384 
ChangeScissorThenDraw(unsigned int iterations,GLsizei numElements,unsigned int windowWidth,unsigned int windowHeight)385 void ChangeScissorThenDraw(unsigned int iterations,
386                            GLsizei numElements,
387                            unsigned int windowWidth,
388                            unsigned int windowHeight)
389 {
390     // Change scissor as such:
391     //
392     // - Start with a narrow vertical bar:
393     //
394     //           Scissor
395     //              |
396     //              V
397     //       +-----+-+-----+
398     //       |     | |     | <-- Window
399     //       |     | |     |
400     //       |     | |     |
401     //       |     | |     |
402     //       |     | |     |
403     //       |     | |     |
404     //       +-----+-+-----+
405     //
406     // - Gradually reduce height and increase width, to end up with a narrow horizontal bar:
407     //
408     //       +-------------+
409     //       |             |
410     //       |             |
411     //       +-------------+ <-- Scissor
412     //       +-------------+
413     //       |             |
414     //       |             |
415     //       +-------------+
416     //
417     // - If more iterations left, restart, but shift the initial bar left to cover more area:
418     //
419     //       +---+-+-------+          +-------------+
420     //       |   | |       |          |             |
421     //       |   | |       |          +-------------+
422     //       |   | |       |   --->   |             |
423     //       |   | |       |          |             |
424     //       |   | |       |          +-------------+
425     //       |   | |       |          |             |
426     //       +---+-+-------+          +-------------+
427     //
428     //       +-+-+---------+          +-------------+
429     //       | | |         |          +-------------+
430     //       | | |         |          |             |
431     //       | | |         |   --->   |             |
432     //       | | |         |          |             |
433     //       | | |         |          |             |
434     //       | | |         |          +-------------+
435     //       +-+-+---------+          +-------------+
436 
437     glEnable(GL_SCISSOR_TEST);
438 
439     constexpr unsigned int kScissorStep  = 2;
440     unsigned int scissorX                = windowWidth / 2 - 1;
441     unsigned int scissorY                = 0;
442     unsigned int scissorWidth            = 2;
443     unsigned int scissorHeight           = windowHeight;
444     unsigned int scissorPatternIteration = 0;
445 
446     for (unsigned int it = 0; it < iterations; it++)
447     {
448         glScissor(scissorX, scissorY, scissorWidth, scissorHeight);
449         glDrawArrays(GL_TRIANGLES, 0, numElements);
450 
451         if (scissorX < kScissorStep || scissorHeight < kScissorStep * 2)
452         {
453             ++scissorPatternIteration;
454             scissorX      = windowWidth / 2 - 1 - scissorPatternIteration * 2;
455             scissorY      = 0;
456             scissorWidth  = 2;
457             scissorHeight = windowHeight;
458         }
459         else
460         {
461             scissorX -= kScissorStep;
462             scissorY += kScissorStep;
463             scissorWidth += kScissorStep * 2;
464             scissorHeight -= kScissorStep * 2;
465         }
466     }
467 }
468 
drawBenchmark()469 void DrawCallPerfBenchmark::drawBenchmark()
470 {
471     // This workaround fixes a huge queue of graphics commands accumulating on the GL
472     // back-end. The GL back-end doesn't have a proper NULL device at the moment.
473     // TODO(jmadill): Remove this when/if we ever get a proper OpenGL NULL device.
474     const auto &eglParams = GetParam().eglParameters;
475     const auto &params    = GetParam();
476     GLsizei numElements   = static_cast<GLsizei>(3 * mNumTris);
477 
478     switch (params.stateChange)
479     {
480         case StateChange::VertexAttrib:
481             ChangeVertexAttribThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1);
482             break;
483         case StateChange::VertexBuffer:
484             ChangeArrayBuffersThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1,
485                                           mBuffer2);
486             break;
487         case StateChange::ManyVertexBuffers:
488             ChangeArrayBuffersThenDraw<5>(params.iterationsPerStep, numElements, mBuffer1,
489                                           mBuffer2);
490             break;
491         case StateChange::Texture:
492             ChangeTextureThenDraw(params.iterationsPerStep, numElements, mTexture1, mTexture2);
493             break;
494         case StateChange::Program:
495             ChangeProgramThenDraw(params.iterationsPerStep, numElements, mProgram1, mProgram2);
496             break;
497         case StateChange::NoChange:
498             if (eglParams.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE ||
499                 (eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE &&
500                  eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE))
501             {
502                 ClearThenDraw(params.iterationsPerStep, numElements);
503             }
504             else
505             {
506                 JustDraw(params.iterationsPerStep, numElements);
507             }
508             break;
509         case StateChange::VertexBufferCycle:
510             CycleVertexBufferThenDraw(params.iterationsPerStep, numElements, mVBOPool,
511                                       &mCurrentVBO);
512             break;
513         case StateChange::Scissor:
514             ChangeScissorThenDraw(params.iterationsPerStep, numElements, getWindow()->getWidth(),
515                                   getWindow()->getHeight());
516             break;
517         case StateChange::InvalidEnum:
518             FAIL() << "Invalid state change.";
519             break;
520     }
521 
522     ASSERT_GL_NO_ERROR();
523 }
524 
TEST_P(DrawCallPerfBenchmark,Run)525 TEST_P(DrawCallPerfBenchmark, Run)
526 {
527     run();
528 }
529 
530 using namespace params;
531 
CombineStateChange(const DrawArraysPerfParams & in,StateChange stateChange)532 DrawArraysPerfParams CombineStateChange(const DrawArraysPerfParams &in, StateChange stateChange)
533 {
534     DrawArraysPerfParams out = in;
535     out.stateChange          = stateChange;
536 
537     // Crank up iteration count to ensure we cycle through all VBs before a swap.
538     if (stateChange == StateChange::VertexBufferCycle)
539     {
540         out.iterationsPerStep = kCycleVBOPoolSize * 2;
541     }
542 
543     return out;
544 }
545 
546 using P = DrawArraysPerfParams;
547 
548 std::vector<P> gTestsWithStateChange =
549     CombineWithValues({P()}, angle::AllEnums<StateChange>(), CombineStateChange);
550 std::vector<P> gTestsWithRenderer =
551     CombineWithFuncs(gTestsWithStateChange, {D3D11<P>, GL<P>, Vulkan<P>, WGL<P>});
552 std::vector<P> gTestsWithDevice =
553     CombineWithFuncs(gTestsWithRenderer, {Passthrough<P>, Offscreen<P>, NullDevice<P>});
554 
555 ANGLE_INSTANTIATE_TEST_ARRAY(DrawCallPerfBenchmark, gTestsWithDevice);
556 
557 }  // anonymous namespace
558