blob: d23ded19dd5320ed3d216f9529c1e0296e09c33a [file] [log] [blame]
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "GLSnapshotTestStateUtils.h"
#include "GLSnapshotTesting.h"
#include "apigen-codec-common/glUtils.h"
#include <gtest/gtest.h>
#include <map>
#include <string>
namespace gfxstream {
namespace gl {
namespace {
static const char kTestVertexShader[] = R"(
attribute vec4 position;
uniform mat4 testFloatMat;
uniform mat4 transform;
uniform mat4 screenSpace;
uniform ivec3 testInts[2];
varying float linear;
void main(void) {
gl_Position = testFloatMat * transform * position;
linear = (screenSpace * position).x;
gl_PointSize = linear * 0.5 + float(testInts[1].x);
}
)";
static const char kTestFragmentShader[] = R"(
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
)";
struct GlShaderVariable {
GLint size;
GLenum type;
std::vector<GLchar> name;
GLint location;
std::vector<GlValues> values;
};
struct GlProgramState {
GLboolean deleteStatus;
GLboolean linkStatus;
GLboolean validateStatus;
std::vector<GLchar> infoLog;
std::vector<GLuint> shaders;
GLint activeAttributes;
GLint maxAttributeName;
std::vector<GlShaderVariable> attributes;
GLint activeUniforms;
GLint maxUniformName;
std::vector<GlShaderVariable> uniforms;
};
// SnapshotGlProgramTest - A helper class for testing the snapshot preservation
// of program objects' state.
//
// This holds state information of a particular single program object whose
// state is mutated in order to set up tests.
// Provide a lambda via setStateChanger to set up the state which will be
// checked for preservation.
// A test can also verify that the snapshot keeps the correct program in use by
// calling useProgram during state setup.
//
class SnapshotGlProgramTest : public SnapshotPreserveTest {
public:
void defaultStateCheck() override {
EXPECT_EQ(GL_FALSE, gl->glIsProgram(m_program_name));
EXPECT_TRUE(compareGlobalGlInt(gl, GL_CURRENT_PROGRAM, 0));
}
void changedStateCheck() override {
SCOPED_TRACE("test program name = " + std::to_string(m_program_name));
EXPECT_EQ(GL_TRUE, gl->glIsProgram(m_program_name));
EXPECT_TRUE(
compareGlobalGlInt(gl, GL_CURRENT_PROGRAM, m_current_program));
GlProgramState currentState = getProgramState();
EXPECT_STREQ(m_program_state.infoLog.data(),
currentState.infoLog.data());
EXPECT_EQ(m_program_state.deleteStatus, currentState.deleteStatus);
EXPECT_EQ(m_program_state.linkStatus, currentState.linkStatus);
EXPECT_EQ(m_program_state.validateStatus, currentState.validateStatus);
// TODO(benzene): allow test to pass even if these are out of order
EXPECT_EQ(m_program_state.shaders, currentState.shaders);
EXPECT_EQ(m_program_state.activeAttributes,
currentState.activeAttributes);
EXPECT_EQ(m_program_state.maxAttributeName,
currentState.maxAttributeName);
ASSERT_EQ(m_program_state.attributes.size(),
currentState.attributes.size());
for (int i = 0; i < currentState.attributes.size(); i++) {
SCOPED_TRACE("active attribute i = " + std::to_string(i));
EXPECT_EQ(m_program_state.attributes[i].size,
currentState.attributes[i].size);
EXPECT_EQ(m_program_state.attributes[i].type,
currentState.attributes[i].type);
EXPECT_STREQ(m_program_state.attributes[i].name.data(),
currentState.attributes[i].name.data());
EXPECT_EQ(m_program_state.attributes[i].location,
currentState.attributes[i].location);
// TODO(benzene): check attribute values?
}
EXPECT_EQ(m_program_state.activeUniforms, currentState.activeUniforms);
EXPECT_EQ(m_program_state.maxUniformName, currentState.maxUniformName);
ASSERT_EQ(m_program_state.uniforms.size(),
currentState.uniforms.size());
for (int i = 0; i < currentState.uniforms.size(); i++) {
SCOPED_TRACE("active uniform i = " + std::to_string(i));
EXPECT_EQ(m_program_state.uniforms[i].size,
currentState.uniforms[i].size);
EXPECT_EQ(m_program_state.uniforms[i].type,
currentState.uniforms[i].type);
EXPECT_STREQ(m_program_state.uniforms[i].name.data(),
currentState.uniforms[i].name.data());
EXPECT_EQ(m_program_state.uniforms[i].location,
currentState.uniforms[i].location);
for (int j = 0; j < currentState.uniforms[i].size; j++) {
SCOPED_TRACE("value j = " + std::to_string(j));
GlValues& expectedVal = m_program_state.uniforms[i].values[j];
GlValues& currentVal = currentState.uniforms[i].values[j];
if (currentVal.floats.size() > 0 &&
currentVal.ints.size() > 0) {
ADD_FAILURE() << "Uniform "
<< currentState.uniforms[i].name.data()
<< " had both ints and floats at index " << j;
}
if (currentVal.floats.size() > 0) {
EXPECT_EQ(currentVal.floats, expectedVal.floats)
<< currentState.uniforms[i].name.data();
} else {
EXPECT_EQ(currentVal.ints, expectedVal.ints)
<< currentState.uniforms[i].name.data();
}
}
}
}
void stateChange() override {
m_program_name = gl->glCreateProgram();
m_program_state = getProgramState();
m_state_changer();
}
void setStateChanger(std::function<void()> changer) {
m_state_changer = changer;
}
protected:
// As part of state change, have the GL use the test program.
void useProgram() {
gl->glUseProgram(m_program_name);
EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
m_current_program = m_program_name;
}
// Collects information about the test program object's current state.
GlProgramState getProgramState() {
GlProgramState ret = {};
if (GL_FALSE == gl->glIsProgram(m_program_name)) {
ADD_FAILURE() << "cannot get program state: was not a program";
return ret;
}
// Info log
GLsizei logLength;
gl->glGetProgramiv(m_program_name, GL_INFO_LOG_LENGTH, &logLength);
ret.infoLog.resize(logLength);
GLsizei actualLength;
gl->glGetProgramInfoLog(m_program_name, logLength, &actualLength,
&ret.infoLog[0]);
// Boolean statuses
GLint val;
gl->glGetProgramiv(m_program_name, GL_DELETE_STATUS, &val);
ret.deleteStatus = val;
gl->glGetProgramiv(m_program_name, GL_LINK_STATUS, &val);
ret.linkStatus = val;
gl->glGetProgramiv(m_program_name, GL_VALIDATE_STATUS, &val);
ret.validateStatus = val;
// Attached shaders
GLint attachedShaders;
gl->glGetProgramiv(m_program_name, GL_ATTACHED_SHADERS,
&attachedShaders);
ret.shaders.resize(attachedShaders);
GLsizei shaderCount;
gl->glGetAttachedShaders(m_program_name, attachedShaders, &shaderCount,
&ret.shaders[0]);
// Uniforms
gl->glGetProgramiv(m_program_name, GL_ACTIVE_UNIFORM_MAX_LENGTH,
&ret.maxUniformName);
gl->glGetProgramiv(m_program_name, GL_ACTIVE_UNIFORMS,
&ret.activeUniforms);
for (GLuint i = 0; i < ret.activeUniforms; i++) {
GlShaderVariable unif = {};
unif.name.resize(ret.maxUniformName);
GLsizei unifLen;
gl->glGetActiveUniform(m_program_name, i, ret.maxUniformName,
&unifLen, &unif.size, &unif.type,
&unif.name[0]);
unif.location =
gl->glGetUniformLocation(m_program_name, unif.name.data());
if (unif.size > 1) {
// uniform array; get values from each index
std::string baseName =
getUniformBaseName(std::string(unif.name.data()));
for (int uniformValueIndex = 0; uniformValueIndex < unif.size;
uniformValueIndex++) {
std::string indexedName =
baseName + '[' + std::to_string(uniformValueIndex) +
']';
GLuint indexedLocation = gl->glGetUniformLocation(
m_program_name, indexedName.c_str());
getUniformValues(indexedLocation, unif.type, unif.values);
}
} else {
getUniformValues(unif.location, unif.type, unif.values);
}
ret.uniforms.push_back(unif);
}
// Attributes
gl->glGetProgramiv(m_program_name, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
&ret.maxAttributeName);
gl->glGetProgramiv(m_program_name, GL_ACTIVE_ATTRIBUTES,
&ret.activeAttributes);
for (GLuint i = 0; i < ret.activeAttributes; i++) {
GlShaderVariable attr = {};
attr.name.resize(ret.maxAttributeName);
GLsizei attrLen;
gl->glGetActiveAttrib(m_program_name, i, ret.maxAttributeName,
&attrLen, &attr.size, &attr.type,
&attr.name[0]);
attr.location =
gl->glGetAttribLocation(m_program_name, &attr.name[0]);
// TODO(benzene): get attribute values?
ret.attributes.push_back(attr);
}
return ret;
}
// Retrieves the values of the uniform at |location| for the test program.
// Returns them into |values|.
void getUniformValues(GLuint location,
GLenum type,
std::vector<GlValues>& values) {
GlValues val = {};
switch (type) {
case GL_FLOAT:
case GL_FLOAT_VEC2:
case GL_FLOAT_VEC3:
case GL_FLOAT_VEC4:
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT3:
case GL_FLOAT_MAT4:
val.floats.resize(glSizeof(type) / sizeof(GLfloat));
gl->glGetUniformfv(m_program_name, location, val.floats.data());
values.push_back(std::move(val));
return;
case GL_INT:
case GL_INT_VEC2:
case GL_INT_VEC3:
case GL_INT_VEC4:
case GL_BOOL:
case GL_BOOL_VEC2:
case GL_BOOL_VEC3:
case GL_BOOL_VEC4:
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
val.ints.resize(glSizeof(type) / sizeof(GLint));
gl->glGetUniformiv(m_program_name, location, val.ints.data());
values.push_back(std::move(val));
break;
default:
ADD_FAILURE() << "unsupported uniform type " << type;
return;
}
}
// If string |name| ends with a subscript ([]) operator, return a substring
// with the subscript removed.
std::string getUniformBaseName(const std::string& name) {
std::string baseName;
int length = name.length();
if (length < 3)
return name;
size_t lastBracket = name.find_last_of('[');
if (lastBracket != std::string::npos) {
baseName = name.substr(0, lastBracket);
} else {
baseName = name;
}
return baseName;
}
GLuint m_program_name = 0;
GlProgramState m_program_state = {};
GLuint m_current_program = 0;
std::function<void()> m_state_changer = [] {};
};
TEST_F(SnapshotGlProgramTest, CreateProgram) {
doCheckedSnapshot();
}
TEST_F(SnapshotGlProgramTest, AttachDetachShader) {
setStateChanger([this] {
GLuint vshader =
loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
kTestFragmentShader);
gl->glAttachShader(m_program_name, vshader);
gl->glAttachShader(m_program_name, fshader);
gl->glDetachShader(m_program_name, vshader);
m_program_state.shaders.push_back(fshader);
});
doCheckedSnapshot();
}
TEST_F(SnapshotGlProgramTest, LinkAndValidate) {
setStateChanger([this] {
GLuint vshader =
loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
kTestFragmentShader);
gl->glAttachShader(m_program_name, vshader);
gl->glAttachShader(m_program_name, fshader);
gl->glLinkProgram(m_program_name);
EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
gl->glValidateProgram(m_program_name);
EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
m_program_state = getProgramState();
EXPECT_EQ(1, m_program_state.activeAttributes);
EXPECT_EQ(4, m_program_state.activeUniforms);
EXPECT_EQ(GL_TRUE, m_program_state.linkStatus);
EXPECT_EQ(GL_TRUE, m_program_state.validateStatus);
});
doCheckedSnapshot();
}
TEST_F(SnapshotGlProgramTest, UseProgramAndUniforms) {
setStateChanger([this] {
GLuint vshader =
loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
kTestFragmentShader);
gl->glAttachShader(m_program_name, vshader);
gl->glAttachShader(m_program_name, fshader);
gl->glLinkProgram(m_program_name);
EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
gl->glValidateProgram(m_program_name);
EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
useProgram();
GLuint floatMatUnifLocation =
gl->glGetUniformLocation(m_program_name, "testFloatMat");
const GLfloat testFloatMatrix[16] = {
1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3,
0.2, 0.1, 0.0, -0.1, -0.1, -0.3, -0.4, -0.5,
};
gl->glUniformMatrix4fv(floatMatUnifLocation, 1, GL_FALSE,
testFloatMatrix);
GLuint intVecUnifLocation =
gl->glGetUniformLocation(m_program_name, "testInts");
const GLint testIntVec[6] = {
10, 11, 12, 20, 21, 22,
};
gl->glUniform3iv(intVecUnifLocation, 2, testIntVec);
m_program_state = getProgramState();
});
doCheckedSnapshot();
}
} // namespace
} // namespace gl
} // namespace gfxstream