/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES Utilities * ------------------------------------------------ * * Copyright 2014 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. * *//*! * \file * \brief Wrapper for GL program object. *//*--------------------------------------------------------------------*/ #include "gluShaderProgram.hpp" #include "gluRenderContext.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "tcuTestLog.hpp" #include "deClock.h" #include <cstring> using std::string; namespace glu { // Shader Shader::Shader (const RenderContext& renderCtx, ShaderType shaderType) : m_gl (renderCtx.getFunctions()) , m_shader (0) { m_info.type = shaderType; m_shader = m_gl.createShader(getGLShaderType(shaderType)); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateShader()"); TCU_CHECK(m_shader); } Shader::Shader (const glw::Functions& gl, ShaderType shaderType) : m_gl (gl) , m_shader (0) { m_info.type = shaderType; m_shader = m_gl.createShader(getGLShaderType(shaderType)); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateShader()"); TCU_CHECK(m_shader); } Shader::~Shader (void) { m_gl.deleteShader(m_shader); } void Shader::setSources (int numSourceStrings, const char* const* sourceStrings, const int* lengths) { m_gl.shaderSource(m_shader, numSourceStrings, sourceStrings, lengths); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glShaderSource()"); m_info.source.clear(); for (int ndx = 0; ndx < numSourceStrings; ndx++) { const size_t length = lengths && lengths[ndx] >= 0 ? lengths[ndx] : strlen(sourceStrings[ndx]); m_info.source += std::string(sourceStrings[ndx], length); } } void Shader::compile (void) { m_info.compileOk = false; m_info.compileTimeUs = 0; m_info.infoLog.clear(); { deUint64 compileStart = deGetMicroseconds(); m_gl.compileShader(m_shader); m_info.compileTimeUs = deGetMicroseconds() - compileStart; } GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCompileShader()"); // Query status { int compileStatus = 0; m_gl.getShaderiv(m_shader, GL_COMPILE_STATUS, &compileStatus); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetShaderiv()"); m_info.compileOk = compileStatus != GL_FALSE; } // Query log { int infoLogLen = 0; int unusedLen; m_gl.getShaderiv(m_shader, GL_INFO_LOG_LENGTH, &infoLogLen); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetShaderiv()"); if (infoLogLen > 0) { // The INFO_LOG_LENGTH query and the buffer query implementations have // very commonly off-by-one errors. Try to work around these issues. // add tolerance for off-by-one in log length, buffer write, and for terminator std::vector<char> infoLog(infoLogLen + 3, '\0'); // claim buf size is one smaller to protect from off-by-one writing over buffer bounds m_gl.getShaderInfoLog(m_shader, (int)infoLog.size() - 1, &unusedLen, &infoLog[0]); if (infoLog[(int)(infoLog.size()) - 1] != '\0') { // return whole buffer if null terminator was overwritten m_info.infoLog = std::string(&infoLog[0], infoLog.size()); } else { // read as C string. infoLog is guaranteed to be 0-terminated m_info.infoLog = std::string(&infoLog[0]); } } } } // Program static bool getProgramLinkStatus (const glw::Functions& gl, deUint32 program) { int linkStatus = 0; gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()"); return (linkStatus != GL_FALSE); } static std::string getProgramInfoLog (const glw::Functions& gl, deUint32 program) { int infoLogLen = 0; int unusedLen; gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv()"); if (infoLogLen > 0) { // The INFO_LOG_LENGTH query and the buffer query implementations have // very commonly off-by-one errors. Try to work around these issues. // add tolerance for off-by-one in log length, buffer write, and for terminator std::vector<char> infoLog(infoLogLen + 3, '\0'); // claim buf size is one smaller to protect from off-by-one writing over buffer bounds gl.getProgramInfoLog(program, (int)infoLog.size() - 1, &unusedLen, &infoLog[0]); // return whole buffer if null terminator was overwritten if (infoLog[(int)(infoLog.size()) - 1] != '\0') return std::string(&infoLog[0], infoLog.size()); // read as C string. infoLog is guaranteed to be 0-terminated return std::string(&infoLog[0]); } return std::string(); } Program::Program (const RenderContext& renderCtx) : m_gl (renderCtx.getFunctions()) , m_program (0) { m_program = m_gl.createProgram(); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateProgram()"); } Program::Program (const glw::Functions& gl) : m_gl (gl) , m_program (0) { m_program = m_gl.createProgram(); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateProgram()"); } Program::Program (const RenderContext& renderCtx, deUint32 program) : m_gl (renderCtx.getFunctions()) , m_program (program) { m_info.linkOk = getProgramLinkStatus(m_gl, program); m_info.infoLog = getProgramInfoLog(m_gl, program); } Program::~Program (void) { m_gl.deleteProgram(m_program); } void Program::attachShader (deUint32 shader) { m_gl.attachShader(m_program, shader); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glAttachShader()"); } void Program::detachShader (deUint32 shader) { m_gl.detachShader(m_program, shader); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDetachShader()"); } void Program::bindAttribLocation (deUint32 location, const char* name) { m_gl.bindAttribLocation(m_program, location, name); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBindAttribLocation()"); } void Program::transformFeedbackVaryings (int count, const char* const* varyings, deUint32 bufferMode) { m_gl.transformFeedbackVaryings(m_program, count, varyings, bufferMode); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glTransformFeedbackVaryings()"); } void Program::link (void) { m_info.linkOk = false; m_info.linkTimeUs = 0; m_info.infoLog.clear(); { deUint64 linkStart = deGetMicroseconds(); m_gl.linkProgram(m_program); m_info.linkTimeUs = deGetMicroseconds() - linkStart; } GLU_EXPECT_NO_ERROR(m_gl.getError(), "glLinkProgram()"); m_info.linkOk = getProgramLinkStatus(m_gl, m_program); m_info.infoLog = getProgramInfoLog(m_gl, m_program); } bool Program::isSeparable (void) const { int separable = GL_FALSE; m_gl.getProgramiv(m_program, GL_PROGRAM_SEPARABLE, &separable); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetProgramiv()"); return (separable != GL_FALSE); } void Program::setSeparable (bool separable) { m_gl.programParameteri(m_program, GL_PROGRAM_SEPARABLE, separable); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glProgramParameteri()"); } // ProgramPipeline ProgramPipeline::ProgramPipeline (const RenderContext& renderCtx) : m_gl (renderCtx.getFunctions()) , m_pipeline (0) { m_gl.genProgramPipelines(1, &m_pipeline); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenProgramPipelines()"); } ProgramPipeline::ProgramPipeline (const glw::Functions& gl) : m_gl (gl) , m_pipeline (0) { m_gl.genProgramPipelines(1, &m_pipeline); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenProgramPipelines()"); } ProgramPipeline::~ProgramPipeline (void) { m_gl.deleteProgramPipelines(1, &m_pipeline); } void ProgramPipeline::useProgramStages (deUint32 stages, deUint32 program) { m_gl.useProgramStages(m_pipeline, stages, program); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgramStages()"); } void ProgramPipeline::activeShaderProgram (deUint32 program) { m_gl.activeShaderProgram(m_pipeline, program); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glActiveShaderProgram()"); } bool ProgramPipeline::isValid (void) { glw::GLint status = GL_FALSE; m_gl.validateProgramPipeline(m_pipeline); GLU_EXPECT_NO_ERROR(m_gl.getError(), "glValidateProgramPipeline()"); m_gl.getProgramPipelineiv(m_pipeline, GL_VALIDATE_STATUS, &status); return (status != GL_FALSE); } // ShaderProgram ShaderProgram::ShaderProgram (const RenderContext& renderCtx, const ProgramSources& sources) : m_program(renderCtx.getFunctions()) { init(renderCtx.getFunctions(), sources); } ShaderProgram::ShaderProgram (const glw::Functions& gl, const ProgramSources& sources) : m_program(gl) { init(gl, sources); } void ShaderProgram::init (const glw::Functions& gl, const ProgramSources& sources) { try { bool shadersOk = true; for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) { for (int shaderNdx = 0; shaderNdx < (int)sources.sources[shaderType].size(); ++shaderNdx) { const char* source = sources.sources[shaderType][shaderNdx].c_str(); const int length = (int)sources.sources[shaderType][shaderNdx].size(); m_shaders[shaderType].reserve(m_shaders[shaderType].size() + 1); m_shaders[shaderType].push_back(new Shader(gl, ShaderType(shaderType))); m_shaders[shaderType].back()->setSources(1, &source, &length); m_shaders[shaderType].back()->compile(); shadersOk = shadersOk && m_shaders[shaderType].back()->getCompileStatus(); } } if (shadersOk) { for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx) m_program.attachShader(m_shaders[shaderType][shaderNdx]->getShader()); for (std::vector<AttribLocationBinding>::const_iterator binding = sources.attribLocationBindings.begin(); binding != sources.attribLocationBindings.end(); ++binding) m_program.bindAttribLocation(binding->location, binding->name.c_str()); DE_ASSERT((sources.transformFeedbackBufferMode == GL_NONE) == sources.transformFeedbackVaryings.empty()); if (sources.transformFeedbackBufferMode != GL_NONE) { std::vector<const char*> tfVaryings(sources.transformFeedbackVaryings.size()); for (int ndx = 0; ndx < (int)tfVaryings.size(); ndx++) tfVaryings[ndx] = sources.transformFeedbackVaryings[ndx].c_str(); m_program.transformFeedbackVaryings((int)tfVaryings.size(), &tfVaryings[0], sources.transformFeedbackBufferMode); } if (sources.separable) m_program.setSeparable(true); m_program.link(); } } catch (...) { for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx) delete m_shaders[shaderType][shaderNdx]; throw; } } ShaderProgram::~ShaderProgram (void) { for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) for (int shaderNdx = 0; shaderNdx < (int)m_shaders[shaderType].size(); ++shaderNdx) delete m_shaders[shaderType][shaderNdx]; } // Utilities deUint32 getGLShaderType (ShaderType shaderType) { static const deUint32 s_typeMap[] = { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_COMPUTE_SHADER }; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typeMap) == SHADERTYPE_LAST); DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typeMap))); return s_typeMap[shaderType]; } deUint32 getGLShaderTypeBit (ShaderType shaderType) { static const deUint32 s_typebitMap[] = { GL_VERTEX_SHADER_BIT, GL_FRAGMENT_SHADER_BIT, GL_GEOMETRY_SHADER_BIT, GL_TESS_CONTROL_SHADER_BIT, GL_TESS_EVALUATION_SHADER_BIT, GL_COMPUTE_SHADER_BIT }; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typebitMap) == SHADERTYPE_LAST); DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typebitMap))); return s_typebitMap[shaderType]; } qpShaderType getLogShaderType (ShaderType shaderType) { static const qpShaderType s_typeMap[] = { QP_SHADER_TYPE_VERTEX, QP_SHADER_TYPE_FRAGMENT, QP_SHADER_TYPE_GEOMETRY, QP_SHADER_TYPE_TESS_CONTROL, QP_SHADER_TYPE_TESS_EVALUATION, QP_SHADER_TYPE_COMPUTE }; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_typeMap) == SHADERTYPE_LAST); DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_typeMap))); return s_typeMap[shaderType]; } tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderInfo& shaderInfo) { return log << tcu::TestLog::Shader(getLogShaderType(shaderInfo.type), shaderInfo.source, shaderInfo.compileOk, shaderInfo.infoLog); } tcu::TestLog& operator<< (tcu::TestLog& log, const Shader& shader) { return log << tcu::TestLog::ShaderProgram(false, "Plain shader") << shader.getInfo() << tcu::TestLog::EndShaderProgram; } static void logShaderProgram (tcu::TestLog& log, const ProgramInfo& programInfo, size_t numShaders, const ShaderInfo* const* shaderInfos) { log << tcu::TestLog::ShaderProgram(programInfo.linkOk, programInfo.infoLog); try { for (size_t shaderNdx = 0; shaderNdx < numShaders; ++shaderNdx) log << *shaderInfos[shaderNdx]; } catch (...) { log << tcu::TestLog::EndShaderProgram; throw; } log << tcu::TestLog::EndShaderProgram; // Write statistics. { static const struct { const char* name; const char* description; } s_compileTimeDesc[] = { { "VertexCompileTime", "Vertex shader compile time" }, { "FragmentCompileTime", "Fragment shader compile time" }, { "GeometryCompileTime", "Geometry shader compile time" }, { "TessControlCompileTime", "Tesselation control shader compile time" }, { "TessEvaluationCompileTime", "Tesselation evaluation shader compile time" }, { "ComputeCompileTime", "Compute shader compile time" }, }; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_compileTimeDesc) == SHADERTYPE_LAST); bool allShadersOk = true; for (size_t shaderNdx = 0; shaderNdx < numShaders; ++shaderNdx) { const ShaderInfo& shaderInfo = *shaderInfos[shaderNdx]; log << tcu::TestLog::Float(s_compileTimeDesc[shaderInfo.type].name, s_compileTimeDesc[shaderInfo.type].description, "ms", QP_KEY_TAG_TIME, (float)shaderInfo.compileTimeUs / 1000.0f); allShadersOk = allShadersOk && shaderInfo.compileOk; } if (allShadersOk) log << tcu::TestLog::Float("LinkTime", "Link time", "ms", QP_KEY_TAG_TIME, (float)programInfo.linkTimeUs / 1000.0f); } } tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderProgramInfo& shaderProgramInfo) { std::vector<const ShaderInfo*> shaderPtrs (shaderProgramInfo.shaders.size()); for (size_t ndx = 0; ndx < shaderPtrs.size(); ndx++) shaderPtrs[ndx] = &shaderProgramInfo.shaders[ndx]; logShaderProgram(log, shaderProgramInfo.program, shaderPtrs.size(), shaderPtrs.empty() ? DE_NULL : &shaderPtrs[0]); return log; } tcu::TestLog& operator<< (tcu::TestLog& log, const ShaderProgram& shaderProgram) { std::vector<const ShaderInfo*> shaderPtrs; for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) { for (int shaderNdx = 0; shaderNdx < shaderProgram.getNumShaders((ShaderType)shaderType); shaderNdx++) shaderPtrs.push_back(&shaderProgram.getShaderInfo((ShaderType)shaderType, shaderNdx)); } logShaderProgram(log, shaderProgram.getProgramInfo(), shaderPtrs.size(), shaderPtrs.empty() ? DE_NULL : &shaderPtrs[0]); return log; } tcu::TestLog& operator<< (tcu::TestLog& log, const ProgramSources& sources) { log << tcu::TestLog::ShaderProgram(false, "(Source only)"); try { for (int shaderType = 0; shaderType < SHADERTYPE_LAST; shaderType++) { for (size_t shaderNdx = 0; shaderNdx < sources.sources[shaderType].size(); shaderNdx++) { log << tcu::TestLog::Shader(getLogShaderType((ShaderType)shaderType), sources.sources[shaderType][shaderNdx], false, ""); } } } catch (...) { log << tcu::TestLog::EndShaderProgram; throw; } log << tcu::TestLog::EndShaderProgram; return log; } } // glu