#include "Base.h" #include "Effect.h" #include "FileSystem.h" #define OPENGL_ES_DEFINE "#define OPENGL_ES\n" namespace gameplay { // Cache of unique effects. static std::map __effectCache; static Effect* __currentEffect = NULL; Effect::Effect() : _program(0) { } Effect::~Effect() { // Remove this effect from the cache. __effectCache.erase(_id); // Free uniforms. for (std::map::iterator itr = _uniforms.begin(); itr != _uniforms.end(); itr++) { SAFE_DELETE(itr->second); } if (_program) { // If our program object is currently bound, unbind it before we're destroyed. if (__currentEffect == this) { GL_ASSERT( glUseProgram(0) ); __currentEffect = NULL; } GL_ASSERT( glDeleteProgram(_program) ); _program = 0; } } Effect* Effect::createFromFile(const char* vshPath, const char* fshPath, const char* defines) { GP_ASSERT(vshPath); GP_ASSERT(fshPath); // Search the effect cache for an identical effect that is already loaded. std::string uniqueId = vshPath; uniqueId += ';'; uniqueId += fshPath; uniqueId += ';'; if (defines) { uniqueId += defines; } std::map::const_iterator itr = __effectCache.find(uniqueId); if (itr != __effectCache.end()) { // Found an exiting effect with this id, so increase its ref count and return it. GP_ASSERT(itr->second); itr->second->addRef(); return itr->second; } // Read source from file. char* vshSource = FileSystem::readAll(vshPath); if (vshSource == NULL) { GP_ERROR("Failed to read vertex shader from file '%s'.", vshPath); return NULL; } char* fshSource = FileSystem::readAll(fshPath); if (fshSource == NULL) { GP_ERROR("Failed to read fragment shader from file '%s'.", fshPath); SAFE_DELETE_ARRAY(vshSource); return NULL; } Effect* effect = createFromSource(vshPath, vshSource, fshPath, fshSource, defines); SAFE_DELETE_ARRAY(vshSource); SAFE_DELETE_ARRAY(fshSource); if (effect == NULL) { GP_ERROR("Failed to create effect from shaders '%s', '%s'.", vshPath, fshPath); } else { // Store this effect in the cache. effect->_id = uniqueId; __effectCache[uniqueId] = effect; } return effect; } Effect* Effect::createFromSource(const char* vshSource, const char* fshSource, const char* defines) { return createFromSource(NULL, vshSource, NULL, fshSource, defines); } void replaceDefines(const char* defines, std::string& out) { if (defines && strlen(defines) != 0) { out = defines; unsigned int pos; out.insert(0, "#define "); while ((pos = out.find(';')) != std::string::npos) { out.replace(pos, 1, "\n#define "); } out += "\n"; } #ifdef OPENGL_ES out.insert(0, OPENGL_ES_DEFINE); #endif } void replaceIncludes(const char* filepath, const char* source, std::string& out) { // Replace the #include "xxxx.xxx" with the sourced file contents of "filepath/xxxx.xxx" std::string str = source; size_t lastPos = 0; size_t headPos = 0; size_t tailPos = 0; size_t fileLen = str.length(); tailPos = fileLen; while (headPos < fileLen) { lastPos = headPos; if (headPos == 0) { // find the first "#include" headPos = str.find("#include"); } else { // find the next "#include" headPos = str.find("#include", headPos + 1); } // If "#include" is found if (headPos != std::string::npos) { // append from our last position for the legth (head - last position) out.append(str.substr(lastPos, headPos - lastPos)); // find the start quote " size_t startQuote = str.find("\"", headPos) + 1; if (startQuote == std::string::npos) { // We have started an "#include" but missing the leading quote " GP_ERROR("Compile failed for shader '%s' missing leading \".", filepath); return; } // find the end quote " size_t endQuote = str.find("\"", startQuote); if (endQuote == std::string::npos) { // We have a start quote but missing the trailing quote " GP_ERROR("Compile failed for shader '%s' missing trailing \".", filepath); return; } // jump the head position past the end quote headPos = endQuote + 1; // File path to include and 'stitch' in the value in the quotes to the file path and source it. std::string filepathStr = filepath; std::string directoryPath = filepathStr.substr(0, filepathStr.rfind('/') + 1); size_t len = endQuote - (startQuote); std::string includeStr = str.substr(startQuote, len); directoryPath.append(includeStr); const char* includedSource = FileSystem::readAll(directoryPath.c_str()); if (includedSource == NULL) { GP_ERROR("Compile failed for shader '%s' invalid filepath.", filepathStr.c_str()); return; } else { // Valid file so lets attempt to see if we need to append anything to it too (recurse...) replaceIncludes(directoryPath.c_str(), includedSource, out); SAFE_DELETE_ARRAY(includedSource); } } else { // Append the remaining out.append(str.c_str(), lastPos, tailPos); } } } void writeShaderToErrorFile(const char* filePath, const char* source) { std::string path = filePath; path += ".err"; FILE* file = FileSystem::openFile(path.c_str(), "wb"); int err = ferror(file); fwrite(source, 1, strlen(source), file); fclose(file); } Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, const char* fshPath, const char* fshSource, const char* defines) { GP_ASSERT(vshSource); GP_ASSERT(fshSource); const unsigned int SHADER_SOURCE_LENGTH = 3; const GLchar* shaderSource[SHADER_SOURCE_LENGTH]; char* infoLog = NULL; GLuint vertexShader; GLuint fragmentShader; GLuint program; GLint length; GLint success; // Replace all comma seperated definitions with #define prefix and \n suffix std::string definesStr = ""; replaceDefines(defines, definesStr); shaderSource[0] = definesStr.c_str(); shaderSource[1] = "\n"; std::string vshSourceStr = ""; if (vshPath) { // Replace the #include "xxxxx.xxx" with the sources that come from file paths replaceIncludes(vshPath, vshSource, vshSourceStr); if (vshSource && strlen(vshSource) != 0) vshSourceStr += "\n"; //writeShaderToErrorFile(vshPath, vshSourceStr.c_str()); // Debugging } shaderSource[2] = vshPath ? vshSourceStr.c_str() : vshSource; GL_ASSERT( vertexShader = glCreateShader(GL_VERTEX_SHADER) ); GL_ASSERT( glShaderSource(vertexShader, SHADER_SOURCE_LENGTH, shaderSource, NULL) ); GL_ASSERT( glCompileShader(vertexShader) ); GL_ASSERT( glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success) ); if (success != GL_TRUE) { GL_ASSERT( glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length) ); if (length > 0) { infoLog = new char[length]; GL_ASSERT( glGetShaderInfoLog(vertexShader, length, NULL, infoLog) ); infoLog[length-1] = '\0'; } // Write out the expanded shader file. if (vshPath) writeShaderToErrorFile(vshPath, shaderSource[2]); GP_ERROR("Compile failed for vertex shader '%s' with error '%s'.", vshPath == NULL ? "NULL" : vshPath, infoLog == NULL ? "" : infoLog); SAFE_DELETE_ARRAY(infoLog); // Clean up. GL_ASSERT( glDeleteShader(vertexShader) ); return NULL; } // Compile the fragment shader. std::string fshSourceStr; if (fshPath) { // Replace the #include "xxxxx.xxx" with the sources that come from file paths replaceIncludes(fshPath, fshSource, fshSourceStr); if (fshSource && strlen(fshSource) != 0) fshSourceStr += "\n"; //writeShaderToErrorFile(fshPath, fshSourceStr.c_str()); // Debugging } shaderSource[2] = fshPath ? fshSourceStr.c_str() : fshSource; GL_ASSERT( fragmentShader = glCreateShader(GL_FRAGMENT_SHADER) ); GL_ASSERT( glShaderSource(fragmentShader, SHADER_SOURCE_LENGTH, shaderSource, NULL) ); GL_ASSERT( glCompileShader(fragmentShader) ); GL_ASSERT( glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success) ); if (success != GL_TRUE) { GL_ASSERT( glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &length) ); if (length > 0) { infoLog = new char[length]; GL_ASSERT( glGetShaderInfoLog(fragmentShader, length, NULL, infoLog) ); infoLog[length-1] = '\0'; } // Write out the expanded shader file. if (fshPath) writeShaderToErrorFile(fshPath, shaderSource[2]); GP_ERROR("Compile failed for fragment shader (%s): %s", fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog); SAFE_DELETE_ARRAY(infoLog); // Clean up. GL_ASSERT( glDeleteShader(vertexShader) ); GL_ASSERT( glDeleteShader(fragmentShader) ); return NULL; } // Link program. GL_ASSERT( program = glCreateProgram() ); GL_ASSERT( glAttachShader(program, vertexShader) ); GL_ASSERT( glAttachShader(program, fragmentShader) ); GL_ASSERT( glLinkProgram(program) ); GL_ASSERT( glGetProgramiv(program, GL_LINK_STATUS, &success) ); // Delete shaders after linking. GL_ASSERT( glDeleteShader(vertexShader) ); GL_ASSERT( glDeleteShader(fragmentShader) ); // Check link status. if (success != GL_TRUE) { GL_ASSERT( glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length) ); if (length > 0) { infoLog = new char[length]; GL_ASSERT( glGetProgramInfoLog(program, length, NULL, infoLog) ); infoLog[length-1] = '\0'; } GP_ERROR("Linking program failed (%s,%s): %s", vshPath == NULL ? "NULL" : vshPath, fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog); SAFE_DELETE_ARRAY(infoLog); // Clean up. GL_ASSERT( glDeleteProgram(program) ); return NULL; } // Create and return the new Effect. Effect* effect = new Effect(); effect->_program = program; // Query and store vertex attribute meta-data from the program. // NOTE: Rather than using glBindAttribLocation to explicitly specify our own // preferred attribute locations, we're going to query the locations that were // automatically bound by the GPU. While it can sometimes be convenient to use // glBindAttribLocation, some vendors actually reserve certain attribute indices // and thereore using this function can create compatibility issues between // different hardware vendors. GLint activeAttributes; GL_ASSERT( glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttributes) ); if (activeAttributes > 0) { GL_ASSERT( glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &length) ); if (length > 0) { GLchar* attribName = new GLchar[length + 1]; GLint attribSize; GLenum attribType; GLint attribLocation; for (int i = 0; i < activeAttributes; ++i) { // Query attribute info. GL_ASSERT( glGetActiveAttrib(program, i, length, NULL, &attribSize, &attribType, attribName) ); attribName[length] = '\0'; // Query the pre-assigned attribute location. GL_ASSERT( attribLocation = glGetAttribLocation(program, attribName) ); // Assign the vertex attribute mapping for the effect. effect->_vertexAttributes[attribName] = attribLocation; } SAFE_DELETE_ARRAY(attribName); } } // Query and store uniforms from the program. GLint activeUniforms; GL_ASSERT( glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms) ); if (activeUniforms > 0) { GL_ASSERT( glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &length) ); if (length > 0) { GLchar* uniformName = new GLchar[length + 1]; GLint uniformSize; GLenum uniformType; GLint uniformLocation; unsigned int samplerIndex = 0; for (int i = 0; i < activeUniforms; ++i) { // Query uniform info. GL_ASSERT( glGetActiveUniform(program, i, length, NULL, &uniformSize, &uniformType, uniformName) ); uniformName[length] = '\0'; // null terminate if (uniformSize > 1 && length > 3) { // This is an array uniform. I'm stripping array indexers off it since GL does not // seem to be consistent across different drivers/implementations in how it returns // array uniforms. On some systems it will return "u_matrixArray", while on others // it will return "u_matrixArray[0]". char* c = strrchr(uniformName, '['); if (c) { *c = '\0'; } } // Query the pre-assigned uniform location. GL_ASSERT( uniformLocation = glGetUniformLocation(program, uniformName) ); Uniform* uniform = new Uniform(); uniform->_effect = effect; uniform->_name = uniformName; uniform->_location = uniformLocation; uniform->_type = uniformType; uniform->_index = uniformType == GL_SAMPLER_2D ? (samplerIndex++) : 0; effect->_uniforms[uniformName] = uniform; } SAFE_DELETE_ARRAY(uniformName); } } return effect; } const char* Effect::getId() const { return _id.c_str(); } VertexAttribute Effect::getVertexAttribute(const char* name) const { std::map::const_iterator itr = _vertexAttributes.find(name); return (itr == _vertexAttributes.end() ? -1 : itr->second); } Uniform* Effect::getUniform(const char* name) const { std::map::const_iterator itr = _uniforms.find(name); return (itr == _uniforms.end() ? NULL : itr->second); } Uniform* Effect::getUniform(unsigned int index) const { unsigned int i = 0; for (std::map::const_iterator itr = _uniforms.begin(); itr != _uniforms.end(); itr++, i++) { if (i == index) { return itr->second; } } return NULL; } unsigned int Effect::getUniformCount() const { return _uniforms.size(); } void Effect::setValue(Uniform* uniform, float value) { GP_ASSERT(uniform); GL_ASSERT( glUniform1f(uniform->_location, value) ); } void Effect::setValue(Uniform* uniform, const float* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniform1fv(uniform->_location, count, values) ); } void Effect::setValue(Uniform* uniform, int value) { GP_ASSERT(uniform); GL_ASSERT( glUniform1i(uniform->_location, value) ); } void Effect::setValue(Uniform* uniform, const int* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniform1iv(uniform->_location, count, values) ); } void Effect::setValue(Uniform* uniform, const Matrix& value) { GP_ASSERT(uniform); GL_ASSERT( glUniformMatrix4fv(uniform->_location, 1, GL_FALSE, value.m) ); } void Effect::setValue(Uniform* uniform, const Matrix* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniformMatrix4fv(uniform->_location, count, GL_FALSE, (GLfloat*)values) ); } void Effect::setValue(Uniform* uniform, const Vector2& value) { GP_ASSERT(uniform); GL_ASSERT( glUniform2f(uniform->_location, value.x, value.y) ); } void Effect::setValue(Uniform* uniform, const Vector2* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniform2fv(uniform->_location, count, (GLfloat*)values) ); } void Effect::setValue(Uniform* uniform, const Vector3& value) { GP_ASSERT(uniform); GL_ASSERT( glUniform3f(uniform->_location, value.x, value.y, value.z) ); } void Effect::setValue(Uniform* uniform, const Vector3* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniform3fv(uniform->_location, count, (GLfloat*)values) ); } void Effect::setValue(Uniform* uniform, const Vector4& value) { GP_ASSERT(uniform); GL_ASSERT( glUniform4f(uniform->_location, value.x, value.y, value.z, value.w) ); } void Effect::setValue(Uniform* uniform, const Vector4* values, unsigned int count) { GP_ASSERT(uniform); GP_ASSERT(values); GL_ASSERT( glUniform4fv(uniform->_location, count, (GLfloat*)values) ); } void Effect::setValue(Uniform* uniform, const Texture::Sampler* sampler) { GP_ASSERT(uniform); GP_ASSERT(uniform->_type == GL_SAMPLER_2D); GP_ASSERT(sampler); GL_ASSERT( glActiveTexture(GL_TEXTURE0 + uniform->_index) ); // Bind the sampler - this binds the texture and applies sampler state const_cast(sampler)->bind(); GL_ASSERT( glUniform1i(uniform->_location, uniform->_index) ); } void Effect::bind() { glUseProgram(_program) ; GLenum test = glGetError(); __currentEffect = this; } Effect* Effect::getCurrentEffect() { return __currentEffect; } Uniform::Uniform() : _location(-1), _type(0), _index(0) { } Uniform::Uniform(const Uniform& copy) { // hidden } Uniform::~Uniform() { // hidden } Effect* Uniform::getEffect() const { return _effect; } const char* Uniform::getName() const { return _name.c_str(); } const GLenum Uniform::getType() const { return _type; } }