622 lines
19 KiB
C++
622 lines
19 KiB
C++
//
|
|
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
//
|
|
// DrawCallPerf:
|
|
// Performance tests for ANGLE draw call overhead.
|
|
//
|
|
|
|
#include "ANGLEPerfTest.h"
|
|
#include "DrawCallPerfParams.h"
|
|
#include "common/PackedEnums.h"
|
|
#include "test_utils/draw_call_perf_utils.h"
|
|
#include "util/shader_utils.h"
|
|
|
|
namespace
|
|
{
|
|
enum class StateChange
|
|
{
|
|
NoChange,
|
|
VertexAttrib,
|
|
VertexBuffer,
|
|
ManyVertexBuffers,
|
|
Texture,
|
|
Program,
|
|
VertexBufferCycle,
|
|
Scissor,
|
|
ManyTextureDraw,
|
|
InvalidEnum,
|
|
EnumCount = InvalidEnum,
|
|
};
|
|
|
|
constexpr size_t kCycleVBOPoolSize = 200;
|
|
constexpr size_t kManyTexturesCount = 8;
|
|
|
|
struct DrawArraysPerfParams : public DrawCallPerfParams
|
|
{
|
|
DrawArraysPerfParams() = default;
|
|
DrawArraysPerfParams(const DrawCallPerfParams &base) : DrawCallPerfParams(base) {}
|
|
|
|
std::string story() const override;
|
|
|
|
StateChange stateChange = StateChange::NoChange;
|
|
};
|
|
|
|
std::string DrawArraysPerfParams::story() const
|
|
{
|
|
std::stringstream strstr;
|
|
|
|
strstr << DrawCallPerfParams::story();
|
|
|
|
switch (stateChange)
|
|
{
|
|
case StateChange::VertexAttrib:
|
|
strstr << "_attrib_change";
|
|
break;
|
|
case StateChange::VertexBuffer:
|
|
strstr << "_vbo_change";
|
|
break;
|
|
case StateChange::ManyVertexBuffers:
|
|
strstr << "_manyvbos_change";
|
|
break;
|
|
case StateChange::Texture:
|
|
strstr << "_tex_change";
|
|
break;
|
|
case StateChange::Program:
|
|
strstr << "_prog_change";
|
|
break;
|
|
case StateChange::VertexBufferCycle:
|
|
strstr << "_vbo_cycle";
|
|
break;
|
|
case StateChange::Scissor:
|
|
strstr << "_scissor_change";
|
|
break;
|
|
case StateChange::ManyTextureDraw:
|
|
strstr << "_many_tex_draw";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return strstr.str();
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, const DrawArraysPerfParams ¶ms)
|
|
{
|
|
os << params.backendAndStory().substr(1);
|
|
return os;
|
|
}
|
|
|
|
GLuint CreateSimpleTexture2D()
|
|
{
|
|
// Use tightly packed data
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
// Generate a texture object
|
|
GLuint texture;
|
|
glGenTextures(1, &texture);
|
|
|
|
// Bind the texture object
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
|
|
// Load the texture: 2x2 Image, 3 bytes per pixel (R, G, B)
|
|
constexpr size_t width = 2;
|
|
constexpr size_t height = 2;
|
|
GLubyte pixels[width * height * 4] = {
|
|
255, 0, 0, 0, // Red
|
|
0, 255, 0, 0, // Green
|
|
0, 0, 255, 0, // Blue
|
|
255, 255, 0, 0 // Yellow
|
|
};
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
|
|
// Set the filtering mode
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
return texture;
|
|
}
|
|
|
|
class DrawCallPerfBenchmark : public ANGLERenderTest,
|
|
public ::testing::WithParamInterface<DrawArraysPerfParams>
|
|
{
|
|
public:
|
|
DrawCallPerfBenchmark();
|
|
|
|
void initializeBenchmark() override;
|
|
void destroyBenchmark() override;
|
|
void drawBenchmark() override;
|
|
|
|
private:
|
|
GLuint mProgram1 = 0;
|
|
GLuint mProgram2 = 0;
|
|
GLuint mProgram3 = 0;
|
|
GLuint mBuffer1 = 0;
|
|
GLuint mBuffer2 = 0;
|
|
GLuint mFBO = 0;
|
|
GLuint mFBOTexture = 0;
|
|
std::vector<GLuint> mTextures;
|
|
int mNumTris = GetParam().numTris;
|
|
std::vector<GLuint> mVBOPool;
|
|
size_t mCurrentVBO = 0;
|
|
};
|
|
|
|
DrawCallPerfBenchmark::DrawCallPerfBenchmark() : ANGLERenderTest("DrawCallPerf", GetParam()) {}
|
|
|
|
void DrawCallPerfBenchmark::initializeBenchmark()
|
|
{
|
|
const auto ¶ms = GetParam();
|
|
|
|
if (params.stateChange == StateChange::Texture)
|
|
{
|
|
mProgram1 = SetupSimpleTextureProgram();
|
|
ASSERT_NE(0u, mProgram1);
|
|
}
|
|
else if (params.stateChange == StateChange::ManyTextureDraw)
|
|
{
|
|
mProgram3 = SetupEightTextureProgram();
|
|
ASSERT_NE(0u, mProgram3);
|
|
}
|
|
else if (params.stateChange == StateChange::Program)
|
|
{
|
|
mProgram1 = SetupSimpleTextureProgram();
|
|
mProgram2 = SetupDoubleTextureProgram();
|
|
ASSERT_NE(0u, mProgram1);
|
|
ASSERT_NE(0u, mProgram2);
|
|
}
|
|
else if (params.stateChange == StateChange::ManyVertexBuffers)
|
|
{
|
|
constexpr char kVS[] = R"(attribute vec2 vPosition;
|
|
attribute vec2 v0;
|
|
attribute vec2 v1;
|
|
attribute vec2 v2;
|
|
attribute vec2 v3;
|
|
const float scale = 0.5;
|
|
const float offset = -0.5;
|
|
|
|
varying vec2 v;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = vec4(vPosition * vec2(scale) + vec2(offset), 0, 1);
|
|
v = (v0 + v1 + v2 + v3) * 0.25;
|
|
})";
|
|
|
|
constexpr char kFS[] = R"(precision mediump float;
|
|
varying vec2 v;
|
|
void main()
|
|
{
|
|
gl_FragColor = vec4(v, 0, 1);
|
|
})";
|
|
|
|
mProgram1 = CompileProgram(kVS, kFS);
|
|
ASSERT_NE(0u, mProgram1);
|
|
glBindAttribLocation(mProgram1, 1, "v0");
|
|
glBindAttribLocation(mProgram1, 2, "v1");
|
|
glBindAttribLocation(mProgram1, 3, "v2");
|
|
glBindAttribLocation(mProgram1, 4, "v3");
|
|
glEnableVertexAttribArray(1);
|
|
glEnableVertexAttribArray(2);
|
|
glEnableVertexAttribArray(3);
|
|
glEnableVertexAttribArray(4);
|
|
}
|
|
else if (params.stateChange == StateChange::VertexBufferCycle)
|
|
{
|
|
mProgram1 = SetupSimpleDrawProgram();
|
|
ASSERT_NE(0u, mProgram1);
|
|
|
|
for (size_t bufferIndex = 0; bufferIndex < kCycleVBOPoolSize; ++bufferIndex)
|
|
{
|
|
GLuint buffer = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
|
|
mVBOPool.push_back(buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mProgram1 = SetupSimpleDrawProgram();
|
|
ASSERT_NE(0u, mProgram1);
|
|
}
|
|
|
|
// Re-link program to ensure the attrib bindings are used.
|
|
if (mProgram1)
|
|
{
|
|
glBindAttribLocation(mProgram1, 0, "vPosition");
|
|
glLinkProgram(mProgram1);
|
|
glUseProgram(mProgram1);
|
|
}
|
|
|
|
if (mProgram2)
|
|
{
|
|
glBindAttribLocation(mProgram2, 0, "vPosition");
|
|
glLinkProgram(mProgram2);
|
|
}
|
|
|
|
if (mProgram3)
|
|
{
|
|
glBindAttribLocation(mProgram3, 0, "vPosition");
|
|
glLinkProgram(mProgram3);
|
|
}
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
mBuffer1 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
|
|
mBuffer2 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
// Set the viewport
|
|
glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());
|
|
|
|
if (params.surfaceType == SurfaceType::Offscreen)
|
|
{
|
|
CreateColorFBO(getWindow()->getWidth(), getWindow()->getHeight(), &mFBOTexture, &mFBO);
|
|
}
|
|
|
|
for (size_t i = 0; i < kManyTexturesCount; ++i)
|
|
{
|
|
mTextures.emplace_back(CreateSimpleTexture2D());
|
|
}
|
|
|
|
if (params.stateChange == StateChange::Program)
|
|
{
|
|
// Bind the textures as appropriate, they are not modified during the test.
|
|
GLint program1Tex1Loc = glGetUniformLocation(mProgram1, "tex");
|
|
GLint program2Tex1Loc = glGetUniformLocation(mProgram2, "tex1");
|
|
GLint program2Tex2Loc = glGetUniformLocation(mProgram2, "tex2");
|
|
|
|
glUseProgram(mProgram1);
|
|
glUniform1i(program1Tex1Loc, 0);
|
|
|
|
glUseProgram(mProgram2);
|
|
glUniform1i(program2Tex1Loc, 0);
|
|
glUniform1i(program2Tex2Loc, 1);
|
|
}
|
|
|
|
if (params.stateChange == StateChange::ManyTextureDraw)
|
|
{
|
|
GLint program3TexLocs[kManyTexturesCount];
|
|
|
|
for (size_t i = 0; i < mTextures.size(); ++i)
|
|
{
|
|
char stringBuffer[8];
|
|
snprintf(stringBuffer, sizeof(stringBuffer), "tex%zu", i);
|
|
program3TexLocs[i] = glGetUniformLocation(mProgram3, stringBuffer);
|
|
}
|
|
|
|
glUseProgram(mProgram3);
|
|
for (size_t i = 0; i < mTextures.size(); ++i)
|
|
{
|
|
glUniform1i(program3TexLocs[i], i);
|
|
}
|
|
|
|
for (size_t i = 0; i < mTextures.size(); ++i)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + i);
|
|
glBindTexture(GL_TEXTURE_2D, mTextures[i]);
|
|
}
|
|
}
|
|
|
|
ASSERT_GL_NO_ERROR();
|
|
}
|
|
|
|
void DrawCallPerfBenchmark::destroyBenchmark()
|
|
{
|
|
glDeleteProgram(mProgram1);
|
|
glDeleteProgram(mProgram2);
|
|
glDeleteProgram(mProgram3);
|
|
glDeleteBuffers(1, &mBuffer1);
|
|
glDeleteBuffers(1, &mBuffer2);
|
|
glDeleteTextures(1, &mFBOTexture);
|
|
glDeleteTextures(mTextures.size(), mTextures.data());
|
|
glDeleteFramebuffers(1, &mFBO);
|
|
|
|
if (!mVBOPool.empty())
|
|
{
|
|
glDeleteBuffers(mVBOPool.size(), mVBOPool.data());
|
|
}
|
|
}
|
|
|
|
void ClearThenDraw(unsigned int iterations, GLsizei numElements)
|
|
{
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
void JustDraw(unsigned int iterations, GLsizei numElements)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
template <int kArrayBufferCount>
|
|
void ChangeVertexAttribThenDraw(unsigned int iterations, GLsizei numElements, GLuint buffer)
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
|
|
{
|
|
glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
|
|
for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
|
|
{
|
|
glVertexAttribPointer(arrayIndex, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
|
|
}
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
template <int kArrayBufferCount>
|
|
void ChangeArrayBuffersThenDraw(unsigned int iterations,
|
|
GLsizei numElements,
|
|
GLuint buffer1,
|
|
GLuint buffer2)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer1);
|
|
for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
|
|
{
|
|
glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer2);
|
|
for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
|
|
{
|
|
glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
void ChangeTextureThenDraw(unsigned int iterations,
|
|
GLsizei numElements,
|
|
GLuint texture1,
|
|
GLuint texture2)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, texture1);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture2);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
void ChangeProgramThenDraw(unsigned int iterations,
|
|
GLsizei numElements,
|
|
GLuint program1,
|
|
GLuint program2)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glUseProgram(program1);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
|
|
glUseProgram(program2);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
void CycleVertexBufferThenDraw(unsigned int iterations,
|
|
GLsizei numElements,
|
|
const std::vector<GLuint> &vbos,
|
|
size_t *currentVBO)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
GLuint vbo = vbos[*currentVBO];
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
*currentVBO = (*currentVBO + 1) % vbos.size();
|
|
}
|
|
}
|
|
|
|
void ChangeScissorThenDraw(unsigned int iterations,
|
|
GLsizei numElements,
|
|
unsigned int windowWidth,
|
|
unsigned int windowHeight)
|
|
{
|
|
// Change scissor as such:
|
|
//
|
|
// - Start with a narrow vertical bar:
|
|
//
|
|
// Scissor
|
|
// |
|
|
// V
|
|
// +-----+-+-----+
|
|
// | | | | <-- Window
|
|
// | | | |
|
|
// | | | |
|
|
// | | | |
|
|
// | | | |
|
|
// | | | |
|
|
// +-----+-+-----+
|
|
//
|
|
// - Gradually reduce height and increase width, to end up with a narrow horizontal bar:
|
|
//
|
|
// +-------------+
|
|
// | |
|
|
// | |
|
|
// +-------------+ <-- Scissor
|
|
// +-------------+
|
|
// | |
|
|
// | |
|
|
// +-------------+
|
|
//
|
|
// - If more iterations left, restart, but shift the initial bar left to cover more area:
|
|
//
|
|
// +---+-+-------+ +-------------+
|
|
// | | | | | |
|
|
// | | | | +-------------+
|
|
// | | | | ---> | |
|
|
// | | | | | |
|
|
// | | | | +-------------+
|
|
// | | | | | |
|
|
// +---+-+-------+ +-------------+
|
|
//
|
|
// +-+-+---------+ +-------------+
|
|
// | | | | +-------------+
|
|
// | | | | | |
|
|
// | | | | ---> | |
|
|
// | | | | | |
|
|
// | | | | | |
|
|
// | | | | +-------------+
|
|
// +-+-+---------+ +-------------+
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
constexpr unsigned int kScissorStep = 2;
|
|
unsigned int scissorX = windowWidth / 2 - 1;
|
|
unsigned int scissorY = 0;
|
|
unsigned int scissorWidth = 2;
|
|
unsigned int scissorHeight = windowHeight;
|
|
unsigned int scissorPatternIteration = 0;
|
|
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
glScissor(scissorX, scissorY, scissorWidth, scissorHeight);
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
|
|
if (scissorX < kScissorStep || scissorHeight < kScissorStep * 2)
|
|
{
|
|
++scissorPatternIteration;
|
|
scissorX = windowWidth / 2 - 1 - scissorPatternIteration * 2;
|
|
scissorY = 0;
|
|
scissorWidth = 2;
|
|
scissorHeight = windowHeight;
|
|
}
|
|
else
|
|
{
|
|
scissorX -= kScissorStep;
|
|
scissorY += kScissorStep;
|
|
scissorWidth += kScissorStep * 2;
|
|
scissorHeight -= kScissorStep * 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawWithEightTextures(unsigned int iterations,
|
|
GLsizei numElements,
|
|
std::vector<GLuint> textures)
|
|
{
|
|
for (unsigned int it = 0; it < iterations; it++)
|
|
{
|
|
for (size_t i = 0; i < textures.size(); ++i)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + i);
|
|
size_t index = (it + i) % textures.size();
|
|
glBindTexture(GL_TEXTURE_2D, textures[index]);
|
|
}
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, numElements);
|
|
}
|
|
}
|
|
|
|
void DrawCallPerfBenchmark::drawBenchmark()
|
|
{
|
|
// This workaround fixes a huge queue of graphics commands accumulating on the GL
|
|
// back-end. The GL back-end doesn't have a proper NULL device at the moment.
|
|
// TODO(jmadill): Remove this when/if we ever get a proper OpenGL NULL device.
|
|
const auto &eglParams = GetParam().eglParameters;
|
|
const auto ¶ms = GetParam();
|
|
GLsizei numElements = static_cast<GLsizei>(3 * mNumTris);
|
|
|
|
switch (params.stateChange)
|
|
{
|
|
case StateChange::VertexAttrib:
|
|
ChangeVertexAttribThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1);
|
|
break;
|
|
case StateChange::VertexBuffer:
|
|
ChangeArrayBuffersThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1,
|
|
mBuffer2);
|
|
break;
|
|
case StateChange::ManyVertexBuffers:
|
|
ChangeArrayBuffersThenDraw<5>(params.iterationsPerStep, numElements, mBuffer1,
|
|
mBuffer2);
|
|
break;
|
|
case StateChange::Texture:
|
|
ChangeTextureThenDraw(params.iterationsPerStep, numElements, mTextures[0],
|
|
mTextures[1]);
|
|
break;
|
|
case StateChange::Program:
|
|
ChangeProgramThenDraw(params.iterationsPerStep, numElements, mProgram1, mProgram2);
|
|
break;
|
|
case StateChange::NoChange:
|
|
if (eglParams.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE ||
|
|
(eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE &&
|
|
eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE))
|
|
{
|
|
ClearThenDraw(params.iterationsPerStep, numElements);
|
|
}
|
|
else
|
|
{
|
|
JustDraw(params.iterationsPerStep, numElements);
|
|
}
|
|
break;
|
|
case StateChange::VertexBufferCycle:
|
|
CycleVertexBufferThenDraw(params.iterationsPerStep, numElements, mVBOPool,
|
|
&mCurrentVBO);
|
|
break;
|
|
case StateChange::Scissor:
|
|
ChangeScissorThenDraw(params.iterationsPerStep, numElements, getWindow()->getWidth(),
|
|
getWindow()->getHeight());
|
|
break;
|
|
case StateChange::ManyTextureDraw:
|
|
glUseProgram(mProgram3);
|
|
DrawWithEightTextures(params.iterationsPerStep, numElements, mTextures);
|
|
break;
|
|
case StateChange::InvalidEnum:
|
|
ADD_FAILURE() << "Invalid state change.";
|
|
break;
|
|
}
|
|
|
|
ASSERT_GL_NO_ERROR();
|
|
}
|
|
|
|
TEST_P(DrawCallPerfBenchmark, Run)
|
|
{
|
|
run();
|
|
}
|
|
|
|
using namespace params;
|
|
|
|
DrawArraysPerfParams CombineStateChange(const DrawArraysPerfParams &in, StateChange stateChange)
|
|
{
|
|
DrawArraysPerfParams out = in;
|
|
out.stateChange = stateChange;
|
|
|
|
// Crank up iteration count to ensure we cycle through all VBs before a swap.
|
|
if (stateChange == StateChange::VertexBufferCycle)
|
|
{
|
|
out.iterationsPerStep = kCycleVBOPoolSize * 2;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
using P = DrawArraysPerfParams;
|
|
|
|
std::vector<P> gTestsWithStateChange =
|
|
CombineWithValues({P()}, angle::AllEnums<StateChange>(), CombineStateChange);
|
|
std::vector<P> gTestsWithRenderer =
|
|
CombineWithFuncs(gTestsWithStateChange, {D3D11<P>, GL<P>, Vulkan<P>, WGL<P>});
|
|
std::vector<P> gTestsWithDevice =
|
|
CombineWithFuncs(gTestsWithRenderer, {Passthrough<P>, Offscreen<P>, NullDevice<P>});
|
|
|
|
ANGLE_INSTANTIATE_TEST_ARRAY(DrawCallPerfBenchmark, gTestsWithDevice);
|
|
|
|
} // anonymous namespace
|