blob: 36e80e3c2ef0b5175869fd9f45f32ec33f2b8bd7 [file] [log] [blame]
//
// Copyright 2016 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.
//
// UniformsBenchmark:
// Performance test for setting uniform data.
//
#include "ANGLEPerfTest.h"
#include <array>
#include <iostream>
#include <random>
#include <sstream>
#include "common/debug.h"
#include "util/Matrix.h"
#include "util/shader_utils.h"
using namespace angle;
namespace
{
constexpr unsigned int kIterationsPerStep = 4;
// Controls when we call glUniform, if the data is the same as last frame.
enum DataMode
{
UPDATE,
REPEAT,
};
// TODO(jmadill): Use an ANGLE enum for this?
enum DataType
{
VEC4,
MAT3x3,
MAT3x4,
MAT4x4,
};
// Determines if we state change the program between draws.
// This covers a performance problem in ANGLE where calling UseProgram reuploads uniform data.
enum ProgramMode
{
SINGLE,
MULTIPLE,
};
enum MatrixLayout
{
TRANSPOSE,
NO_TRANSPOSE,
};
struct UniformsParams final : public RenderTestParams
{
UniformsParams()
{
iterationsPerStep = kIterationsPerStep;
// Common default params
majorVersion = 3;
minorVersion = 0;
windowWidth = 720;
windowHeight = 720;
}
std::string story() const override;
size_t numVertexUniforms = 200;
size_t numFragmentUniforms = 200;
DataType dataType = DataType::VEC4;
DataMode dataMode = DataMode::REPEAT;
MatrixLayout matrixLayout = MatrixLayout::NO_TRANSPOSE;
ProgramMode programMode = ProgramMode::SINGLE;
};
std::ostream &operator<<(std::ostream &os, const UniformsParams &params)
{
os << params.backendAndStory().substr(1);
return os;
}
std::string UniformsParams::story() const
{
std::stringstream strstr;
strstr << RenderTestParams::story();
if (eglParameters.deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE)
{
strstr << "_null";
}
if (dataType == DataType::VEC4)
{
strstr << "_" << (numVertexUniforms + numFragmentUniforms) << "_vec4";
}
else if (dataType == DataType::MAT3x3)
{
strstr << "_" << (numVertexUniforms + numFragmentUniforms) << "_mat3x3";
}
else if (dataType == DataType::MAT3x4)
{
strstr << "_" << (numVertexUniforms + numFragmentUniforms) << "_mat3x4";
}
else
{
strstr << "_" << (numVertexUniforms + numFragmentUniforms) << "_mat4x4";
}
if (matrixLayout == MatrixLayout::TRANSPOSE)
{
strstr << "_transpose";
}
if (programMode == ProgramMode::MULTIPLE)
{
strstr << "_multiprogram";
}
if (dataMode == DataMode::REPEAT)
{
strstr << "_repeating";
}
return strstr.str();
}
class UniformsBenchmark : public ANGLERenderTest,
public ::testing::WithParamInterface<UniformsParams>
{
public:
UniformsBenchmark();
void initializeBenchmark() override;
void destroyBenchmark() override;
void drawBenchmark() override;
private:
void initShaders();
template <bool MultiProgram, typename SetUniformFunc>
void drawLoop(const SetUniformFunc &setUniformsFunc);
std::array<GLuint, 2> mPrograms;
std::vector<GLuint> mUniformLocations;
using MatrixData = std::array<std::vector<Matrix4>, 2>;
MatrixData mMatrixData;
};
std::vector<Matrix4> GenMatrixData(size_t count, int parity)
{
std::vector<Matrix4> data;
// Very simple matrix data allocation scheme.
for (size_t index = 0; index < count; ++index)
{
Matrix4 mat;
for (int row = 0; row < 4; ++row)
{
for (int col = 0; col < 4; ++col)
{
mat.data[row * 4 + col] = (row * col + parity) % 2 == 0 ? 1.0f : -1.0f;
}
}
data.push_back(mat);
}
return data;
}
UniformsBenchmark::UniformsBenchmark() : ANGLERenderTest("Uniforms", GetParam()), mPrograms({}) {}
void UniformsBenchmark::initializeBenchmark()
{
const auto &params = GetParam();
// Verify the uniform counts are within the limits
GLint maxVertexUniformVectors, maxFragmentUniformVectors;
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &maxVertexUniformVectors);
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &maxFragmentUniformVectors);
GLint vectorCountPerUniform;
bool isMatrix;
switch (params.dataType)
{
case DataType::MAT3x3:
vectorCountPerUniform = 3;
isMatrix = true;
break;
case DataType::MAT3x4:
// depends on transpose, conservatively set to 4
vectorCountPerUniform = 4;
isMatrix = true;
break;
case DataType::MAT4x4:
vectorCountPerUniform = 4;
isMatrix = true;
break;
default:
vectorCountPerUniform = 1;
isMatrix = false;
break;
}
GLint numVertexUniformVectors =
static_cast<GLint>(params.numVertexUniforms) * vectorCountPerUniform;
GLint numFragmentUniformVectors =
static_cast<GLint>(params.numFragmentUniforms) * vectorCountPerUniform;
if (numVertexUniformVectors > maxVertexUniformVectors)
{
FAIL() << "Vertex uniform vector count (" << numVertexUniformVectors << ")"
<< " exceeds maximum vertex uniform vector count: " << maxVertexUniformVectors
<< std::endl;
}
if (numFragmentUniformVectors > maxFragmentUniformVectors)
{
FAIL() << "Fragment uniform vector count (" << numFragmentUniformVectors << ")"
<< " exceeds maximum fragment uniform vector count: " << maxFragmentUniformVectors
<< std::endl;
}
initShaders();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());
if (isMatrix)
{
size_t count = params.numVertexUniforms + params.numFragmentUniforms;
mMatrixData[0] = GenMatrixData(count, 0);
if (params.dataMode == DataMode::REPEAT)
{
mMatrixData[1] = GenMatrixData(count, 0);
}
else
{
mMatrixData[1] = GenMatrixData(count, 1);
}
}
GLint attribLocation = glGetAttribLocation(mPrograms[0], "pos");
ASSERT_NE(-1, attribLocation);
ASSERT_EQ(attribLocation, glGetAttribLocation(mPrograms[1], "pos"));
glVertexAttrib4f(attribLocation, 1.0f, 0.0f, 0.0f, 1.0f);
ASSERT_GL_NO_ERROR();
}
std::string GetUniformLocationName(size_t idx, bool vertexShader)
{
std::stringstream strstr;
strstr << (vertexShader ? "vs" : "fs") << "_u_" << idx;
return strstr.str();
}
void UniformsBenchmark::initShaders()
{
const auto &params = GetParam();
const std::string kUniformVarPlaceHolder = "%s";
std::string typeString;
std::string uniformOperationTemplate;
switch (params.dataType)
{
case DataType::VEC4:
typeString = "vec4";
uniformOperationTemplate = kUniformVarPlaceHolder;
break;
case DataType::MAT3x3:
typeString = "mat3";
uniformOperationTemplate =
"mat4(" + kUniformVarPlaceHolder + ") * vec4(1.0, 1.0, 1.0, 1.0)";
break;
case DataType::MAT3x4:
typeString = "mat3x4";
uniformOperationTemplate =
"mat4(" + kUniformVarPlaceHolder + ") * vec4(1.0, 1.0, 1.0, 1.0)";
break;
case DataType::MAT4x4:
typeString = "mat4";
uniformOperationTemplate = kUniformVarPlaceHolder + "* vec4(1.0, 1.0, 1.0, 1.0)";
break;
default:
UNREACHABLE();
}
std::stringstream vstrstr;
vstrstr << "#version 300 es\n";
vstrstr << "precision mediump float;\n";
vstrstr << "in vec4 pos;\n";
for (size_t i = 0; i < params.numVertexUniforms; i++)
{
vstrstr << "uniform " << typeString << " " << GetUniformLocationName(i, true) << ";\n";
}
vstrstr << "void main()\n"
"{\n"
" gl_Position = pos;\n";
for (size_t i = 0; i < params.numVertexUniforms; i++)
{
std::string uniformOperation = uniformOperationTemplate;
std::size_t pos = uniformOperation.find(kUniformVarPlaceHolder);
ASSERT(pos != std::string::npos);
uniformOperation.replace(pos, kUniformVarPlaceHolder.size(),
GetUniformLocationName(i, true));
vstrstr << " gl_Position += ";
vstrstr << uniformOperation;
vstrstr << ";\n";
}
vstrstr << "}";
std::stringstream fstrstr;
fstrstr << "#version 300 es\n";
fstrstr << "precision mediump float;\n";
fstrstr << "out vec4 fragColor;\n";
for (size_t i = 0; i < params.numFragmentUniforms; i++)
{
fstrstr << "uniform " << typeString << " " << GetUniformLocationName(i, false) << ";\n";
}
fstrstr << "void main()\n"
"{\n"
" fragColor = vec4(0, 0, 0, 0);\n";
for (size_t i = 0; i < params.numFragmentUniforms; i++)
{
std::string uniformOperation = uniformOperationTemplate;
std::size_t pos = uniformOperation.find(kUniformVarPlaceHolder);
ASSERT(pos != std::string::npos);
uniformOperation.replace(pos, kUniformVarPlaceHolder.size(),
GetUniformLocationName(i, false));
fstrstr << " fragColor += ";
fstrstr << uniformOperation;
fstrstr << ";\n";
}
fstrstr << "}";
mPrograms[0] = CompileProgram(vstrstr.str().c_str(), fstrstr.str().c_str());
ASSERT_NE(0u, mPrograms[0]);
mPrograms[1] = CompileProgram(vstrstr.str().c_str(), fstrstr.str().c_str());
ASSERT_NE(0u, mPrograms[1]);
for (size_t i = 0; i < params.numVertexUniforms; ++i)
{
std::string name = GetUniformLocationName(i, true);
GLint location = glGetUniformLocation(mPrograms[0], name.c_str());
ASSERT_NE(-1, location);
ASSERT_EQ(location, glGetUniformLocation(mPrograms[1], name.c_str()));
mUniformLocations.push_back(location);
}
for (size_t i = 0; i < params.numFragmentUniforms; ++i)
{
std::string name = GetUniformLocationName(i, false);
GLint location = glGetUniformLocation(mPrograms[0], name.c_str());
ASSERT_NE(-1, location);
ASSERT_EQ(location, glGetUniformLocation(mPrograms[1], name.c_str()));
mUniformLocations.push_back(location);
}
// Use the program object
glUseProgram(mPrograms[0]);
}
void UniformsBenchmark::destroyBenchmark()
{
glDeleteProgram(mPrograms[0]);
glDeleteProgram(mPrograms[1]);
}
// Hopefully the compiler is smart enough to inline the lambda setUniformsFunc.
template <bool MultiProgram, typename SetUniformFunc>
void UniformsBenchmark::drawLoop(const SetUniformFunc &setUniformsFunc)
{
const auto &params = GetParam();
size_t frameIndex = 0;
for (size_t it = 0; it < params.iterationsPerStep; ++it, frameIndex = (frameIndex == 0 ? 1 : 0))
{
if (MultiProgram)
{
glUseProgram(mPrograms[frameIndex]);
}
if (params.dataMode == DataMode::UPDATE)
{
for (size_t uniform = 0; uniform < mUniformLocations.size(); ++uniform)
{
setUniformsFunc(mUniformLocations, mMatrixData, uniform, frameIndex);
}
}
glDrawArrays(GL_TRIANGLES, 0, 3);
}
}
void UniformsBenchmark::drawBenchmark()
{
const auto &params = GetParam();
GLboolean transpose = static_cast<GLboolean>(params.matrixLayout == MatrixLayout::TRANSPOSE);
switch (params.dataType)
{
case DataType::MAT4x4:
{
auto setFunc = [=](const std::vector<GLuint> &locations, const MatrixData &matrixData,
size_t uniform, size_t frameIndex) {
glUniformMatrix4fv(locations[uniform], 1, transpose,
matrixData[frameIndex][uniform].data);
};
drawLoop<false>(setFunc);
break;
}
case DataType::MAT3x4:
{
auto setFunc = [=](const std::vector<GLuint> &locations, const MatrixData &matrixData,
size_t uniform, size_t frameIndex) {
glUniformMatrix3x4fv(locations[uniform], 1, transpose,
matrixData[frameIndex][uniform].data);
};
drawLoop<false>(setFunc);
break;
}
case DataType::MAT3x3:
{
auto setFunc = [=](const std::vector<GLuint> &locations, const MatrixData &matrixData,
size_t uniform, size_t frameIndex) {
glUniformMatrix3fv(locations[uniform], 1, transpose,
matrixData[frameIndex][uniform].data);
};
drawLoop<false>(setFunc);
break;
}
case DataType::VEC4:
{
auto setFunc = [](const std::vector<GLuint> &locations, const MatrixData &matrixData,
size_t uniform, size_t frameIndex) {
float value = static_cast<float>(uniform);
glUniform4f(locations[uniform], value, value, value, value);
};
if (params.programMode == ProgramMode::MULTIPLE)
{
drawLoop<true>(setFunc);
}
else
{
drawLoop<false>(setFunc);
}
break;
}
default:
UNREACHABLE();
}
ASSERT_GL_NO_ERROR();
}
using namespace egl_platform;
UniformsParams VectorUniforms(const EGLPlatformParameters &egl,
DataMode dataMode,
ProgramMode programMode = ProgramMode::SINGLE)
{
UniformsParams params;
params.eglParameters = egl;
params.dataMode = dataMode;
params.programMode = programMode;
return params;
}
UniformsParams MatrixUniforms(const EGLPlatformParameters &egl,
DataMode dataMode,
DataType dataType,
MatrixLayout matrixLayout)
{
UniformsParams params;
params.eglParameters = egl;
params.dataType = dataType;
params.dataMode = dataMode;
params.matrixLayout = matrixLayout;
// Reduce the number of uniforms to fit within smaller upper limits on some configs.
params.numVertexUniforms = 55;
params.numFragmentUniforms = 55;
return params;
}
} // anonymous namespace
TEST_P(UniformsBenchmark, Run)
{
run();
}
ANGLE_INSTANTIATE_TEST(
UniformsBenchmark,
VectorUniforms(D3D11(), DataMode::REPEAT),
VectorUniforms(D3D11(), DataMode::UPDATE),
VectorUniforms(D3D11_NULL(), DataMode::UPDATE),
VectorUniforms(OPENGL_OR_GLES(), DataMode::UPDATE),
VectorUniforms(OPENGL_OR_GLES(), DataMode::REPEAT),
VectorUniforms(OPENGL_OR_GLES_NULL(), DataMode::UPDATE),
MatrixUniforms(D3D11(), DataMode::UPDATE, DataType::MAT4x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(OPENGL_OR_GLES(),
DataMode::UPDATE,
DataType::MAT4x4,
MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::UPDATE, DataType::MAT4x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::UPDATE, DataType::MAT4x4, MatrixLayout::TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::REPEAT, DataType::MAT4x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::UPDATE, DataType::MAT3x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::UPDATE, DataType::MAT3x3, MatrixLayout::TRANSPOSE),
MatrixUniforms(VULKAN_NULL(), DataMode::REPEAT, DataType::MAT3x3, MatrixLayout::TRANSPOSE),
MatrixUniforms(VULKAN(), DataMode::UPDATE, DataType::MAT4x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN(), DataMode::REPEAT, DataType::MAT4x4, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN(), DataMode::UPDATE, DataType::MAT3x3, MatrixLayout::NO_TRANSPOSE),
MatrixUniforms(VULKAN(), DataMode::REPEAT, DataType::MAT3x3, MatrixLayout::NO_TRANSPOSE),
VectorUniforms(D3D11_NULL(), DataMode::REPEAT, ProgramMode::MULTIPLE));