// 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 #include #include 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 name; GLint location; std::vector values; }; struct GlProgramState { GLboolean deleteStatus; GLboolean linkStatus; GLboolean validateStatus; std::vector infoLog; std::vector shaders; GLint activeAttributes; GLint maxAttributeName; std::vector attributes; GLint activeUniforms; GLint maxUniformName; std::vector 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 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& 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 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