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