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