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 ¶ms)
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 ¶ms = 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 ¶ms = 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