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