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